数据挖掘笔记十一: 协同过滤 & Slope One 算法

很久以前,我们上一些新闻网站,购物网站点过一些页面后,就会不停的有类似“相似推荐”的东西,我们基本上是不会点进去看的,因为推荐的太水了,不费脑子想就知道是基于一些标签的推荐,毫无新意。

现在的广告就有了很大进步,很多广告看上去就是 为你而设 的。关于推荐算法,最多使用的是 协同过滤。 最简单的协同过滤算法是 Slope One

今天就自己不用框架,纯手打一个 Slope One 算法出来!:grin:

Slope One算法

如下数据,已经三个用户对三部电影在豆花网上的评分如下,问号是因为他们没有评分,所以暂时没有,那么:求问号内的值。(也就是他们如果看了电影再来评,可能会评多少分)?

用户 丑人鱼 东游 长江8号
用户1 3 5 2
用户2 4 3
用户3 2 5

原理推演:

  1. 提取其它对<东游>有评价的用户的数据, 用户1,用户2的数据(取一列)[东游, 丑人鱼] [5,3],[3,4]
  2. 相加[5+3, 3+4] = [ 8, 7 ] 8-7 = 1,由于1这个差值是2个用户贡献出来的,所以除以2 = 0.5
  3. 那么预测用户3对<东游>的评分就是 2+0.5 = 2.5
  4. 重复到过程1,再取一列 [ 东游, 长江8号 ] 由于用户2没有数据,就得到了用户1 [5,2]
  5. 那么这个差值是5-2 = 3,由于是1个用户贡献出来的,所以除以1 = 3
  6. 那么再次预测用户3对<东游>的评分就是 5+3 = 8
  7. 那么根据加权平均算,根据已经参考过的数据算 (2.5*2 + 8*1) / (2 + 1) =4.3
  8. 结论:用户3对<东游>电影打分可能是4.3分
  9. 继续重复过程,可以将其它未打分的项都可以做预测了。

代码实现

了解了上面的过程原理,按思路写算法就行了,由于 Sklearn 工具包中没有相应的算法模块,所以自己手写一个即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class SlopeOne(object):
#self.diffs 矩阵存储评分矩阵,
#self.freqs 存储一对 items 被相同用户评分的数量。
def __init__(self):
self.diffs = {}
self.freqs = {}
# 根据提供的数据,构建self.diffs / self.freqs字典
def update(self, data):
# 遍历每个用户的每个评分数据
for user, prefs in data.iteritems():
# 确保子字典存在
for item, rating in prefs.iteritems():
self.freqs.setdefault(item, {})
self.diffs.setdefault(item, {})
# setdefault 作用:
#如果对于给定的键值/setdefault的第一个参数,
#字典中为对应value为空,
#则将setdefault的第二个参数赋值给它。
# 下面再次循环遍历user对应的prefs中的每一组评分
for item2, rating2 in prefs.iteritems():
self.freqs[item].setdefault(item2, 0)
self.diffs[item].setdefault(item2, 0.0)
# 使用整数0初始化次数,浮点型零初始化评分。
# 利用两个item是否同时被一个用户评分,
#对self.freqs进行更新
self.freqs[item][item2] += 1
# 利用两个item的评分之差,对self.diffs矩阵进行更新
self.diffs[item][item2] += rating - rating2
# 将两个item在diffs 矩阵与 freqs矩阵对应位置相除,
#结果保存到freqs中,即为两个item的平均差距
for item, ratings in self.diffs.iteritems():
for item2, rating in ratings.iteritems():
ratings[item2] /= self.freqs[item][item2]
# 对新的用户偏好,根据 self.diffs / self.freqs 对新用户进行评分预测
def predict(self, userprefs):
# 定义两个空字典,preds存储预测数据,freqs存储计数
preds, freqs = {}, {}
# 迭代每一个物品(被用户评分过的)
# 使用try/except跳过没有被评分的物品对
for item, rating in userprefs.iteritems():
for diffitem, diffratings in self.diffs.iteritems():
try:
freq = self.freqs[diffitem][item]
except KeyError:
continue
# 设置preds初始值为0.0, freqs初始值为0
preds.setdefault(diffitem, 0.0)
freqs.setdefault(diffitem, 0)
# 累加
preds[diffitem] += freq * (diffratings[item] + rating)
freqs[diffitem] += freq
# 在返回结果之前,进行过滤
# 返回一个 带权重预测值 的新字典
# 结果中除去了 用户已经评分过的内容 和 物品计数为零的内容
return dict([(item, value / freqs[item]) for item, value in preds.iteritems() if item not in userprefs and freqs[item] > 0])

使用

1
2
3
4
5
6
s = SlopeOne()
s.update(dict(alice=dict(squid=1.0, cuttlefish=4.0),
bob=dict(squid=1.0, ,cuttlefish = 1.0,octupus=3.0)))
prediction = s.predict(dict(squid=3.0, cuttlefish=4.0))
for item,rating in prediction.iteritems():
print item+":\t"+str(rating)

其它协同过滤算法

实际应用中,有很多推荐系统未必适用Slope One,基本可以根据业务场景自行选择,有些基于用户数据,有些基于物品数据,也有很多多次聚类的,根据具体场景不同,分析的方式也会有差异。