使用Python实现Slope One算法

我们将沿用第二章中编写的Python类,重复的代码我不在这里赘述。输入的数据是这样的:

  1. users2 = {"Amy": {"Taylor Swift": 4, "PSY": 3, "Whitney Houston": 4},
  2. "Ben": {"Taylor Swift": 5, "PSY": 2},
  3. "Clara": {"PSY": 3.5, "Whitney Houston": 4},
  4. "Daisy": {"Taylor Swift": 5, "Whitney Houston": 3}}

我们先来计算两两物品之间的差异,公式是:

使用Python实现Slope One算法 - 图1

计算后的输出结果应该如下表所示:

使用Python实现Slope One算法 - 图2

括号中的数值表示同时给这两个歌手评过分的用户数。

第一步

  1. def computeDeviations(self):
  2. # 获取每位用户的评分数据
  3. for ratings in self.data.values():

self.data是一个Python字典,它的values()方法可以获取所有键的值。比如上述代码在第一次迭代时,ratings变量的值为{“Taylor Swift”: 4, “PSY”: 3, “Whitney Houston”: 4}。

第二步

  1. def computeDeviations(self):
  2. # 获取每位用户的评分数据
  3. for ratings in self.data.values():
  4. # 对于该用户的每个评分项(歌手、分数)
  5. for (item, rating) in ratings.items():
  6. self.frequencies.setdefault(item, {})
  7. self.deviations.setdefault(item, {})

在这个类的初始化方法中,我们需要对self.frequencies和self.deviations进行赋值:

  1. def __init__(self, data, k=1, metric='pearson', n=5):
  2. ...
  3. # 以下变量将用于Slope One算法
  4. self.frequencies = {}
  5. self.deviations = {}

Python字典的setdefault()方法接受两个参数,它的作用是:如果字典中不包含指定的键,则将其设为默认值;若存在,则返回其对应的值。

第三步

  1. def computeDeviations(self):
  2. # 获取每位用户的评分数据
  3. for ratings in self.data.values():
  4. # 对于该用户的每个评分项(歌手、分数)
  5. for (item, rating) in ratings.items():
  6. self.frequencies.setdefault(item, {})
  7. self.deviations.setdefault(item, {})
  8. # 再次遍历该用户的每个评分项
  9. for (item2, rating2) in ratings.items():
  10. if item != item2:
  11. # 将评分的差异保存到变量中
  12. self.frequencies[item].setdefault(item2, 0)
  13. self.deviations[item].setdefault(item2, 0.0)
  14. self.frequencies[item][item2] += 1
  15. self.deviations[item][item2] += rating - rating2

还是用{“Taylor Swift”: 4, “PSY”: 3, “Whitney Houston”: 4}举例,在第一次遍历中,外层循环item = “Taylor Swift”,rating = 4;内层循环item2 = “PSY”,rating2 = 3,因此最后一行代码是对self.deviations[“Taylor Swift”][“PSY”]做+1的操作。

第四步

最后,我们便可计算出差异值:

  1. def computeDeviations(self):
  2. # 获取每位用户的评分数据
  3. for ratings in self.data.values():
  4. # 对于该用户的每个评分项(歌手、分数)
  5. for (item, rating) in ratings.items():
  6. self.frequencies.setdefault(item, {})
  7. self.deviations.setdefault(item, {})
  8. # 再次遍历该用户的每个评分项
  9. for (item2, rating2) in ratings.items():
  10. if item != item2:
  11. # 将评分的差异保存到变量中
  12. self.frequencies[item].setdefault(item2, 0)
  13. self.deviations[item].setdefault(item2, 0.0)
  14. self.frequencies[item][item2] += 1
  15. self.deviations[item][item2] += rating - rating2
  16. for (item, ratings) in self.deviations.items():
  17. for item2 in ratings:
  18. ratings[item2] /= self.frequencies[item][item2]

完成了!仅仅用了18代码我们就实现了这个公式:

使用Python实现Slope One算法 - 图3

让我们测试一下:

  1. >>> r = recommender(users2)
  2. >>> r.computeDeviations()
  3. >>> r.deviations
  4. {'PSY': {'Taylor Swift': -2.0, 'Whitney Houston': -0.75}, 'Taylor Swift': {'PSY': 2.0, 'Whitney Houston': 1.0}, 'Whitney Houston': {'PSY': 0.75, 'Taylor Swift': -1.0}}

结果和我们之前手工计算的一致:

使用Python实现Slope One算法 - 图4

感谢Bryan O’Sullivan,这里用Python实现的Slope One算法正是基于他的成果。

使用Python实现Slope One算法 - 图5

加权的Slope One算法:推荐逻辑的实现

使用Python实现Slope One算法 - 图6

  1. def slopeOneRecommendations(self, userRatings):
  2. recommendations = {}
  3. frequencies = {}
  4. # 遍历目标用户的评分项(歌手、分数)
  5. for (userItem, userRating) in userRatings.items():
  6. # 对目标用户未评价的歌手进行计算
  7. for (diffItem, diffRatings) in self.deviations.items():
  8. if diffItem not in userRatings and userItem in self.deviations[diffItem]:
  9. freq = self.frequencies[diffItem][userItem]
  10. recommendations.setdefault(diffItem, 0.0)
  11. frequencies.setdefault(diffItem, 0)
  12. # 分子
  13. recommendations[diffItem] += (diffRatings[userItem] + userRating) * freq
  14. # 分母
  15. frequencies[diffItem] += freq
  16. recommendations = [(k, v / frequencies[k]) for (k, v) in recommendations.items()]
  17. # 排序并返回
  18. recommendations.sort(key=lambda artistTuple: artistTuple[1], reverse=True)
  19. return recommendations
  20. >>> r.slopeOneRecommendations(users2['Ben'])
  21. [('Whitney Houston', 3.375)]

MovieLens数据集

让我们在另一个数据集上尝试一下Slope One算法。

MovieLens数据集是由明尼苏达州大学的GroupLens研究项目收集的,是用户对电影的评分。这个数据集可以在www.grouplens.org下载,有三种大小,这里我使用的是最小的那个,包含了943位用户对1682部电影的评价,约10万条记录。

我们一起来测试一下:

  1. >>> r = recommender(0)
  2. >>> r.loadMovieLens('/Users/raz/Downloads/ml-100k/')
  3. 102625
  4. >>> r.computeDeviations() # 大约需要50秒
  5. >>> r.slopeOneRecommendations(r.data['25'])
  6. [('Aiqing wansui (1994)', 5.674418604651163), ('Boys, Les (1997)', 5.523076923076923), ...]

作业

  1. 看看Slope One的推荐结果是否靠谱:对数据集中的10部电影进行评分,得到的推荐结果是否是你喜欢的电影呢?
  2. 实现修正的余弦相似度算法,比较一下两者的运算效率。
  3. (较难)我的笔记本电脑有8G内存,在尝试用Slope One计算图书漂流站数据集时报内存溢出了。那个数据集中有27万本书,因此需要保存超过7300万条记录的Python字典。这个字典的数据是否很稀疏呢?修改算法,让它能够处理更多数据吧。

使用Python实现Slope One算法 - 图7

祝贺大家学完第三章了