潜在因子(Latent Factor)推荐算法—音乐推荐

算法思想

这个算法的思想是:用元素去连接用户和音乐。不同的用户(user)对偏好的元素(factor)不同,不同的音乐(item)含有不同的元素。比如用户A喜欢带有古风、小清新、许嵩等元素的歌,那么如果一首歌带有这些元素,我们就这首歌推荐给用户。所以我们期望得到这样两个矩阵:用户-潜在因子矩阵,音乐潜在因子矩阵。

用户潜在因子矩阵P

古风 小清新 流行 伤感 许嵩
用户A 0.6 0.8 0.1 0.1 0.7
用户B 0.1 0 0.9 0.1 0.2
用户C 0.5 0.7 0.9 0.9 0

表示用户对不同元素的偏好程度,1表示喜欢,0表示不喜欢。

音乐潜在因子矩阵Q

古风 小清新 流行 伤感 许嵩
音乐A 0.9 0.1 0.2 0.4 0
音乐B 0.5 0.6 0.1 0.9 1
音乐C 0.1 0.2 0.5 0.1 0
音乐D 0 0.6 0.1 0.2 0

表示音乐含有各种因素的成分,比如音乐A含有古风的成分为0.9,含有小清新的成分为0.1,含有流行的成分为0.2,含有伤感的成分为0.4,含有许嵩的成分为0等。

利用P、Q矩阵求解用户对音乐的喜欢程度

我们可以利用用户潜在因子矩阵P和音乐潜在因子矩阵求解用户对音乐的喜欢程度,ru,i=例如用户A对音乐A的喜欢程度=用户A对古风的偏好程度 音乐A含有古风的成分+用户A对小清新的偏好程度 音乐A含有小清新的成分+用户A对流行的偏好程度 * 音乐A含有流行的成分+…….

即:0.6 0.9+0.8 0.1+0.1 0.2+0.1 0.4+0.7 * 0=0.69

古风 小清新 流行 伤感 许嵩
用户A 0.6 0.3 0.1 0.4 0.4
古风 小清新 流行 伤感 许嵩
音乐A 0.9 0.1 0.2 0.4 0

每个用户对每首歌都这样计算,就可以得到所有用户对所有歌曲的评分。预测评分矩阵r=PQT

音乐A 音乐B 音乐C 音乐D
用户A 0.68 1.58 0.28 0.51
用户B 0.31 0.43 0.47 0.11
用户C 1.06 1.57 0.73 0.69

由预测评分矩阵可知用户A对音乐B的评分最高,用户B对音乐C的评分最高,用户C对音乐B的评分最高。所以给用户推荐音乐B,给用户B推荐音乐C,给用户C推荐音乐B。

How to get matrix P and Q

显然音乐含有的因素是多种多样的,我们无法人为的给出精确的分类,也无法让用户给出对不同因素的偏好程度,我们方便得到的仅仅是用户的行为。这里沿用@邰原朗的量化标准:单曲循环=5, 分享=4, 收藏=3, 主动播放=2 , 听完=1, 跳过=-2 , 拉黑=-5。这样根据用户的行为就可以得到实际的评分矩阵R,如下表所示。

Markdown

用户的实际评分矩阵R实际上是一个稀疏矩阵,因为用户听过的音乐仅仅是全部音乐中的一小部分。那么我们如何利用实际评分矩阵R求得潜在因子呢,这就要用到矩阵分解。我们期望求得矩阵P和Q使得
$$
R \approx PQ^T
$$

Markdown

我们期望P和QT的乘积约等于实际评分矩阵R,也就是求
$$
min \sum(R_{u,i}-r_{u,i})^2
$$


$$
min \sum(R_{u,i}-q_i^Tp_u)^2
$$
这就矩阵分解问题转换成了求最优化的问题。这样求得的最小值,会出现数据过拟合的问题,导致结果虽然能对训练集完美的预测,但是对测试集预测的结果不好。为了防止数据过拟合的问题,采用L2正则化的方法,即在损失函数的后面加上每个参数的平方。这样误差不仅仅取决于拟合数据的好坏,而且取决于参数值的大小。并加入惩罚因子$\lambda$,控制正则化的强度。
$$
min \sum(R_{u,i}-q_i^Tp_u)^2+ \lambda q_i^2+\lambda p_u^2
$$
为了降低算法的复杂度,采用随机梯度下降法求最小值。所谓随机梯度下降算法,优化的不是全部训练数据上的损失函数,而是在每轮迭代中随机优化某一条训练数据上的损失函数,这样每轮参数的更新速度大大加快。这里我们在每次迭代中对每个$(R_{u,i}-q_i^Tp_u)^2+ \lambda q_i^2+\lambda p_u^2$求最小值。

梯度是一个向量,每个元素为函数对一元变量的偏导数,它既有大小也有方向。梯度方向是函数值增长最快的方向,所以沿着梯度相反的方向可以较快找到函数的最小值。

损失函数$f=(R_{u,i}-q_i^Tp_u)^2+ \lambda q_i^2+\lambda p_u^2$分别对qi和pu求偏导如下:
$$
\begin{cases}
\frac {\partial f}{\partial q_i}=-2p_u(R_{u,i}-q_i^Tp_u)+2\lambda q_i
\\
\frac {\partial f}{\partial p_u}=-2q_i(R_{u,i}-q_i^Tp_u)+2\lambda p_u
\end{cases}
$$

对于每个评分数据$R_{u,i}$,预测评分为$q_i^Tp_u$,因此偏差定义为$e_{u,i}=R_{u,i}-q_i^Tp_u$。

为了最小化损失函数,令学习步长为$\frac {\gamma}{2}$,朝着梯度相反的方向改变$p_u$和$q_i$,所以:
$$
\begin{cases}
q_i=q_i+\gamma(e_{u,i}p_u-\lambda q_i)
\\
p_u=p_u+\gamma(e_{u,i}q_i-\lambda p_u)
\end{cases}
$$

伪代码

输入:实际评分矩阵R,学习率y,惩罚因子x,潜在因子大小f,最大循环次数max_iter。

输出:预测评分矩阵r。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while(iter<max_iter){
//遍历R(u,i)
for(u=0;u<u_num;u++)
for(i=0;i<i_num;i++){
r(u,i)=0;
for(f=0;f<f_num;f++){
r(u,i)+=q(i,f)*p(u,f);
}
e(u,i)=R(u,i)-r(u,i);
loss+=e(u,i)*e(u,i);
for(f=0;f<f_num;f++){
q(i,f)+=y(e(u,i)*p(u,f)-xq(i,f));
p(u,f)+=y(e(u,i)*q(i,f)-xp(u,f));
loss+=xp(u,f)*p(u,f)+xq(i,f)q(i,f);
}
}
}