基于梯度下降的矩阵分解
一,矩阵分解能够解决的推荐问题
假如你得到了用户对商品的打分数据,数据形式如下:
Item1 | I2 | I3 | I4 | |
---|---|---|---|---|
User1 | 5 | 3 | - | 1 |
U2 | 4 | - | - | 1 |
U3 | 1 | 1 | - | 5 |
U4 | 1 | - | - | 4 |
U5 | - | 1 | 5 | 4 |
其中,U1⋯U5U1⋯U5表示的是5个不同的用户,I1,I2⋯I4表示的是4个不同的商品,这样便构成了户-商品矩阵,在该矩阵中,有用户对每一件商品的打分,其中“-”表示的是用户未对该商品进行打分。我们可以利用矩阵分解来计算未评分的商品,然后对他们进行排序,把评分靠前的推荐给用户。是不是很神奇?下面就让我们开始学习吧!
二,矩阵分解
1,矩阵分解的优势
矩阵的分解,其实有三种方法。1.特征值分解(Eigendecomposition)2.奇异值分解SVD(Singular Value Decomposition)3.Funk - SVD (Simon Funk SVD),对症下药,适合自己的才是最好的,这里应用的是第三种,推荐系统中起先应用最早的是协同过滤算法,后来者居上的原因当然是矩阵分解能解决对稀疏数据的处理问题,比如基于用户的协同过滤就是尽可能的找到和你相似的用户,将他们喜欢的电影推荐给你。这里面的问题就是,如果一个电影虽然很符合你的兴趣,但是你的朋友却很少有评价或观看过,那么协同过滤就很难将这个电影推荐给你,因为没有评价就不确定这个电影与你看过的电影是否有很近的相似度。那么矩阵分解的出现,很好的解决了协同过滤存在的问题。
2,矩阵分解的思想
矩阵分解(Funk-SVD)是将一个矩阵,比如为m × \times ×n的R矩阵,就可以分解为 P m k P_{mk} Pmk × \times × Q k n Q_{kn} Qkn。这里的k的大小决定了 R ^ \hat{R} R^(预测矩阵)的准确度,其取值要经过多次的实验来确定。
3,基于矩阵分解的推荐算法步骤
注意:下面的带^的表示预测值,小写字母代表具体的值。
3.1一般形式
对于上述的用户-商品矩阵(评分矩阵),记为 R m n R_{mn} Rmn,可以将其分解成两个或者多个矩阵的乘积,假设分解成两个矩阵 P m k P_{mk} Pmk × \times × Q k n Q_{kn} Qkn,我们要使得矩阵 P m k P_{mk} Pmk和 Q k n Q_{kn} Qkn的乘积能够近似的还原原始的矩阵 R m n R_{mn} Rmn: R m n ≈ P m k × Q k n = R ^ m n R_{mn}\approx P_{mk}\times Q_{kn}=\hat{R}_{mn} Rmn≈Pmk×Qkn=R^mn
3.2 套路求Loss函数
3.21 构造损失函数
损失函数往往是拿预测值与真实值进行做差来得到的,这也是损失的精髓,加上平方的作用则是防止负值与正值得抵消。这里不妨构造
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_{ij}^{2}=(r_{ij}-\hat r_{ij})^{2}=(r_{ij}-\sum_{k=1}^{k}p_{ik}q_{kj})^{2}
eij2=(rij−r^ij)2=(rij−k=1∑kpikqkj)2
然后通过给定的阈值来限定loss函数的结束或迭代次数。
3.22 loss函数的求解
对于上面loss函数的求解,我们会采用梯度下降算法.
(1)求损失函数的梯度:
δ
e
i
j
2
δ
p
i
k
=
−
2
e
i
j
q
k
j
\frac{\delta e_{ij}^2}{\delta p_{ik}}=-2e_{ij}q_{kj}
δpikδeij2=−2eijqkj
δ
e
i
j
2
δ
q
k
j
=
−
2
e
i
j
p
i
k
\frac{\delta e_{ij}^2}{\delta q_{kj}}=-2e_{ij}p_{ik}
δqkjδeij2=−2eijpik
或许有人会纳闷
∑
\sum
∑符号为什么会随着求偏导的过程而消失,下面给出的手工推导会给你答案:
(2)根据负梯度的方向来更新参数变量:
p
i
k
′
=
p
i
k
−
α
δ
e
i
j
2
δ
p
i
k
=
p
i
k
+
2
α
e
i
j
q
k
j
p_{ik'}=p_{ik}-\alpha \frac{\delta e_{ij}^{2}}{\delta p_{ik}}=p_{ik}+2\alpha e_{ij}q_{kj}
pik′=pik−αδpikδeij2=pik+2αeijqkj
q
k
j
′
=
q
k
j
−
α
δ
e
i
j
2
δ
q
k
j
=
q
k
j
+
2
α
e
i
j
p
i
k
q_{kj'}=q_{kj}-\alpha \frac{\delta e_{ij}^{2}}{\delta q_{kj}}=q_{kj}+2\alpha e_{ij}p_{ik}
qkj′=qkj−αδqkjδeij2=qkj+2αeijpik
(3)不停的迭代计算直到算法最终收敛(即预测值与真实值小于设定的阈值)
(4)很多时候会涉及正则化项的步骤,对loss函数进行正则化是为了解决函数过拟合(就是得到的结果与训练集的拟合程度近乎完美而对测试集数据不适用)的问题。有关正则化的问题这里暂不进行详细解说。
4,实例说明
4.1 例子
就拿上面的例子吧。
4.2 代码实现
from math import pow
import numpy
#定义求修正的p跟q矩阵函数
def matrix_factorzation(R,P,Q,K,N=5000,step=0.0002,beta=0.02):
#N是迭代次数,step为步长,beat为正则化参数
Q=Q.T# .T操作表示矩阵的转置
for n in range(N):
for i in range(len(R)):
for j in range(len(R[i])):
if R[i][j]>0:#对原始矩阵中有评分的项构造损失函数
eij=R[i][j]-numpy.dot(P[i,:],Q[:,j])#.dot(P,Q) 表示矩阵乘法
for k in range(K):
P[i][k]=P[i][k]+step*(2*eij*Q[k][j]-beta*P[i][k])
Q[k][j]=Q[k][j]+step*(2*eij*P[i][k]-beta*Q[k][j])
e=0
for i in range(len(R)):
for j in range(len(R[i])):
if R[i][j]>0:
e=e+pow(R[i][j]-numpy.dot(P[i,:],Q[:,j]),2)
for k in range(K):
e=e+(beta/2)*(pow(P[i][k],2)+pow(Q[k][j],2))
if e<0.001:#阈值
break
return P,Q.T
R=[[5,3,0,1],
[4,0,0,1],
[1,1,0,5],
[1,0,0,4],
[0,1,5,4]
]
R=numpy.array(R)#生成矩阵R
K=10
P=numpy.random.rand(len(R),K)#随机生成P,Q矩阵
Q=numpy.random.rand(len(R[0]),K)
P,Q=matrix_factorzation(R,P,Q,K)
print("原始的评分矩阵R为:\n",R)
R_MF=numpy.dot(P,Q.T)#再做一次修正后P,Q矩阵乘法
print("经过MF算法评分后的矩阵是:\n",R_MF)
结果: