基于评分数据的推荐算法实现:slopeone和矩阵分解

几年推荐算法研究得比较火热,得益于netflix的百万大奖。推荐算法有多种分法,有人喜欢分成基于内容和基于用户行为的,而主流的文献还是从算法分得多:即neighborhood-based和基于factorization的。 neighbor-based方法比较早,主流的user-base和item-base,其思想都是猜测用户会喜欢和他口味一致的东西。矩阵分解直接把预测问题转换成一个估计对评分矩阵的补全问题。这两种方法都有不少的综述文章了。除了user、item-base,基于图模型、slopeone应该也算neighborhood-based的方法。 矩阵分解的方法我最早是看到Yehuda Koren的论文里介绍的,一时没看懂,N久以后的后来看了项亮博士的论文才明白(博士论文综述信息量总是特别大~)。矩阵分解的方法比协同过滤的效果好(主要应该是RMSE)的主要原因我觉得在于它做了全局优化目标优化,而协同过滤的特点主要还是能捕捉小部分强相关的数据信息却忽视全局信息。Koren巧妙地把两者结合起来,使得推荐效果达到了更好。 我实现了koren那篇factors in the neighbor中的item neighborhood的方法,当然一路看下来从最基本的分解开始实现、加入用户偏好、看到效果慢慢才感受到关系分解的巧妙(建议阅读项亮博士的论文或者koren自己写的一篇矩阵分解的介绍性文章,代码重构已经放入项目,见博客的置顶帖
这里简单介绍一下koren的方法,从最早SVD的方法开始:评分依据是用户对电影类型的偏好 以及 每个电影类型成分
 r_ui = pu * qi ,
(其中r_ui表示对用户u的电影i评分,pu是一个向量,表示用户u潜在喜欢哪些类型的电影,qi是一个向量表示电影i在各种类型中占有多少比例,例如一部电影可以既有恐怖也有悬疑也有爱情…… pu 和qi通过数据集中的打分rui来估算,使所有的(rui - pu*qi)最小化,通过随机梯度下降来求,建议阅读项亮博士的论文)
RSVD在SVD的基础上增加了用户和项目的偏好,有些用户喜欢打高分,有些用户苛刻,有的电影好看,普遍被打高分……
 r_ui = avg + bu + bi + pu * qi 
(avg是全局平均分,求解同理)
Koren加入协同信息:
 r_ui = avg + bu + bi + sigma[wij * (ruj - buj)] for j belongs to u
(其中buj = avg + bu + bi, wij可以看作项i对项j的影响, 也就是对一个i打分,我们还要关心用户u中所有物品对i的影响)
wij是一个二元关系,要保存起来很占空间,于是可以进行分解,用xi * qi表示,那么预测打分公式就变成:
 r_ui = avg + bu + bi + qi * {sigma[(ruj - buj)* xi] for j belongs to u}
也就是pu被分解成后面一串,当然这里不可能介绍太细,大家还是应该去读一下原文。
 
说说实现。我代码用java写的,开始时候想把数据留在外存,计算的时候一行行地读进来,发现内存使用很多,想到Java的String实际释放比较难控制,干脆改成所有东西存成byte数组,直接用inputstream去读取。后来发现需要保存每个用户的打分信息反复迭代,索性把数据集全读进内存算了。那么要存上千万的rating就得省着点用。itemid和打分用一个int表示3个字节表示itemid,1个字节表示打分,打分中4比特表示个位,4比特表示小数点后一位;数据集按照userid排序,通过一个long表示一个用户打分数据的起始和终止下标:各4字节。对于movielens 1000W打分需要1000W * 4字节 = 40M,加上 7W 用户*8 = 560K。就算Netflix 有一亿打分、50W用户也只占400M而已。我看到还有更疯狂节省空间的做法是直接把所有信息的范围用k个bit表示。 矩阵分解方法里面调整学习速率是有讲究的,如果太小,在十几次迭代都收敛不了,如果太大,那收敛到一定程度还会发散。Koren论文里有提到netflix上的取值大概在多少合适,另外学习速率每次迭代还是应该减小10%。另外一个影响收敛的因素是初始随机值,不能太大。
 我还实现了slopeone,为的是比较一下RMSE效果,这个算法网上有比较详实的介绍,我对它的理解也是从网上。我没有用Mahout的taste,以前尝试过很小的数据集就吃了很多内存,而且看了源码发现嵌套太多,还不如自己写靠谱。我找了movielens的1000万数据做实验,用taste的SVDrecommender吃了6G内存还说不够。
 
代码在这里:http://download.csdn.net/detail/lgnlgn/4001712,代码水平有限,各位请勿狠拍。
实验的RMSE结果是:在movielens上,原始SVD在15次迭代f=100的时候,有0.8115,加入user和item偏好的RSVD,提高到0.8069;这两个算法迭代一次都只有5秒左右。Item neighborhood的RMSE达到0.7883,一次迭代10秒出头;加入隐式反馈达到0.7846,一次迭代近18秒。而slopeone只有0.8569,跑起来还不快,谁看看代码帮找找原因。 
 
后话: 电影打分数据属于比较优质的数据,netflix有1亿打分,估计只是一个很小的部分。而一般互联网积累的数据大多只能看做是二值数据,而且稀疏程度比电影数据高,用户和物品数量比netflix提供的估计大一个数量级,用户量甚至会大两个量级。在这种情况下,如何设计开发出一个高性能有效的推荐算法也是值得研究的。协同过滤中图模型貌似更合适这样的情况。
 
----------------------------------------------
后来又跑了一下netflix的数据, 随机选400W做测试集。SVD在15次迭代f=100时候 RMSE=0.8351, RSVD只提升了0.0002。每次迭代1分钟左右。而Item neighborhood的RMSE 我试了很多次都跑出NaN。只有初始化缩小100倍的、学习速率调低才跑出来; 没有隐式反馈f=50的时候RMSE达到0.8268。netflix数据集不保证每个用户看过多少电影,很多用户只看1、2部电影。
----------------------------------------------

你可能感兴趣的:(java,算法,UI,优化,user,BI)