描述
我们都熟知在一些软件中常常有评分系统,但并不是所有的用户user
都会对项目item
进行评分,因此评分系统所收集到的用户评分信息必然是不完整的矩阵。那如何跟据这个不完整矩阵中已有的评分来预测未知评分呢。使用矩阵分解的思想很好地解决了这一问题。
假如我们现在有一个用户-项目的评分矩阵R(n,m)是n
行m
列的矩阵,n
表示user
个数,m
行表示item
个数
i1 | i2 | i3 | i4 | |
---|---|---|---|---|
u1 | 5 | 3 | - | 1 |
u2 | 4 | - | - | 1 |
u3 | 1 | 1 | - | 5 |
u4 | 1 | - | - | 4 |
u5 | - | 1 | 5 | 4 |
其中,u1⋯u5表示的是5个不同的用户,I1⋯i4表示的是4个不同的商品,这样便构成了用户-商品矩阵,在该矩阵中,有用户对每一件商品的打分,其中“-”表示的是用户未对该商品进行打分。
在推荐系统中有一类问题是对未打分的商品进行评分的预测。
基于矩阵分解的推荐算法
矩阵分解的一般形式
矩阵分解是指将一个矩阵分解成两个或者多个矩阵的乘积。对于上述的用户-商品矩阵(评分矩阵),记为 R m × n R_{m\times n} Rm×n 。可以将其分解成两个或者多个矩阵的乘积,假设分解成两个矩阵 P m × k P_{m\times k} Pm×k 和 Q k × n Q_{k\times n} Qk×n ,我们要使得矩阵 P m × k P_{m\times k} Pm×k 和 Q k × n Q_{k\times n} Qk×n的乘积能够还原原始的矩阵 R m × n R_{m\times n} Rm×n:
R m × n ≈ P m × k × Q k × n = R ^ m × n R_{m\times n}\approx P_{m\times k}\times Q_{k\times n}=\hat{R}_{m\times n} Rm×n≈Pm×k×Qk×n=R^m×n
其中,矩阵 P m × k P_{m\times k} Pm×k 表示的是m个用户与k个主题之间的关系,而矩阵 Q k × n Q_{k\times n} Qk×n表示的是 k k k个主题与 n n n个商品之间的关系。
利用矩阵分解进行预测
在上述的矩阵分解的过程中,将原始的评分矩阵 R m × n R_{m\times n} Rm×n分解成两个矩阵 P m × k P_{m\times k} Pm×k 和 Q k × n Q_{k\times n} Qk×n的乘积:
R m × n ≈ P m × k × Q k × n = R ^ m × n R_{m\times n}\approx P_{m\times k}\times Q_{k\times n}=\hat{R}_{m\times n} Rm×n≈Pm×k×Qk×n=R^m×n
那么接下来的问题是如何求解矩阵 P m × k P_{m\times k} Pm×k 和 Q k × n Q_{k\times n} Qk×n的每一个元素,可以将这个问题转化成机器学习中的回归问题进行求解。
损失函数
可以使用原始的评分矩阵 R m × n R_{m\times n} Rm×n与重新构建的评分矩阵 R ^ m × n \hat{R}_{m\times n} R^m×n之间的误差的平方作为损失函数,即:
e i , j 2 = ( r i , j − r ^ i , j ) 2 = ( r i , j − ∑ k = 1 K p i , k q k , j ) 2 e_{i,j}^2=\left ( r_{i,j}-\hat{r}_{i,j} \right )^2=\left (r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )^2 ei,j2=(ri,j−r^i,j)2=(ri,j−∑k=1Kpi,kqk,j)2
最终,需要求解所有的非“-”项的损失之和的最小值:
m i n l o s s = ∑ r i , j ≠ − e i , j 2 min\; loss= \sum_{r_{i,j}\neq -}e_{i,j}^2 minloss=ri,j=−∑ei,j2
损失函数的求解
对于上述的平方损失函数,可以通过梯度下降法求解,梯度下降法的核心步骤是
- 求解损失函数的负梯度:
∂ ∂ p i , k e i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) q k , j = − 2 e i , j q k , j \frac{\partial }{\partial p_{i,k}}e_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )q_{k,j}=-2e_{i,j}q_{k,j} ∂pi,k∂ei,j2=−2(ri,j−∑k=1Kpi,kqk,j)qk,j=−2ei,jqk,j
∂ ∂ q k , j e i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) p i , k = − 2 e i , j p i , k \frac{\partial }{\partial q_{k,j}}e_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )p_{i,k}=-2e_{i,j}p_{i,k} ∂qk,j∂ei,j2=−2(ri,j−∑k=1Kpi,kqk,j)pi,k=−2ei,jpi,k
- 根据负梯度的方向更新变量:
p i , k ′ = p i , k − α ∂ ∂ p i , k e i , j 2 = p i , k + 2 α e i , j q k , j {p_{i,k}}'=p_{i,k}-\alpha \frac{\partial }{\partial p_{i,k}}e_{i,j}^2=p_{i,k}+2\alpha e_{i,j}q_{k,j} pi,k′=pi,k−α∂pi,k∂ei,j2=pi,k+2αei,jqk,j
q k , j ′ = q k , j − α ∂ ∂ q k , j e i , j 2 = q k , j + 2 α e i , j p i , k {q_{k,j}}'=q_{k,j}-\alpha \frac{\partial }{\partial q_{k,j}}e_{i,j}^2=q_{k,j}+2\alpha e_{i,j}p_{i,k} qk,j′=qk,j−α∂qk,j∂ei,j2=qk,j+2αei,jpi,k
通过迭代,直到算法最终收敛。
加入正则项的损失函数即求解方法
通常在求解的过程中,为了能够有较好的泛化能力,会在损失函数中加入正则项,以对参数进行约束,加入 L 2 L_2 L2正则的损失函数为:
E i , j 2 = ( r i , j − ∑ k = 1 K p i , k q k , j ) 2 + β 2 ∑ k = 1 K ( p i , k 2 + q k , j 2 ) E_{i,j}^2=\left (r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )^2+\frac{\beta }{2}\sum_{k=1}^{K}\left ( p_{i,k}^2+q_{k,j}^2 \right ) Ei,j2=(ri,j−∑k=1Kpi,kqk,j)2+2β∑k=1K(pi,k2+qk,j2)
利用梯度下降法的求解过程为:
- 求解损失函数的负梯度:
∂ ∂ p i , k E i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) q k , j + β p i , k = − 2 e i , j q k , j + β p i , k \frac{\partial }{\partial p_{i,k}}E_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )q_{k,j}+\beta p_{i,k}=-2e_{i,j}q_{k,j}+\beta p_{i,k} ∂pi,k∂Ei,j2=−2(ri,j−∑k=1Kpi,kqk,j)qk,j+βpi,k=−2ei,jqk,j+βpi,k
∂ ∂ q k , j E i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) p i , k + β q k , j = − 2 e i , j p i , k + β q k , j \frac{\partial }{\partial q_{k,j}}E_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )p_{i,k}+\beta q_{k,j}=-2e_{i,j}p_{i,k}+\beta q_{k,j} ∂qk,j∂Ei,j2=−2(ri,j−∑k=1Kpi,kqk,j)pi,k+βqk,j=−2ei,jpi,k+βqk,j
- 根据负梯度的方向更新变量:
p i , k ′ = p i , k − α ( ∂ ∂ p i , k e i , j 2 + β p i , k ) = p i , k + α ( 2 e i , j q k , j − β p i , k ) {p_{i,k}}'=p_{i,k}-\alpha \left ( \frac{\partial }{\partial p_{i,k}}e_{i,j}^2+\beta p_{i,k} \right )=p_{i,k}+\alpha \left ( 2e_{i,j}q_{k,j}-\beta p_{i,k} \right ) pi,k′=pi,k−α(∂pi,k∂ei,j2+βpi,k)=pi,k+α(2ei,jqk,j−βpi,k)
q k , j ′ = q k , j − α ( ∂ ∂ q k , j e i , j 2 + β q k , j ) = q k , j + α ( 2 e i , j p i , k − β q k , j ) {q_{k,j}}'=q_{k,j}-\alpha \left ( \frac{\partial }{\partial q_{k,j}}e_{i,j}^2+\beta q_{k,j} \right )=q_{k,j}+\alpha \left ( 2e_{i,j}p_{i,k}-\beta q_{k,j} \right ) qk,j′=qk,j−α(∂qk,j∂ei,j2+βqk,j)=qk,j+α(2ei,jpi,k−βqk,j)
通过迭代,直到算法最终收敛。
预测
利用上述的过程,我们可以得到矩阵 P m × k P_{m\times k} Pm×k 和 Q k × n Q_{k\times n} Qk×n,这样便可以为用户 i i i对商品 j j j进行打分:
∑ k = 1 K p i , k q k , j \sum_{k=1}^{K}p_{i,k}q_{k,j} k=1∑Kpi,kqk,j
代码实现
import numpy as np
from math import pow
import matplotlib.pyplot as plt
%matplotlib inline
def mf(R,P,Q,K):
alpha = 0.0002
beta = 0.02
times = 5000
eplison = 0.001
result = []
#将矩阵Q进行转置
Q = Q.T
#将生成的矩阵相乘
for time in range(times):
#求R尖的值
for i in range(len(R)):
for j in range(len(R[i])):
eij = R[i][j] - np.dot(P[i,:],Q[:,j])
for k in range(K):
P[i][k] = P[i][k] + 2*alpha*eij*Q[k][j]
Q[k][j] = Q[k][j] + 2*alpha*eij*P[i][k]
eR = np.dot(P,Q)
#求eij的平方
eij_2 = 0
for i in range(len(R)):
for j in range(len(R[i])):
for k in range(K):
eij_2 = eij_2 + pow((R[i][j] - np.dot(P[i,:],Q[:,j])),2) + 0.5*beta*(pow(P[i][k],2))+pow(Q[k][j],2)
result.append(eij_2)
if eij_2 < eplison:
break
return P,Q.T,result
def main():
R = [
[5,3,0,1],
[4,0,0,1],
[1,1,0,5],
[1,0,0,4],
[0,1,5,4]
]
#将R列表转化为矩阵
R = np.array(R)
#获取该矩阵的大小
N = len(R)
M = len(R[0])
print(N)
print(M)
'''
如何预测缺失的评分呢?对于缺失的评分,可以转化为基于机器学习的回归问题,也就是连续值的预测,
对于矩阵分解有如下式子,R是类似图1的评分矩阵,假设N*M维(N表示行数,M表示列数),可以分解为P跟Q矩阵,
其中P矩阵维度N*K,P矩阵维度K*M。
对于P,Q矩阵的解释,直观上,P矩阵是N个用户对K个主题的关系,Q矩阵是K个主题跟M个物品的关系,至于K个
主题具体是什么,在算法里面K是一个参数,需要调节的,通常10~100之间。
'''
K = 2
P = np.random.rand(N,K)
Q = np.random.rand(M,K)
#调用矩阵分解的函数
mf_P,mf_Q,result = mf(R,P,Q,K)
print("原始矩阵:",R)
mf_R = np.dot(mf_P,mf_Q.T)
print("经过MF后:",mf_R)
#做出图像
for t in range(5000):
plt.scatter(t,result[t])
plt.show()
main()