介绍
协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分)并记录下来以达到过滤的目的进而帮助别人筛选信息,回应不一定局限于特别感兴趣的,特别不感兴趣信息的纪录也相当重要。 协同过滤又可分为评比(rating)或者群体过滤(social filtering)协同过滤以其出色的速度和健壮性,在全球互联网领域炙手可热。
以上来自于百度百科介绍,协同过滤(collaborative filtering)在我们推荐系统中发挥了巨大作用,譬如抖音会基于你的点赞记录等推送视频,淘宝会基于你的浏览记录等推送商品,这些其实都离不开协同过滤算法。
- 如网易云音乐上的喜欢这首歌的人也在听:
协同过滤我们一般可以将其分为两类:
- 基于user;
- 基于item(可能是商品,电影,视频等等)。
基于user的算法会先根据你的浏览记录,收藏记录等找到与你相似的人群,然后再将相似的人群中喜欢的商品,电影等再推送给你,如网易云音乐中的私人FM;
基于item的算法会是假如你查看了某件商品,然后算法会去找到与之相似的商品再来推荐给你,如淘宝上的看了又看:
算法整体逻辑来说其实很简单,主要是如何去找到相似的user or item,接下来会通过MovieLens数据集实现一个简单的基于用户的协同过滤算法。
数据集
MovieLens数据集包含多个用户对多部电影的评级数据,也包括电影元数据信息和用户属性信息,根据量级的大小又分为不同版本,本文选用的最小的1M版本。
- 官方网站:http://movielens.org
- 下载地址:http://files.grouplens.org/datasets/movielens/ml-latest-small.zip
文件下载下来会有三个数据文件以及一个readme文档:
- users.dat:用户信息,包括用户年纪,性别,职业等信息;
- movies.dat:电影信息,电影名,分类等信息;
- ratings.dat:用户对于电影的评分信息,这也是本次文章会主要用到的数据,数据示例如下:
user_id | movie_id | rating | timestamp |
---|---|---|---|
1 | 1193 | 5 | 978300760 |
2 | 661 | 3 | 978302109 |
3 | 3408 | 4 | 978300275 |
相似用户
为方便计算,我们将rating数据转换成一个(n,m)的稀疏矩阵,其中n为用户数,m为电影数目,x_{ij}的值表示用户i对于电影j的评分。
对于user_i的评分信息便是一个长度为m的数组,我们将user_i的评分信息与其他所有用户的评分信息去对比,找到最相似的那一个或者那一群用户。
如何去定义相似,选择很多,需要去结合不同的场景来选择:
- 欧式距离;
- 余弦相似度;
- Jaccard系数;
- 皮尔逊系数;
- 汉明距离;
- ...
具体的计算公式各位可自行去百度,我就不一一介绍了,毕竟Markdown里面敲数据公式可太难受了。
不知各位有没有发现,这一步其实与K-NN算法很类似,K-NN中是找到相似的k个用户然后去进行分类,我们其实也需要去找到相似的k个用户,然后根据这k个用户的评分记录去进行电影推荐。
推荐
通过上一步获取到相似的k个用户之后,根据这k个用户的评分我们便能得到一个待推荐的movie list,然后我们可以通过电影的热门程度或者评分高低将其依次推荐给用户,一个简单的推荐系统便算是完成了。
代码部分
- 完整代码可参见我的GitHub:我的GitHub——Movie_Recommend
- 核心代码:
"""
@File : co_filtering.py
@Time : 2019/8/29 12:22
@Author : AwesomeTang
"""
from data_helper import DataSet
from math import sqrt
class CoFiltering:
def __init__(self):
self.data = DataSet()
def pearson_sim(self, user1, user2):
"""
Calculate Pearson-Correlation-Coefficient of user1 & user2.
"""
user1_array = self.data.matrix[user1 - 1]
user2_array = self.data.matrix[user2 - 1]
length = len(user1_array)
sum1 = sum(user1_array)
sum2 = sum(user2_array)
sum_mul = self.multi(user1_array, user2_array)
sum_x2 = sum([i ** 2 for i in user1_array])
sum_y2 = sum([j ** 2 for j in user2_array])
num = sum_mul - (float(sum1) * float(sum2) / length)
den = sqrt((sum_x2 - float(sum1 ** 2) / length) * (sum_y2 - float(sum2 ** 2) / length))
return num / den
@staticmethod
def multi(x, y):
"""
To get two 1D-arrays' multiply result.
The two arrays must have the same size.
:param x: one array.
:param y: another array.
:return: multiply result.
"""
result = 0.
for i in range(len(x)):
result = x[i] * y[i]
return result
def most_similar(self, user1, top_n=5):
"""
To find TOP_N most similar users.
:param user1: user_id, NOT ARRAY, eg. 23
:param top_n: Just like what "TOP_N" said.
:return: LIKE "[(most_similar_user_1, score),...(most_similar_user_topN, score)]".
"""
result_collect = {}
for user2 in self.data.users:
if user2 == user1:
pass
else:
try:
result = self.pearson_sim(user1, user2)
result_collect[user2] = result
except IndexError:
pass
results_sorted = sorted(result_collect.items(), key=lambda item: item[1], reverse=True)[:top_n]
print('Most similar users: {}'.format(' | '.join([str(x[0]) for x in results_sorted])))
return results_sorted
def predict(self, user, top_n=5, recommend_num=5):
if user not in self.data.users:
raise ValueError('Cannot find user "{}", please check.'.format(user))
results = self.most_similar(user, top_n)
recommend = []
for user_id, val in results:
diff_list = list(self.data.matrix[user_id] - self.data.matrix[user])
temp = filter(lambda x: x[1] > 0, enumerate(diff_list))
recommend.extend(temp)
recommend = sorted(recommend, key=lambda x: x[1], reverse=True)
movie_list = []
while True:
for i in range(1, 6).__reversed__():
temp_list = filter(lambda x: x[1] == i, recommend)
temp_list = sorted(temp_list, key=lambda x: self.data.move_pop_rank[x[0]], reverse=True)
for x in temp_list:
movie_id = x[0] 1
if movie_id not in movie_list:
movie_list.append(movie_id)
if len(movie_list) >= recommend_num:
break
else:
continue
break
movie_list = [self.data.id2movie(x) for x in movie_list[:recommend_num]]
print('Recommend movies: {}'.format(' | '.join(movie_list)))
if __name__ == "__main__":
cf = CoFiltering()
cf.predict(1464, 5, 5)
最后
以上便是一个简单的协同过滤推荐算法的实现,当然在实际应用过程中,远比以上复杂,还需要考虑很多东西:
- item的热门程度和user的活跃程度,毕竟有时候我看了一篇文章并不是因为我有多喜欢,而是你们一直将它放在首页;
- 上下文关系,比如我买了牙膏之后不需要你推荐相似的牙膏,而可能更需要推荐一把牙刷;
- 时间因素,我昨天感兴趣的不代表我今天还感兴趣;
- 甚至在你对着你的算法沾沾自喜的时候,你老板走过来跟你说:瞅瞅你这推荐的啥东西,都是一些亏本的商品,能不能推荐点利润高的产品;
skr~ skr~~~