Skip to content

Latest commit

 

History

History
557 lines (406 loc) · 16.6 KB

File metadata and controls

557 lines (406 loc) · 16.6 KB

八、使用协同过滤生成建议

协同过滤是使用包括多个代理,视点,数据源等之间的协作的技术对信息或模式进行过滤的过程。 协同过滤方法已应用于许多不同类型的数据,包括感测和监视数据,例如矿物勘探,大面积环境感测或多个传感器; 金融数据,例如集成了许多财务来源的金融服务机构; 或以用户数据等为重点的电子商务和 Web 应用中。

协同过滤方法背后的基本原理是,它通过查看自己的品味来寻找彼此相似的人。 假设某人主要喜欢动作片,那么它将尝试找到曾经看过类似电影的人,并且会推荐一个未被第一人看过但被第二人看过的人。 。

在本章中,我们将重点介绍以下类型的协同过滤:

  • 基于用户的协同过滤
  • 基于项目的协同过滤

推荐数据

我们将使用一组用户,他们为他们选择的电影提供了评级。 以下是一个字典对象,其中包含键形式的不同用户,以及电影词典形式的值,每个电影的值都是用户给出的等级:

movie_user_preferences={'Jill': {'Avenger: Age of Ultron': 7.0,
 'Django Unchained': 6.5,
 'Gone Girl': 9.0,
 'Kill the Messenger': 8.0},
'Julia': {'Avenger: Age of Ultron': 10.0,
 'Django Unchained': 6.0,
 'Gone Girl': 6.5,
 'Kill the Messenger': 6.0,
 'Zoolander': 6.5},
'Max': {'Avenger: Age of Ultron': 7.0,
 'Django Unchained': 7.0,
 'Gone Girl': 10.0,
 'Horrible Bosses 2': 6.0,
 'Kill the Messenger': 5.0,
 'Zoolander': 10.0},
'Robert': {'Avenger: Age of Ultron': 8.0,
 'Django Unchained': 7.0,
 'Horrible Bosses 2': 5.0,
 'Kill the Messenger': 9.0,
 'Zoolander': 9.0},
'Sam': {'Avenger: Age of Ultron': 10.0,
 'Django Unchained': 7.5,
 'Gone Girl': 6.0,
 'Horrible Bosses 2': 3.0,
 'Kill the Messenger': 5.5,
 'Zoolander': 7.0},
'Toby': {'Avenger: Age of Ultron': 8.5,
 'Django Unchained': 9.0,
 'Zoolander': 2.0},
'William': {'Avenger: Age of Ultron': 6.0,
 'Django Unchained': 8.0,
 'Gone Girl': 7.0,
 'Horrible Bosses 2': 4.0,
 'Kill the Messenger': 6.5,
 'Zoolander': 4.0}}

movie_user_preferences['William']['Gone Girl']
7.0

基于用户的协同过滤

让我们开始,通过查找彼此相似的用户来构建基于用户的协同过滤器。

寻找相似用户

当您获得有关人们喜欢的数据时,您需要一种方法来确定不同用户之间的相似性。 通过将每个用户与每个其他用户进行比较并计算相似度得分,可以确定不同用户之间的相似度。 可以使用 Pearson 相关性,欧几里得距离,曼哈顿距离等来计算该相似性分数。

欧几里得距离得分

欧式距离是空间中两点之间的最小距离。 让我们通过绘制观看过 Django Unchained 和 Avengers 的用户来了解这一点。

我们将创建一个数据帧,其中包含userdjangoavenger列,其中djangoavenger包含用户给出的评分:

>>> data = []
>>> for i in movie_user_preferences.keys():
 try:
 data.append( (i
 ,movie_user_preferences[i]['Django Unchained']
 ,movie_user_preferences[i]['Avenger: Age of Ultron']) )
 except:
 pass

>>> df = pd.DataFrame(data = data, columns = ['user', 'django', 'avenger'])
>>> df

The Euclidean distance score

使用前面的DataFrame的,我们将通过将Django保持为y轴,而将Avengers保持为x轴来绘制不同的用户:

>>> plt.scatter(df.django, df.avenger)
>>> plt.xlabel('Django')
>>> plt.ylabel('Avengers')
>>> for i,txt in enumerate(df.user):
 plt.annotate(txt, (df.django[i],df.avenger[i]))
>>> plt.show()

The Euclidean distance score

我们可以看到JillToby彼此距离很远,而RobertMax彼此非常接近。 让我们计算两者之间的欧几里德距离:

>>> #Euclidean distance between Jill and Toby rating
>>> sqrt(pow(8.5-7,2)+pow(9-6.5,2))

2.9154759474226504

>>> #Euclidean distance between Robert and Max rating
>>> sqrt(pow(8-7,2)+pow(7-7,2))

1.0

我们可以看到,用户之间的距离越远,欧几里得距离就越大。 从前面的代码中可以看出,欧氏距离越小,相似度就越大。 我们将欧几里德距离除以1,以便获得一个度量,该度量表示更高的数字具有更大的相似性。 我们还将在分母中添加1以避免得到ZeroDivisionError

>>> #Similarity Score based on Euclidean distance between Jill and Toby
>>> 1/(1 + sqrt(pow(8.5-7,2)+pow(9-6.5,2)) )
0.2553967929896867

>>> #Similarity Score based on Euclidean distance between Robert and Max
>>> 1/(1 + sqrt(pow(8-7,2)+pow(7-7,2)) )

0.5

让我们创建一个函数,该函数基于两个用户之间的欧几里得距离来计算相似度,其中除我们前面提到的的两部电影外,还考虑了他们观看的所有电影:

>>> # Returns a distance-based similarity score for person1 and person2
>>> def sim_distance(prefs,person1,person2):
 # Get the list of shared_items
 si={}
 for item in prefs[person1]:
 if item in prefs[person2]:
 si[item]=1

 # if they have no ratings in common, return 0
 if len(si)==0: return 0

 # Add up the squares of all the differences
 sum_of_squares=sum([pow(prefs[person1][item] - prefs[person2][item],2)
 for item in prefs[person1] if item in prefs[person2]])

 return 1/(1+sum_of_squares)

让我们应用前面的函数来计算SamToby之间的相似性得分:

>>> sim_distance(movie_user_preferences,'Sam','Toby')

0.03278688524590164

Pearson 相关性评分

我们已经在第 2 章,“推断统计”中研究了 Pearson 相关性。 欧几里得距离是用户彼此分开的距离,而 Pearson 相关性考虑了两个人之间的关联。 我们将使用 Pearson 相关性来计算两个用户之间的相似度得分。

让我们看看SamToby如何相互关联:

>>> def create_movie_user_df(input_data, user1, user2):
 data = []
 for movie in input_data[user1].keys():
 if movie in input_data[user2].keys():
 try:
 data.append( (movie
 ,input_data[user1][movie]
 ,input_data[user2][movie]) )
 except:
 pass

 return pd.DataFrame(data = data, columns = ['movie', user1, user2])

>>> df = create_movie_user_df(movie_user_preferences, 'Sam', 'William')
>>> df

The Pearson correlation score

一旦在DataFrame之前创建了,我们将像之前一样绘制散点图:

>>> plt.scatter(df.Sam, df.William)
>>> plt.xlabel('Sam')
>>> plt.ylabel('William')
>>> for i,txt in enumerate(df.movie):
 plt.annotate(txt, (df.Sam[i],df.William[i]))
>>> plt.show()

The Pearson correlation score

让我们计算和William之间的皮尔逊相关性:

>>> pearsonr(df.Sam,df.William)

(0.37067401970178415, 0.46945413268410929)

让我们看一下SamJulia之间的相关散点图:

>>> df = create_movie_user_df(movie_user_preferences, 'Sam', 'Julia')
>>> df

>>> plt.scatter(df.Sam, df.Julia)
>>> plt.xlabel('Sam')
>>> plt.ylabel('Julia')
>>> for i,txt in enumerate(df.movie):
 plt.annotate(txt, (df.Sam[i],df.Julia[i]))
>>> plt.show()

The Pearson correlation score

让我们计算SamJulia之间的皮尔逊相关性:

>>> pearsonr(df.Sam,df.Julia)

(0.88285183326025096, 0.047277507003439537)

我们可以看到SamJulia彼此非常相似,因为0.88的相关值接近 1。

现在,我们将创建一个函数,该函数将接收数据并计算两个用户之间的 Pearson 相关性:

>>> # Returns the Pearson correlation coefficient for p1 and p2
>>> def sim_pearson(prefs,p1,p2):
 # Get the list of mutually rated items
 si={}
 for item in prefs[p1]:
 if item in prefs[p2]: si[item]=1

 # Find the number of elements
 n=len(si)

 # if they are no ratings in common, return 0
 if n==0: return 0

 # Add up all the preferences
 sum1=sum([prefs[p1][it] for it in si])
 sum2=sum([prefs[p2][it] for it in si])

 # Sum up the squares
 sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
 sum2Sq=sum([pow(prefs[p2][it],2) for it in si])

 # Sum up the products
 pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])

 # Calculate Pearson score
 num=pSum-(sum1*sum2/n)
 den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
 if den==0: return 0

 r=num/den

 return r

让我们使用前面的函数来计算SamJulia之间的皮尔逊相关性,并验证其计算是否正确:

>>> sim_pearson(movie_user_preferences,'Sam','Julia')

0.8828518332602507

排名用户

一旦有了计算用户之间相似度的方法,我们便根据特定用户之间的相似度对它们进行排名。 我想认识与我最相似的人。 我们可以使用以下代码来实现:

>>> def top_matches(prefs,person,n=5,similarity=sim_pearson):
 scores=[(similarity(prefs,person,other),other)
 for other in prefs if other!=person]

 # Sort the list so the highest scores appear at the top
 scores.sort( )
 scores.reverse( )
 return scores[0:n]

让我们来看看与Sam相似的前三个:

>>> top_matches(movie_user_preferences,'Toby', 
 n = 3, similarity = sim_distance)

[(0.10526315789473684, 'Jill'),
(0.08163265306122448, 'William'),
(0.03278688524590164, 'Sam')]

推荐商品

一旦您知道与您相似的人,您现在就想知道推荐给您的电影。 下图显示了如何计算电影的得分,以便我们找出最推荐的电影是:

Recommending items

我们将相似性得分乘以每个用户的电影评分。 然后,我们将这个新分数相加,然后将其除以适用的相似性分数。 总而言之,我们将基于相似度分数得出加权平均值。

从前面的输出中,我们可以看到《Gone Girl》在被推荐方面得分很高,然后是 Kill the Messenger。

现在,我们将创建一个函数,该函数将通过包含上述逻辑为用户生成推荐:

>>> # Gets recommendations for a person by using a weighted average
>>> # of every other user's rankings
>>> def get_recommendations(prefs,person,similarity=sim_pearson):
 totals={}
 simSums={}
 for other in prefs:
 # don't compare me to myself
 if other==person: continue
 sim=similarity(prefs,person,other)

 # ignore scores of zero or lower
 if sim<=0: continue
 for item in prefs[other]:

 # only score movies I haven't seen yet
 if item not in prefs[person] or prefs[person][item]==0:
 # Similarity * Score
 totals.setdefault(item,0)
 totals[item]+=prefs[other][item]*sim
 # Sum of similarities
 simSums.setdefault(item,0)
 simSums[item]+=sim

 # Create the normalized list
 rankings=[(total/simSums[item],item) for item,total in totals.items( )]

 # Return the sorted list
 rankings.sort( )
 rankings.reverse( )
 return rankings

让我们使用前面的函数获得推荐:

>>> get_recommendations(movie_user_preferences,'Toby')

[(6.587965809121004, 'Gone Girl'),
(6.087965809121004, 'Kill the Messenger'),
(3.608127720528246, 'Horrible Bosses 2')]

>>> getRecommendations(movie_user_preferences,'Toby', similarity = sim_distance)

[(7.773043918833565, 'Gone Girl'),
(6.976295282563891, 'Kill the Messenger'),
(4.093380589669568, 'Horrible Bosses 2')]

现在,我们已经创建了一个基于用户的协同过滤器。

基于项目的协同过滤

基于用户的协同过滤查找用户之间的相似性,然后使用用户之间的这些相似性进行推荐。

基于项目的协同过滤可发现项目之间的相似性。 然后将其用于为用户查找新推荐。

首先要开始基于项目的协同过滤,我们首先必须将电影放在第一层中,然后将用户放在第二层中,以反转数据集:

>>> def transform_prefs(prefs):
 result={}
 for person in prefs:
 for item in prefs[person]:
 result.setdefault(item,{})

 # Flip item and person
 result[item][person]=prefs[person][item]
 return result

{'Avenger: Age of Ultron': {'Jill': 7.0,'Julia': 10.0,
 'Max': 7.0,
 'Robert': 8.0,
 'Sam': 10.0,
 'Toby': 8.5,
 'William': 6.0},
'Django Unchained': {'Jill': 6.5,
 'Julia': 6.0,
 'Max': 7.0,
 'Robert': 7.0,
 'Sam': 7.5,
 'Toby': 9.0,
 'William': 8.0},
'Gone Girl': {'Jill': 9.0,
 'Julia': 6.5,
 'Max': 10.0,
 'Sam': 6.0,
 'William': 7.0},
'Horrible Bosses 2': {'Max': 6.0, 'Robert': 5.0, 'Sam': 3.0, 'William': 4.0},
'Kill the Messenger': {'Jill': 8.0,
 'Julia': 6.0,
 'Max': 5.0,
 'Robert': 9.0,
 'Sam': 5.5,
 'William': 6.5},
'Zoolander': {'Julia': 6.5,
 'Max': 10.0,
 'Robert': 9.0,
 'Sam': 7.0,
 'Toby': 2.0,
 'William': 4.0}}

现在,我们希望为每部电影找到相似的电影:

>>> def calculate_similar_items(prefs,n=10):
 # Create a dictionary of items showing which other items they
 # are most similar to.
 result={}

 # Invert the preference matrix to be item-centric
 itemPrefs=transform_prefs(prefs)
 c=0
 for item in itemPrefs:
 # Status updates for large datasets
 c+=1
 if c%100==0: print "%d / %d" % (c,len(itemPrefs))
 # Find the most similar items to this one
 scores=top_matches(itemPrefs, item, n=n, similarity=sim_distance)
 result[item]=scores
 return result

>>> itemsim=calculate_similar_items(movie_user_preferences)
>>> itemsim

{'Avenger: Age of Ultron': [(0.034782608695652174, 'Django Unchained'),
 (0.023121387283236993, 'Gone Girl'),
 (0.022988505747126436, 'Kill the Messenger'),
 (0.015625, 'Horrible Bosses 2'),
 (0.012738853503184714, 'Zoolander')],
'Django Unchained': [(0.05714285714285714, 'Kill the Messenger'),
 (0.05063291139240506, 'Gone Girl'),
 (0.034782608695652174, 'Avenger: Age of Ultron'),
 (0.023668639053254437, 'Horrible Bosses 2'),
 (0.012578616352201259, 'Zoolander')],
'Gone Girl': [(0.09090909090909091, 'Zoolander'),
 (0.05063291139240506, 'Django Unchained'),
 (0.036036036036036036, 'Kill the Messenger'),
 (0.02857142857142857, 'Horrible Bosses 2'),
 (0.023121387283236993, 'Avenger: Age of Ultron')],
'Horrible Bosses 2': [(0.03278688524590164, 'Kill the Messenger'),
 (0.02857142857142857, 'Gone Girl'),
 (0.023668639053254437, 'Django Unchained'),
 (0.02040816326530612, 'Zoolander'),
 (0.015625, 'Avenger: Age of Ultron')],
'Kill the Messenger': [(0.05714285714285714, 'Django Unchained'),
 (0.036036036036036036, 'Gone Girl'),
 (0.03278688524590164, 'Horrible Bosses 2'),
 (0.02877697841726619, 'Zoolander'),
 (0.022988505747126436, 'Avenger: Age of Ultron')],
'Zoolander': [(0.09090909090909091, 'Gone Girl'),
 (0.02877697841726619, 'Kill the Messenger'),
 (0.02040816326530612, 'Horrible Bosses 2'),
 (0.012738853503184714, 'Avenger: Age of Ultron'),
 (0.012578616352201259, 'Django Unchained')]}

一旦我们在所有电影之间都有相似之处,我们便想为用户生成推荐。

下表显示Movie列下Toby所观看的电影以及Toby所给出的等级。 Movie列包含与Toby相似的电影。 以 R 为前缀的列是评级和相似性分数的乘积。

最后,我们通过对R前缀列求和,然后将其除以Movie列的相似性得分总和,来对这些值进行归一化。

下表显示了“杀死信使”为最受推荐的电影:

Item-based collaborative filtering

现在,我们希望通过包含以下逻辑来生成建议:

>>> def get_recommendedItems(prefs,itemMatch,user):
 userRatings=prefs[user]
 scores={}
 totalSim={}

 # Loop over items rated by this user
 for (item,rating) in userRatings.items( ):

 # Loop over items similar to this one
 for (similarity,item2) in itemMatch[item]:

 # Ignore if this user has already rated this item
 if item2 in userRatings: continue

 # Weighted sum of rating times similarity
 scores.setdefault(item2,0)
 scores[item2]+=similarity*rating

 # Sum of all the similarities
 totalSim.setdefault(item2,0)
 totalSim[item2]+=similarity

 # Divide each total score by total weighting to get an average
 rankings=[(score/totalSim[item],item) for item,score in scores.items( )]

 # Return the rankings from highest to lowest
 rankings.sort( )
 rankings.reverse( )
 return rankings 
 # Divide each total score by total weighting to get an average
 rankings=[(score/totalSim[item],item) for item,score in scores.items( )]

 # Return the rankings from highest to lowest
 rankings.sort( )
 rankings.reverse( )
 return rankings

让我们使用基于项目的推荐器为Toby生成推荐:

>>> get_recommendedItems(movie_user_preferences, itemsim,'Toby')

[(7.044841200971884, 'Kill the Messenger'),
(6.476296577225752, 'Horrible Bosses 2'),
(5.0651585538275095, 'Gone Girl')]

总结

在本章中,您学习了如何执行基于用户和基于项目的协同过滤。 您还了解了一些可用于计算用户和项目之间相似度的度量标准,以及如何应用此相似度为最终用户生成建议。

下一章将介绍不同的集成模型,这些模型基本上将多个模型结合起来以提高预测的表现。