如何编写自己的推荐系统

摘要:推荐系统过去都是用在物品推荐上,Netflix是最典型的例子。当你浏览几个物品之后,它会给你推荐新的物品。你是否思考过这是如何工作的?或者应用到你自己的网站或者应用中?

作者将使用Python完成一个完整的、简单的推荐系统,并给出相应的代码。这里将使用电影点击率数据(1 - 5 星)作为源数据集。

小规模实例

在进行成千上万大规模点击率实验之前,先使用小规模的数据集来展示推荐系统是如何工作的,这样会很快看到一些有意义的结果。

作者使用的样列数据见链接HP代表<哈利波特>(Harry Potter)三部曲,TW代<表暮光之城>(Twilight),SW代表<星球大战>(Star Wars)前三部。

HP1 HP2 HP3 TW SW1 SW2 SW3
A 4 5 1
B 5 5 4
C 2 4 5
D 3 3

AD是四个评分用户。比较意外的是:用户D未观看第一部<哈利波特>就跑去看第二部<哈利波特>。让我们试着用已有的用户评分数据去评估用户D有多大可能去观看<哈利波特>第一部。这个过程也可以用来处理推荐系统中遇到的缺失数据问题,这部分会在稍后讲解。

找出缺失评分数据(D, HP1)最简单的方法是:看过第一部<哈利波特>的用户具有相似的品味,根据他们的观影评分来评估。本例的数据太少,只有4个用户,可能最终的推荐结果不太精确。但使用更多的用户数据会使得推荐结果更精确。

那我们如何找出与用户D 品味最相似的用户呢?余弦相似度(或称为余弦距离)可以解决这个问题。余弦相似度通过输入两个用户的评分,输出0 到1 之间的值(此值即为余弦相似度)。1 代表电影评分完全相同。下面我们就来找出用户最大的余弦相似度。

Cosine Distance/Similarity

一般公式为:

1
\frac{(a_1 \times b_1) + (a_2 \times b_2) + \ldots}{\sqrt{(a_1)^2 + (a_2)^2 + \ldots}\sqrt{(b_1)^2 + (b_2)^2 + \ldots}}

其中, a 和 b 代表用户。

下面让我们来计算用户 A (红色)和用户 B(蓝色)余弦相似度(cosine similarity):

1
\frac{(\color{red}4 \times \color{blue}5) + (\color{red}0 \times \color{blue}5) + (\color{red}0 \times \color{blue}4) + (\color{red}5 \times \color{blue}0) + (\color{red}1 \times \color{blue}0) + (\color{red}0 \times \color{blue}0) + (\color{red}0 \times \color{blue}0)}{\sqrt{\color{red}4^2 + \color{red}0^2 + \color{red}0^2 + \color{red}5^2 + \color{red}1^2 + \color{red}0^2 + \color{red}0^2}\sqrt{\color{blue}5^2 + \color{blue}5^2 + \color{blue}4^2 + \color{blue}0^2 + \color{blue}0^2 + \color{blue}0^2 + \color{blue}0^2 }}

上述中的零值对最终的结果没有影响,简化得到:

1
\frac{(\color{red}4 \times \color{blue}5)}{\sqrt{\color{red}4^2 + \color{red}5^2 + \color{red}1^2}\sqrt{\color{blue}5^2 + \color{blue}5^2 + \color{blue}4^2}}

这里只能比较用户A 和B ,因为用户C 并没有对HP1电影评分,所以对推荐用户D 观看HP1电影的工作没有啥帮助。我们可以计算出对应的余弦相似度:

  • [ ] 用户A 和 用户D 的余弦相似度= 0.0
  • [ ] 用户B 和 用户D 的余弦相似度= 0.435

所以,可以看出用户B 很明显有最大的余弦相似度。下面是相应的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_ = 0 # A missing value.
users = {
'A': [4, _, _, 5, 1, _, _],
'B': [5, 5, 4, _, _, _, _],
'C': [_, _, _, 2, 4, 5, _],
'D': [_, 3, _, _, _, _, 3],
}
def cosine_distance(user1, user2):
top = 0
user1bottom = 0
user2bottom = 0
for i in range(0, len(user1)):
top += user1[i] * user2[i]
user1bottom += user1[i] * user1[i]
user2bottom += user2[i] * user2[i]
return top / (sqrt(user1bottom) * sqrt(user2bottom))
print cosine_distance(users['A'], users['D'])
print cosine_distance(users['B'], users['D'])

需要注意的是:前面我们是把缺失值置为0。但这意味着我们把没有评分的电影都认为是用户不喜欢的电影(因为评分若为0代表用户不喜欢这部电影,而缺失评分值实际上只是未知,并不代表喜欢,也不代表不喜欢)。一般更好的做法是计算出一个平衡值(balanced value)来替代缺失数据。在本例中选择2.5更好,在改变_的值后我们得到:

  • [ ] 用户A 和 用户D 的余弦相似度= 0.915
  • [ ] 用户B 和 用户D 的余弦相似度= 0.952

我们找出用户B给出的电影评分,5 星好评。那说明用户D 也应该会喜欢<哈利波特>第一部,并给出5 星好评。

代码如下:

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
def estimate_rating(users, userName, movie):
user_best_match = None
user_best_match_dist = 0
for user in users:
# We don't want to calculate ourself.
if user == userName:
continue
# Ignore users that haven't rated the movie.
if users[user][movie] == 0:
continue
dist = cosine_distance(users[userName], users[user])
print '%s -> %s = %.3f' % (userName, user, dist)
if dist >= user_best_match_dist:
user_best_match_dist = dist
user_best_match = user
# Return the rating of the best matched user.
return users[user_best_match][movie]
HP1, HP2, HP3, TW, SW1, SW2, SW3 = range(0, 7)
print estimate_rating(users, 'D', HP1)

如果有更多的用户评分数据,我们将会得到更精确的结果。而且我们会给出固定数量的相似用户的平均相似值,而不是最大的相似值。

下期内容

在下篇文章中,作者使用真实场景的数据集进行实战。主要关注三部分:推荐算法的性能、推荐结果质量和推荐算法精度的计算。

http://elliot.land/how-to-write-your-own-recommendation-system-part-1


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image