算法思想
这个算法的思想是:用元素去连接用户和音乐。不同的用户(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,如下表所示。

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

我们期望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 | while(iter<max_iter){ |