Netflix Prize 矩阵分解(Matrix factorization)预测用户评分

Python实现 Netflix Prize 矩阵分解(Matrix factorization)预测用户评分

笔者使用Python实现用于 Netflix Prize的矩阵分解预测模型,
Github链接:https://github.com/SJTUzhou/NetflixPrizeMatrixFactorization
参考文献:https://datajobs.com/data-science-repo/Recommender-Systems-[Netflix].pdf
以下详细说明。

1. 背景

Netflix Prize 是奈飞公司于2006年开始举行的一场比赛,旨在提高对用户的影视剧评分的预测准确率。

Netflix 提供不同用户对不同电影的评分用于参赛者的模型训练,提交模型的评判标准基于对给定的用户和电影的评分预测的均方根误差(Root Mean Square Error)。
R M S E = ∑ i = 1 N ( y i ^ − y i ) 2 N RMSE = \sqrt{\frac{\sum_{i=1}^{N}(\hat{y_i}-y_i)^2}{N}} RMSE=Ni=1N(yi^yi)2
N N N为需要进行预测的数量, y i y_i yi为用户的实际评分( y i ∈ { 1 , 2 , 3 , 4 , 5 } y_i \in \{1,2,3,4,5\} yi{1,2,3,4,5}), y i ^ \hat{y_i} yi^为预测评分(虽然Netflix允许提交小数预测,比如3.4,但是在官方进行RMSE计算时,会对这一预测进行四舍五入取整)。

直接采用电影评分平均分作为预测的模型,在测试集上得到的 R M S E RMSE RMSE 约为1.0540
Netflix原来的预测模型Cinematch,在测试集上得到的 R M S E RMSE RMSE 约为0.9514;若想得到Netflix Prize的大奖,需要达到将0.9514这一准确率再提升10%的要求,即0.8567左右。在2009年,已有参赛者达到这一要求。

详细简介可以浏览维基百科:https://en.wikipedia.org/wiki/Netflix_Prize
最终官方排名网址:https://www.netflixprize.com/leaderboard.html

2. Netflix Prize数据集

Netflix Prize数据集下载地址:https://www.kaggle.com/netflix-inc/netflix-prize-data
数据集文件夹中有README,官方写得很详细,请仔细阅读。
数据集中共有100,480,570条评分,这是480,189名用户对17,770部电影的评分。

Netflix提供了 training data, probe data 和 qualifying data,其中training data用于训练模型,probe data实际为training data中的一部分,用于测试模型准确率,qualifying data用于对提交的算法进行准确率比较,因此只有大赛评审团知道其中的用户评分(rating),在训练时不需要qualifying data这个数据集。

数据集中的每一条数据都可以认为是一个四元组,
即(movieID, userID, 用户评分Rating, 评分时间Timestamp)。
值得注意的是movieID为 1 ~ 17,770的整数,userID为不连续的整数,且不从0开始。

笔者在实际训练中采用交叉验证(Cross Validation)的方式,对training data中的每一部电影的用户评分进行随机划分,分别用于测试和训练,因此没有用到probe data,得到的结果与官网测试的结果不具有可比性。
关于什么是交叉验证:https://blog.csdn.net/lhx878619717/article/details/49079785

3. 矩阵分解 Matrix Factorization

在预测第 i i i 个用户对第 j j j 部电影的评分过程中,矩阵分解的基本思路是把这个用户(user_i)看作一个n维的列向量( u i ∈ R n \mathbf {u_i} \in R^n uiRn),同理,把这部电影(movie_j)也看作是一个n维的列向量( m j ∈ R n \mathbf {m_j} \in R^n mjRn),预测评分由 y ^ i j = m j T u i \hat {y}_{ij} = \mathbf {m_j} ^T\mathbf {u_i} y^ij=mjTui 给出。 这里是矩阵相乘,所以 m j T u i = u i T m j \mathbf {m_{j}} ^T\mathbf {u_{i}} = \mathbf {u_{i}} ^T\mathbf {m_{j}} mjTui=uiTmj,相当于把这两个向量对应元素相乘然后求和得到预测评分。

关于用户向量 u i \mathbf {u_{i}} ui,电影向量 m j \mathbf {m_{j}} mj 具体代表什么,我们可以这样理解。比如,这两个向量的维度都是10, m j \mathbf {m_{j}} mj 的第一个元素可能代表着这部电影是否是动作电影,数值为正意味着是,为负意味着不是,大小代表动作的激烈程度; u i \mathbf {u_{i}} ui 的第一个元素可能代表着这个用户是否喜欢动作电影,数值为正意味着喜欢,为负意味着不喜欢,大小代表喜好程度。这样,一个喜欢动作电影的用户在给动作电影评分时偏向于给出正面评价,对应元素相乘为正;反之,不喜欢动作电影的用户对这部动作电影评分可能偏低,对应元素相乘为负。以此类推,向量维度为10,特征数量为10,相当于从10个方面刻画电影的对应程度和用户对着10个方面的偏好程度。当然,以上只是一种解释方法。

考虑到每部电影有自己的平均分,每个用户也有自己的打分偏好,比如张三给看过的电影打分偏高,李四给看过的电影打分偏低,这些都是偏差量(bias),因此我们在预测的时候可以采取以下策略:
y ^ i j = m j T u i + m j a v g + u i o f f s e t \hat {y}_{ij} = \mathbf {m}_j ^T\mathbf {u}_i+m_j^{avg}+u_i^{offset} y^ij=mjTui+mjavg+uioffset 其中 u i o f f s e t = u i a v g − y g l o b a l a v g u_i^{offset} = u_i^{avg}-y_{global}^{avg} uioffset=uiavgyglobalavg u i a v g u_i^{avg} uiavg 是用户 i i i 所打分的平均分, y g l o b a l a v g y_{global}^{avg} yglobalavg 是数据集中所有评分的总平均分。

因此,使用矩阵分解进行预测的模型,所需训练的实际是用户特征矩阵 U n × N u s e r \mathbf {U_{n \times N_{user}}} Un×Nuser和电影特征矩阵 M n × N m o v i e \mathbf {M_{n \times N_{movie}}} Mn×Nmovie,其中 n n n 是特征数量, N m o v i e N_{movie} Nmovie N u s e r N_{user} Nuser 是数据集中的电影总数和用户总数。

通过添加L2正则化系数 λ \lambda λ,我们可以更好的优化模型,最优矩阵 U n × N u s e r \mathbf {U_{n \times N_{user}}} Un×Nuser M n × N m o v i e \mathbf {M_{n \times N_{movie}}} Mn×Nmovie,可以通过将以下损失函数函数取最小值得到,即:
L = ∑ ( i , j ) ∈ K ( y i j − m j T u i − m j a v g − u i o f f s e t ) 2 + λ ( ∥ m j ∥ 2 + ∥ u i ∥ 2 + m j a v g 2 + u i o f f s e t 2 ) L = \sum_{(i,j) \in K} (y_{ij}-\mathbf {m}_j ^T\mathbf {u}_i-m_j^{avg}-u_i^{offset})^2 + \lambda (\| \mathbf {m}_j \| ^2 + \| \mathbf {u}_i \| ^2+{m_j^{avg}}^2+{u_i^{offset}}^2) L=(i,j)K(yijmjTuimjavguioffset)2+λ(mj2+ui2+mjavg2+uioffset2)

M n × N m o v i e , U n × N u s e r = a r g m i n ( L ) \mathbf {M_{n \times N_{movie}}}, \mathbf {U_{n \times N_{user}}} = argmin (L) Mn×Nmovie,Un×Nuser=argmin(L)

对于这两个需要训练的特征矩阵,可以采用交替最小二乘法(Alternating least squares)或随机梯度下降(Stochastic gradient descent)进行优化,以使模型获得更小的均方根误差(RMSE)。

这两种方法的数学原理这里不作赘述。
交替最小二乘法(Alternating least squares)的数学原理:https://flashgene.com/archives/55882.html
随机梯度下降(Stochastic gradient descent)的数学原理:https://www.zhihu.com/question/264189719

4.具体实现(Python)

笔者Github链接:https://github.com/SJTUzhou/NetflixPrizeMatrixFactorization

4.1 数据预处理

因为Netflix给出的数据是文本文件,数据格式如下所示:
Netflix Prize 矩阵分解(Matrix factorization)预测用户评分_第1张图片
1:” 指的是电影ID为1,接下来是不同用户的评分,每一行里分别是用户ID,评分和评分时间。

在这里我不考虑评分时间对评分带来的影响,我需要的是一个列数为3的矩阵作为数据集,第1列是电影ID,第2列是用户ID,第3列是评分;从而可以用numpy进行相关运算。实际上这个数据集,笔者按照电影ID顺序排列。

movieID为 1 ~ 17,770的整数,userID为不连续的整数,且不从0开始。考虑到编程中的索引从0开始,所以笔者的实际训练计算中 movie_index 为 0 ~ 17,769 的整数而 user_index 是将实际userID从小到大依次映射到 0 ~ 480188 的整数上,这样可以方便numpy运算。因此,在处理数据中保存一个user_ids.npy的numpy二进制文件。

处理完数据,我们进行数据集划分。笔者按照一个比例,比如0.1,来划分,即对一部电影的所有评分,随机90%作为训练集,剩余10%作为测试集。

预处理数据及训练集测试集划分的相关代码:prepare_data.py
如果采用交替最小二乘法(Alternating least squares)作为优化算法,需要额外获得按用户ID顺序重新排列的数据集,相关代码:ALS_extra_data.py
保存的文件名及一些常量:const.py

4.2 使用随机梯度下降(SGD)训练模型并评估

寻找最优超参数:特征维度 n n n 和 L2 正则化系数 λ \lambda λ
n ∈ { 5 , 10 , 50 } n \in \{ 5,10,50 \} n{5,10,50}
λ ∈ { 0 , 0.01 , 0.02 , 0.03 , 0.04 , 0.05 , 0.06 , 0.07 , 0.08 , 0.09 , 0.1 , 0.15 , 0.2 , 0.4 } \lambda \in \{0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.15,0.2,0.4 \} λ{0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.15,0.2,0.4}
相关代码:matrix_stoc_grad_desc.py

4.3 使用交替最小二乘法(ALS)训练模型并评估

寻找最优超参数:特征维度 n n n 和 L2 正则化系数 λ \lambda λ
n ∈ { 5 , 10 , 20 , 50 , 100 } n \in \{ 5,10,20,50,100 \} n{5,10,20,50,100}
λ ∈ { 0 , 0.01 , 0.02 , 0.03 , 0.04 , 0.05 , 0.06 , 0.07 , 0.08 , 0.09 , 0.1 , 0.15 , 0.2 , 0.4 } \lambda \in \{0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.15,0.2,0.4 \} λ{0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.15,0.2,0.4}
相关代码:ALS_matrix.py

4.4 绘制结果

相关代码:graphique.py

不同超参数对RMSE的影响

部分训练效果

5. 结语

  1. 使用随机梯度下降算法(SGD)训练时,没有对数据集整体进行随机打乱,可能导致效果偏差。
  2. 交替最小二乘法(ALS)在训练速度和收敛速度上优于随机梯度下降算法(SGD),主要是因为在这个问题环境下,交替最小二乘法(ALS)可以并行计算。

你可能感兴趣的:(Matrix,Factorization,Netflix,Prize)