0x00 下载MovieLens数据
1) 从网站http://grouplens.org/datasets/movielens/下载数据
2) u.item文件包含两列数据,为电影id和电影名称的对应关系;u.data文件包含四列数据,为用户id,电影id,评价,时间
3)读入数据
<code>
movie_list = {} #id:title
with open("./u.item") as f:
for line in f.readlines():
(mid, title) = line.split('|')[0:2]
movie_list[mid] = title
pref_by_people = {}
with open("./u.data") as f:
for line in f.readlines():
(uid, mid, rating) = line.split('\t')[0:3]
if not uid in pref_by_people.keys():
pref_by_people[uid] = {}
pref_by_people[uid][movie_list[mid]] = int(rating)
</code>
4)数据类型转换 {people:{movie:1}} -->> {movie:{people:1}}
<code>
def TransfromPref(pref):
re_pref = {}
for k1, v1 in pref.items():
for k2, v2 in v1.items():
if not k2 in re_pref.keys():
re_pref[k2] = {}
re_pref[k2][k1] = v2
return re_pref
pref_by_movie = TransfromPref(pref_by_people)
0x01 协同过滤两种方式
协同过滤:常常被用于分辨某位特定顾客可能感兴趣的东西,这些结论来自于对其他相似顾客对哪些产品感兴趣的分析。
欧几里德距离评价
1)以人们对事物的评价作为坐标轴,比较两人在坐标空间的距离,来判断人们偏好的相近程度
2)代码
<code>
def edclidean(prefs, person1, person2):
#找出两人均做出评价的事物
both_list = [item for item in prefs[person1].keys() if item in prefs[person2].keys()]
#计算距离
dis = math.sqrt(sum([(prefs[person1][item] - perfs[person2][item]) ** 2 for item in both_list]))
#以距离的倒数为返回值,两人偏好程度越就近,该值越大,假设不存在完全相同的两人,不考虑dis为0的情况
return 1/dis
</code>
prefs中以python字典嵌套字典的方式存储了每个人对于某时候的评价
prefs = {person:{item:1...}...},下同
皮尔逊相关系数评价
1)考虑到人们对于事物的评价标准不同,即有些人倾向打出更高的分数,而其他人更倾向于打出较低的分数,这是单纯的考虑逻辑空间距离就不能很好的得出相近的偏好程度(例:person1对item1和item2打分分别为6和8,person2对item1和item2打分分别为8和10,两人分数不相同,但两人的评价有相同的趋势,此时也认为两人偏好相同)
2)皮尔逊相关系数计算
3)代码
<code>
通过每个电影人们的评价计算出电影之间的相关系数,将上例中通过中每个人对电影的评价计算人们之间的相关系数同理
def Pearson(pref , movie1, movie2):
#找出对两部电影都评论的人
people_list = [person for person in pref[movie1].keys() if person in pref[movie2].keys()]
n = len(people_list)
if n == 0:
return 0
#计算评价和
sum1 = sum([pref[movie1][person] for person in people_list])
sum2 = sum([pref[movie2][person] for person in people_list])
#计算评价平方和
sumSq1 = sum([pref[movie1][person] ** 2 for person in people_list])
sumSq2 = sum([pref[movie2][person] ** 2 for person in people_list])
#计算评价成绩和
psum = sum([pref[movie1][person] * pref[movie2][person] for person in people_list])
# 皮尔逊相关系数计算
num = psum - sum1 * sum2 / n
den = sqrt((sumSq1 - (sum1 ** 2) / n) * (sumSq2 - (sum2 ** 2) / n))
if den == 0:
return 0
return num / den
</code>
0x02 根据电影推荐相关的电影
1)通过皮尔逊相关系数计算出每个电影的最相近的电影
<code>
def TopMatch(pref, movie, n = 5):
#计算给电影和每部电影的皮尔逊相关系数
scores = [(Pearson(pref_by_movie, movie, mov), mov) for mov in pref_by_movie.keys() if mov != movie]
#根据系数进行排序,并由大到小排序
scores.sort(key = lambda x:x[0], reverse = True)
return scores[0:n]
</code>
2)对每部电影计算最接近电影,得到match_list
<code>
def CreateMatchList(pref = pref_by_movie):
match_list = {}
for movie in pref.keys():
match_list[movie] = TopMatch(pref, movie, 5)
return match_list
match_list = CreateMatchList()
</code>
0x03 为用户推荐电影
1)找到我们已经看过的电影和评价,从match_list中找出那些和我们看过的影片相近程度更高的没看过的电影,用相关系数作为权值,计算每部电影的评分的加权平均值。
例:每一横列出我们看过的电影第一列是我们对它的评分,之后的没两列分别是新电影的相关系数和系数乘评分的值,总计记录的相关系数的和与相关系数与评分的乘机的和,最后一横做除法得到评分的加权平均数,即推荐得分,分数越高的越值得推荐
2)代码
<code>
def get_recommanded_items(pref = pref_by_people, match_list = match_list, user = '1'):
try:
user_ratings = pref[user] #找出用户看过的电影与评价
except KeyError:
print("no user")
return 0
scores = {} #记录加权和
totalsim = {} #记录评分和
for movie, rating in user_ratings.items(): #遍历当前用户评分电影
for sim, sim_movie in match_list[movie]: #遍历当前电影相近电影
if sim_movie in user_ratings.keys(): #如果用户看过该电影,跳出本次循环
continue
#记录加权和与评分和
if not sim_movie in scores.keys():
scores[sim_movie] = sim * rating
totalsim[sim_movie] = sim
scores[sim_movie] += sim * rating
totalsim[sim_movie] += sim
rankings = [(scores[sim_movie]/totalsim[sim_movie], sim_movie) for sim_movie in scores.keys() if totalsim[sim_movie] != 0]
#排序并取前5
rankings.sort(key=lambda x:x[0], reverse=True)
return rankings[0:5]
</code>
0x04 基于用户进行过滤还是基于物品进行过滤
1)本文采用了基于物品方式的过滤,即对每样物品计算和它相似的物品。当为某位用户推荐时,我们找到他过去评分靠前的物品,再找出这些物品相似的物品,用相关系数作为权值,计算每个物品的评分的加权平均值
2)基于用户的推荐即对每位用户求出和他取向相近的人,找出这些人中评分靠前的物品,用相关系数作为权值,计算每个物品的评分的加权平均值
3)物品间相似度的变化不会向人之间变化那么快,即相关系数计算不用那么频繁,可以事先计算好,不用在每次推荐时都计算
4)针对大量数据推荐列表时,基于物品的过滤明显比基于用户的过滤快,不过需要额外的存储空间开销。
5)对于稀疏数据集,基于物品的推荐明显优于基于用户的推荐;对于密集数据集,两者效果类似
0x05 测试
输入用户id得到推荐电影的前5位
第一次推荐由于需要计算match_list,花费较长时间