场景
当然是推荐系统了。
假设有四部电影,有五个人看了其中的几部,对其打了分数(实际情况是一部电影有四个侧重方向,也就是因子): 我们的目标就是预测出“-”位置对应的分值,这样就可以通过用户“可能”的喜欢程度,将分值最高的推荐给用户。
通过矩阵分解,可以把 R 分解成两个矩阵的乘积:
R
(
n
,
m
)
=
P
(
n
,
k
)
∗
Q
(
k
,
m
)
R(n,m)=P(n,k)*Q(k,m)
R(n,m)=P(n,k)∗Q(k,m) 。也就是把高维的矩阵分解成两个低维的矩阵。
矩阵
P
(
n
,
k
)
P(n,k)
P(n,k)表示n个用户和k个特征之间的关系矩阵,这k个特征是一个中间变量,矩阵
Q
(
k
,
m
)
Q(k,m)
Q(k,m)的转置是矩阵
Q
(
m
,
k
)
Q(m,k)
Q(m,k),矩阵
Q
(
m
,
K
)
Q(m,K)
Q(m,K)表示m个对象和k个特征之间的关系矩阵,其中k需要手动调节。当然,这只是近似,通过不断调整P与Q的值,让他们的乘积
R
^
\hat R
R^ 越来越接近
R
R
R ,这就是梯度下降进行矩阵分解的过程。
相关函数
1. 目标函数
假设用户的真实评分与预测之间的差值服从高斯分布,令
R
^
=
∑
k
=
0
K
p
i
k
q
k
j
\hat R=\sum_{k=0}^K p_{ik}q_{kj}
R^=∑k=0Kpikqkj
2. 损失函数——Loss Function
损失函数(loss function)是用来估量模型的预测值f(x)与真实值S的不一致程度。因为e对
p
i
k
p_{ik}
pik与
q
k
j
q_{kj}
qkj都是凸函数,我们可以用平方损失函数:
首先令
R
^
=
∑
k
=
0
K
p
i
k
q
k
j
\hat R=\sum_{k=0}^K p_{ik}q_{kj}
R^=∑k=0Kpikqkj
设
e
^
=
(
R
i
j
−
R
^
i
j
)
2
=
(
r
i
j
−
∑
k
=
1
K
p
i
k
q
k
j
)
2
\hat e= (R_{ij}-\hat R_{ij})^2=(r_{ij}-\sum_{k=1}^Kp_{ik}q_{kj})^2
e^=(Rij−R^ij)2=(rij−∑k=1Kpikqkj)2
在
R
i
j
R_{ij}
Rij已知的情况下,
e
^
\hat e
e^即为我们构建的损失函数,最后需要求出的为所有非“-”项的loss和的最小值,即
m
i
n
l
o
s
s
=
∑
R
i
j
!
=
‘
−
’
n
e
i
j
2
min\ loss=\sum_{R_{ij}!=‘-’}^n e_{ij}^2
min loss=∑Rij!=‘−’neij2
梯度下降求解
现在我们有了损失函数,就可以求出各个变量的负梯度(求偏导):
接下来根据梯度方向,更新变量:
这里
α
\alpha
α为步长,根据数据集的大小拟定。
上面就是用户因子和项目因子的更新公式,迭代更新公式即可找到可接受的局部最优解。其实负梯度的负方向,当函数是凸函数时是函数值减小的方向走;当函数是凹函数时是往函数值增大的方向移动。而矩阵分解的目标函数L是凸函数,因此,通过梯度下降法我们能够得到目标函数L的极小值(理想情况是最小值)。
PS: 矩阵分解还有许多其他的方法,如特征值分解与奇异值分解,在此不多赘述。
过拟合
有时Loss函数会得到0的结果,表面上效果很好,但是在用户数据中表现并不好,模型需要顾忌每一个点,最终形成的拟合函数波动很大。在某些很小的区间里,函数值的变化很剧烈。这就意味着函数在某些小区间里的导数值(绝对值)非常大,当用户-项目评分矩阵R非常稀疏时,就会出现过拟合 (overfitting) 的问题,过拟合问题的解决方法就是正则化 (regularization)。
引入正则化项后目标函数变为:
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 i j 2 ) e_{ij}^2 =(R_{ij}-\sum_{k=1}^Kp_{ik}q_{kj})^2+ \frac\beta{2}\sum_{k=1}^K(p_{ik}^2+q_{ij}^2) eij2=(Rij−∑k=1Kpikqkj)2+2β∑k=1K(pik2+qij2)
之后再对其进行梯度下降,最后得到:
关于正则化防止过拟合:
代码
import numpy as np
from math import pow
import matplotlib.pyplot as plt
def matrix_factorization(R,P,Q,K,steps=5000,alpha=0.0002,Lambda=0.02):
result=[]
for step in range(steps):
for i in range(len(R)):
for j in range(len(R[i])):
if R[i][j]>0:
eij=R[i][j]-np.dot(P[i,:],Q[:,j]) #乘法运算
for k in range(K):
P[i][k]=P[i][k]+alpha*(2*eij*Q[k][j]-Lambda*P[i][k])
Q[k][j]=Q[k][j]+alpha*(2*eij*P[i][k]-Lambda*Q[k][j])
eR=np.dot(P,Q)
e=0 #e即为损失函数
for i in range(len(R)):
for j in range(len(R[i])):
if R[i][j]>0:
e=e+pow(R[i][j]-eR[i][j],2) #只对已评分的元素求损失函数
result.append(e)
if e<0.001: #若损失函数小于阈值,则跳出for循环,否则继续进行上述过程
break
return P,Q,result
if __name__ == '__main__':
R=[
[5,3,0,1,0],
[4,0,0,1,2],
[1,1,0,5,0],
[1,0,0,4,5],
[0,1,5,4,3],
[3,0,2,1,0]
]
R = np.array(R)
M = len(R)
N = len(R[0])
K = 3
P = np.random.rand(M, K) # 随机生成一个 M行 K列的矩阵
Q = np.random.rand(K, N) # 随机生成一个 K行 N列的矩阵
nP, nQ, result = matrix_factorization(R, P, Q, K)
print("原始的评分矩阵R为:\n", R)
R_MF = np.dot(nP, nQ)
print("经过MF算法填充0处评分值后的评分矩阵R_MF为:\n", R_MF)
n = len(result)
x = range(n)
plt.plot(x, result, color='r', linewidth=3)
plt.title("Convergence curve")
plt.xlabel("generation")
plt.ylabel("loss")
plt.show()