这里简单介绍一下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表示。
我没有用Mahout的taste,以前尝试过很小的数据集就吃了很多内存,而且看了源码发现嵌套太多,还不如自己写靠谱。我找了movielens的1000万数据做实验,用taste的SVDrecommender吃了6G内存还说不够。
实验的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,跑起来还不快,谁看看代码帮找找原因。
另外,这些结果在不同电脑上可能时间差别有些大。