动手实现基于协同过滤的电影推荐系统

项亮的《推荐系统实践》是一本面向推荐系统初学者的好书。这本书中间花了不少的篇幅去介绍了推荐系统中很重要的一个算法:协同过滤。囿于书中的篇幅限制,作者只给出了核心部分的代码。基于此书内容,我动手实现了基于用户的协同过滤算法和基于物品的协同过滤算法,并在MovieLens数据集上做了测试,效果令人满意。现在总结一下自己对协同过滤系统的理解和动手实践过程的经验。

本电影推荐系统已开源在:https://github.com/fuxuemingzhu/MovieLens-Recommender

head.jpg

协同过滤理论

协同过滤算法(Collaborative Filtering,CF)是只基于用户的历史行为对用户未来的购买习惯进行预测的一种算法。对于协同过滤的算法,最著名的、在业界得到最广泛应用的算法是基于邻域的方法,而基于邻域的方法主要包含下面两种算法。

  • 基于用户的协同过滤算法(UserBasedCF) 这种算法给用户推荐和他兴趣相似的其他用户喜欢的物品。
  • 基于物品的协同过滤算法(ItemBasedCF) 这种算法给用户推荐和他之前喜欢的物品相似的物品。

(对于两种算法的基本概念我就不详细解读了,可以看《推荐系统实践》或者IBM的文章推荐引擎初探。)

因为这两种算法都是基于求邻域的,并且直观上UserBasedCF更容易理解,所以UserBasedCF比ItemBasedCF提出的更早一点。关于协同过滤理论,最值得一看的论文就是亚马逊发表的《Amazon.com Recommendations Item-to-Item Collaborative Filtering》。这篇论文是ItemBasedCF开山之作,很值得学习。

这篇论文的主要思想是:

在 User-based 方法中,随着用户数量的不断增多,在大数量级的用户范围内进行“最近邻搜索”会成为整个算法的瓶颈。Item-based 方法通过计算项之间的相似性来代替用户之间的相似性。对于项来讲,它们之间的相似性要稳定很多,因此可以离线完成工作量最大的相似性计算步骤,从而大大降低了在线计算量,提高推荐效率。

在 Item-based 方法中,要对 A 和 B 进行项相似性计算,通常分为两步:1)找出同时对 A 和 B 打过分的组合;2)对这些组合进行相似度计算,常用的算法包括:皮尔森相关系数、余弦相似性、调整余弦相似性和条件概率等。

这篇论文中也提出了可以只计算购买了这个物品的用户和其购买的其他物品的相似性来简化计算,项亮的书里也有用到,所以我实现的时候也采用了。

其实,很容易看出协同过滤算法的思想非常简单淳朴:用户之前的行为记录会反映出用户-用户的相关性(UserBasedCF)或者物品-物品的相关性(ItemBasedCF)。也就是UserBasedCF是基于用户的历史信息求用户的相关性,然后根据该用户的相似用户购买了的物品产生推荐信息;ItemBasedCF是基于用户的历史信息求物品的相关性,然后根据用户购买了的物品产生推荐信息。

评测指标

本文采用的评测指标和《推荐系统实践》一致,分为精确率,召回率,覆盖率和新颖度。

精确率描述最终的推荐列表中有多少比例是发生过的用户—物品评分记录;

召回率描述有多少比例的用户—物品评分记录包含在最终的推荐列表中;

覆盖率反映了推荐算法发掘长尾的能力,覆盖率越高,说明推荐算法越能够将长尾中的物品推荐给用户;

新颖度反映了推荐列表中物品的平均流行度。如果推荐出的物品都很热门,说明推荐的新颖度较低,否则说明推荐结果比较新颖。

对用户u推荐N个物品(记为R(u)),令用户u在测试集上喜欢的物品集合为T(u),那么各个评测指标的公式如下:

精确率 召回率 覆盖率

算法步骤

基于用户的协同过滤算法

基于用户的协同过滤算法

基于用户的协同过滤算法主要包括两个步骤。

(1) 找到和目标用户兴趣相似的用户集合。
(2) 找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。

求用户的相似性矩阵的时候,使用的余弦相似度。

余弦相似度

由于两个用户之间可能根本没有购买过同样的物品,所以直接求用户两两之间的相似性矩阵浪费计算资源。所以使用物品—用户倒排表,这样可以很轻易地看出哪些用户购买了同样的商品,只要两个用户在同一个物品的用户表格里出现,就直接把他们两个用户的共现矩阵C[u][v]+1,最终就可以得到所有用户之间不为0的C[u][v]。

物品—用户倒排表

上面计算的是余弦相似度的分子,如果要求用户之间的相似性矩阵还要除以用户购买物品个数的乘积的算数平方根。

得到用户之间的兴趣相似度后, UserCF算法会计算与该用户兴趣最相似的K个用户的购买记录中的每个物品的兴趣程度。如下的公式度量了UserCF算法中用户u对物品i的感兴趣程度:

usercf-interest.png

所以,只要找出和该用户最相似的K个用户的购买记录,对每个商品打分,得分最高的N个物品推荐给用户即可。

基于用户的协同过滤算法的改进

上面的基于用户的协同过滤算法没有对流行的商品进行惩罚,更有根据的做法是惩罚用户u和用户v共同兴趣列表中热门物品对他们相似度的影响。该改进算法称为User-IIF算法。

User-IIF算法

基于物品的协同过滤算法

基于物品的协同过滤算法

根据影响最广的,被引用的次数也最多的一篇推荐系统论文《Item-Based Collaborative Filtering Recommendation Algorithms》,Item-based算法分为两步:

(1)相似度计算,得到各item之间的相似度

  • 基于余弦(Cosine-based)的相似度计算
  • 基于关联(Correlation-based)的相似度计算
  • 调整的余弦(Adjusted Cosine)相似度计算

(2)预测值计算,对用户未打分的物品进行预测

  • 加权求和。用户u已打分的物品的分数进行加权求和,权值为各个物品与物品i的相似度,然后对所有物品相似度的和求平均,计算得到用户u对物品i打分。
  • 回归。如果两个用户都喜欢一样的物品,因为打分习惯不同,他们的欧式距离可能比较远,但他们应该有较高的相似度 。在通过用线性回归的方式重新估算一个新的R(u,N).

本文采用的是余弦相似度和加权求和的方式,主要的步骤和基于用户的协同过滤算法很接近,就不再赘述,可以看书、论文或者我的代码。

基于物品的协同过滤算法的改进

考虑到可能有用户对物品产生了大量的行为,那么这个用户的购买记录将对众多的书产生了相似度影响。所以可以对用户活跃度进行惩罚,活跃用户对物品相似度的贡献应该小于不活跃的用户,即ItemCF-IUF算法:

ItemCF-IUF算法

电影推荐系统实现

我在实现MovieLens-Recommender时,花了很多的时间做了很好的优化。把不同的推荐算法分成独立文件,每个文件的每个函数都有详细的注释,变量名等通俗易懂,很适合详细看看。

推荐过程分为4步:

  1. 构造训练集和预测集
  2. 训练推荐系统模型
  3. 给出推荐结果
  4. 评价推荐结果

为了对比协同过滤和其他推荐算法的区别,所以我一共实现了六种推荐算法:

  • UserCF
  • ItemCF
  • UserCF-IIF
  • ItemCF-IUF
  • Random
  • Most Popular

本项目使用的数据集是Movielens-1M和Movielens-100k,当然也可以很简单的换成其他数据集。运行的方式很简单,修改main.py里的参数为你想要的即可:

dataset_name = 'ml-100k'
# dataset_name = 'ml-1m'
# model_type = 'UserCF'
# model_type = 'UserCF-IIF'
# model_type = 'ItemCF'
# model_type = 'Random'
# model_type = 'MostPopular'
model_type = 'ItemCF-IUF'
test_size = 0.1

另外,因为当test_size不变时,训练的模型也不会有变化,所以我在运行的时候会保存已有模型,再次运行相同参数的推荐时不用再重新训练模型,大大加快了推荐速度。在求相似性矩阵时,使用defaultdict可以大幅优化速度,这个也是我的一个经验。

运行时会打印进度,最后给出推荐结果,比如在ml-1m上训练的ItemCF的推荐结果如下:

recommend for userid = 1:
['1196', '364', '1265', '318', '2081', '1282', '1198', '2716', '1', '2096']

recommend for userid = 100:
['2916', '1580', '457', '1240', '589', '1291', '780', '1036', '1610', '1214']

recommend for userid = 233:
['1022', '594', '1282', '2087', '2078', '1196', '608', '2081', '593', '1393']

recommend for userid = 666:
['296', '1704', '593', '356', '1196', '589', '1580', '50', '1393', '1']

recommend for userid = 888:
['2916', '457', '480', '2628', '1265', '1610', '1198', '1573', '2762', '1527']

打印完推荐结果之后,会评估模型。评估模型的方式是对每个用户进行推荐,最后使用评估指标进行评测,所以这个步骤可能相对较慢。比如某次的评估过程如下:

Test recommendation system start...
 steps(0), 0.10 seconds have spent..
 steps(1000), 291.42 seconds have spent..
 steps(2000), 627.60 seconds have spent..
 steps(3000), 898.21 seconds have spent..
 steps(4000), 1219.94 seconds have spent..
 steps(5000), 1523.29 seconds have spent..
 steps(6000), 1817.46 seconds have spent..
Test recommendation system success.
total  step number is 6040
total 1829.26 seconds have spent

precision=0.1900    recall=0.1147    coverage=0.1673    popularity=7.3911

推荐结果评价

在测试集大小选为0.1的情况下,使用本项目对两个数据集进行了推荐并评估,得出以下的表格。基本和书中的结果一致。

Movielens 1M:

Movielens 1M Precision Recall Coverage Popularity
UserCF 19.84% 11.97% 28.16% 7.2023
ItemCF 19.00% 11.47% 16.73% 7.3911
UserCF-IIF 19.77% 11.93% 29.62% 7.1660
ItemCF-IUF 18.71% 11.29% 15.03% 7.4748
Random 0.54% 0.33% 100.00% 4.4075
Most Popular 10.59% 6.39% 2.76% 7.7462

Movielens 100k:

Movielens 100k Precision Recall Coverage Popularity
UserCF 19.69% 18.50% 22.20% 5.4928
ItemCF 17.89% 16.80% 13.23% 5.6202
UserCF-IIF 19.57% 18.38% 22.74% 5.4716
ItemCF-IUF 20.38% 12.30% 17.30% 7.3643
Random 0.82% 0.77% 99.64% 3.0332
Most Popular 10.54% 9.90% 4.07% 5.9578

总结

本文用精简的篇幅概括了协同过滤的思想,并穿插了两篇最重要的协同过滤论文。算法实现本身并不难,可以把书中的代码作为参考,实现一个系统的难点应该在于构思框架、优化算法、如何评估。我觉得这个项目本身的可参考意义还是很大的,希望对大家有所帮助。

本电影推荐系统的所有代码都已经更新到仓库:https://github.com/fuxuemingzhu/MovieLens-Recommender,欢迎Fork和Star。

走心推广

因为本博客部署在Digital Ocean 服务器上,每个月都要有5美元的成本,对于学生党来说也是不小开支。所以既然你能看到这篇博客的话,希望你能帮助我一下,鼓励我写出更好的文章。

Digital Ocean 服务器购买

如果你也需要购买服务器,强烈推荐Digital Ocean,推荐原因如下:

  • 基础套餐很便宜,每月最低5美元
  • 服务器资源很给力,基础套餐配置是512MB内存,20GB的SSD硬盘,带宽不限制
  • 可以搭建ShadowSocks,实现科学上网(特别实用!!)
  • 支持ipv6,轻松解决校园网流量不够用的问题

我使用的是san francisco节点,搭建好ss之后,看油管1080p视频轻松无压力!!

你如果使用我的这个网址注册Digital Ocean,你可以得到10美元的代金券,我也能赚到一点,就是这个链接:https://m.do.co/c/86d4e56f6c7a

另外,如果你也是学生,有校园网邮箱,即可申请GitHub的Student Pack,免费送你50美刀的Digital Ocean代金券!地址是:https://education.github.com/pack/offers

如果满分10分,你会给这篇文章打几分?
负雪明烛 微信

微信

负雪明烛 支付宝

支付宝