recommender system

پیشنهاد فیلم مشابه بر اساس همبستگی-Correlation

سیستم‌های پیشنهاد دهنده در محیط های مختلف قابل مشاهده هستند. برای مثال در music Player که بر اساس سلیقه ما پیشنهاداتی برای موسیقی ارائه می‌شود.در این نوشته قصد داریم به صورت ابتدایی سیستم recommender را پیاده‌سازی کنیم.

این سیستم‌ها بر اساس دیتا عمل می‌کنند و دو روش معروف آن با نام‌های زیر شناخته می‌شوند:

  • 1- Collaborative filtering Based
  • 2- Content based

در روش Collaborative based بر اساس رفتاری که سایر کاربران داشته‌اند به کاربر فعلی پیشنهاداتی داده می‌شود.

در روش Content based بر اساس ویژگی‌هایی که یک ایتم دارد، پیشنهاداتی داده می‌شود. برای مثال اگر شخصی در فروشگاه آنلاین، دوچرخه خریداری کند، سیستم پیشنهاددهنده، کالاهای جانبی (مثلا، کلاه و …) نیز به او پیشنهاد می‌دهد. یا اگر فردی لباس خریداری کند، رنگ‌های دیگری از آن لباس نیز ممکن است به او پیشنهاد شود. یا شخصی که در سایت آمازون معمولا کتابهای علمی خریداری می‌کند، پیشنهادات داده شده به این شخص به احتمال زیاد در همین حوزه خواهد بود.

حال به سراغ نوتبوک می‌رویم و سعی می‌کنیم یک سیستم پیشنهاد دهنده تشکیل دهیم. در ابتدا کتابخانه‌های مورد نیاز را ایمپورت می‌کنیم:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

سپس دیتای خود را فرا می‌خوانیم (در بخش منابع پایان متن، دیتاها در دسترس هستند):

df1 = pd.read_csv('u_data.txt') 
df1

مشاهده می‌شود که دیتای ما یک سری داده هستند که با اسلش(\) از یکدیگر جدا شده‌اند. اما بر اساس domain knowledge می‌دانیم که عدد اول user_id ، عدد دوم Item_id و … هستند. کاری که باید کنیم این است که آنها را در قالب استاندارد در بیاوریم.

column_names = ['user_id', 'item_id', 'rating', 'timestamp']
df = pd.read_csv('u_data.txt', sep='\t', names=column_names)

کاری که کد بالا می‌کند این است که دیتا‌ها را بر اساس t\ جداسازی (seperate) می‌کند.

df:

دیتاست بعدی را نیز می‌خوانیم:

movie_titles = pd.read_csv("Movie_Id_Titles.txt")
movie_titles.head()

با توجه به وجود یک ستون مشترک به نام item_id ، می‌توانیم این دو دیتاست را در کنار هم قرار دهیم یا merge کنیم. این کار باعث می‌شود ستون title به دیتاست اول (u.data) افزوده شود:

df = pd.merge(df,movie_titles,on='item_id')
df.head()

می‌خواهیم ببینیم هر فیلم چه تعداد rate دریافت کرده است و متوسط آن چقدر بوده است. برای این کار از groupby استفاده می‌کنیم:

1: df.groupby('title')['rating'].mean().sort_values(ascending=False).head(15)

2:df.groupby('title')['rating'].count().sort_values(ascending=False).head()

دستور اول بالا می‌گوید، دیتا فریم df را در نظر بگیر و عمل groupby را روی title انجام بده و در این کار میانگین rating را در نظر بگیر. (و از بیشترین به کمترین sort کن.)

دستور دوم کار مشابهی را انجام می‌دهد با این تفاوت که به تعداد rating توجه می‌کند. (و از بیشترین به کمترین sort می‌کند.) حال آنچه داریم را با استفاده از pandasدر قالب دیتافریم در می‌آوریم:

1- بر اساس میانگین

ratings = pd.DataFrame(df.groupby('title')['rating'].mean())
ratings.head()

2- بر اساس تعداد فیدبک

ratings['num of ratings'] = pd.DataFrame(df.groupby('title')['rating'].count())
ratings.head()

حال می‌خواهیم اطلاعاتی در مورد دیتافریم خود به صورت بصری داشته باشیم.

plt.figure(figsize=(10,4))
sns.histplot(data=ratings, x="num of ratings")
فیلم‌هایی که ریتینگ آنها صفر بوده (فیدبک نداشته‌اند)، حدود 600 تا بوده‌اند.
plt.figure(figsize=(10,4))
sns.histplot(data=ratings, x="rating")
اغلب فیلم‌ها ریتینگ بین 3 تا 4 دریافت کرده‌اند.

و نمودار jointplot بر حسب دو شاخص تعداد فیدبک و مقدار هر فیدبک:

sns.jointplot(x='rating',y='num of ratings',data=ratings,alpha=0.5)

شکل بالا نشان می‌دهد که فیلم‌هایی که ریتینگ دریافت نکرده‌اند، پرتعداد بوده‌اند و همچینن فیلم‌های زیادی امتیازی بین 3 تا 4 دریافت کرده‌اند.

ساخت سیستم پیشنهاد دهنده:

حال یک دیتا فریم می‌سازیم که محور عمودی آن user_ID و محور افقی آن عناوین فیلم‌ها باشد.

moviemat = df.pivot_table(index='user_id',columns='title',values='rating')
moviemat.head()

اگر فیلمی rate نداشته باشد، NaN ثبت شده است. برای مثال یوزر شماره یک، به فیلم شماره 101 امتیاز 2 را داده است. طبیعی است که هر شخص به دلیل محددیت زمان و تعداد بالای ویدئو‌ها تنها تعدادی از فیلم‌ها را دیده باشد. در این مسئله تلاشی برای پر کردن خانه‌های NaN نمی‌کنیم، زیرا ذات مسئله همین است. مجددا داده‌ها را بر اساس تعداد ریتینگ sort می‌کنیم:

ratings.sort_values('num of ratings',ascending=False).head(10)
فیلم جنگ ستارگان بیشترین تعداد فیدبک (584 تا) و متوسط امتیاز 4.35 از 5 را داشته است.

حال دو فیلم را انتخاب می‌کنیم. یکی جنگ ستارگان (star wars) که ژانر علمی تخیلی دارد و دیگری فیلم liar liar با ژانر کمدی. از دیتافریم moviemat ستون مرتبط با این دو فیلم را جدا می‌کنیم:

starwars_user_ratings = moviemat['Star Wars (1977)']
starwars_user_ratings.head()
امتیاز داده شده 5 کاربر به فیلم جنگ ستارگان

با استفاده از متود corrwith می‌توانیم همبستگی یا correlation بین دو سری را بدست بیاوریم:

similar_to_starwars = moviemat.corrwith(starwars_user_ratings)
similar_to_starwars 
میزان همبستگی هر فیلم با فیلم جنگ ستارگان با یک عدد بین 1- تا 1 نمایش داده شده است.

حال تنها مقادیر NaN را حذف می‌کنیم و به جای سری از دیتا‌فریم استفاده می‌کنیم و نام تک ستون موجود را نیز Correlation می‌گذاریم:

corr_starwars = pd.DataFrame(similar_to_starwars,columns=['Correlation'])
corr_starwars.dropna(inplace=True)
corr_starwars.head()

حال این Correlation را sort می‌کنیم:

corr_starwars.sort_values('Correlation',ascending=False).head(10)
زمانی که شاخص همبستگی یک باشد، یعنی شباهت زیادی بین آن دو فیلم وجود دارد.

اما این معیار صحیحی نیست. زیرا مقدار ریتینگ مهم نیست، بلکه تعداد و مقدار مهم است. مثلا فیلمی که در لیست بالا شاخص همبستگی یک دریافت کرده است ممکن است تنها یک رای (فیدبک) دریافت کرده باشد. در ابتدا تعداد ریت را به دیتافریم خود اضافه می‌کنیم:

corr_starwars = corr_starwars.join(ratings['num of ratings'])
corr_starwars.head()

سپس فیلم‌هایی انتخاب می‌کنیم که حداقل صد ریتینگ دریافت کرده باشند. بنابراین با استفاده از conditional matrix فیلم‌هایی که کمتر از صد ریتینگ دریافت کرده باشند را drop می‌کنیم.

corr_starwars[corr_starwars['num of ratings']>100].sort_values('Correlation',ascending=False).head()
بیشترین شباهت با خود فیلم وجود دارد که مقدار همبستگی یک دریافت کرده است.

اگر شاخص همبستگی 1- باشد یعنی بیشترین شباهت اما در جهت معکوس بین آن دو وجود دارد.

همین مراحل را برای فیلم دوم (Liar Liar) هم اجرا می‌کنیم:

liarliar_user_ratings = moviemat['Liar Liar (1997)']
similar_to_liarliar = moviemat.corrwith(liarliar_user_ratings)

corr_liarliar = pd.DataFrame(similar_to_liarliar,columns=['Correlation'])
corr_liarliar.dropna(inplace=True)
corr_liarliar = corr_liarliar.join(ratings['num of ratings'])
corr_liarliar[corr_liarliar['num of ratings']>100].sort_values('Correlation',ascending=False).head()

منابع:

1- گیت هاب (دانلود دیتاست‌ها)

2- کانال آموزش-DataroadMap – مونا حاتمی

3- my-github

دیدگاهی بنویسید

نشانی ایمیل شما منتشر نخواهد شد.