矩阵分解
矩阵分解的概念
矩阵分解是指将一个矩阵分解成两个或者多个矩阵的乘积。譬如,一个N行M列的矩阵可以被分解为一个N行K列的矩阵和一个K行M列的矩阵的乘积(注意乘积顺序不能颠倒)。K可以理解为一个参数,这个参数会对这两个矩阵乘积产生的矩阵大小产生影响,但是不会影响乘积得到的矩阵的行数和列数。可以利用矩阵分解来解决很多实际生活中的问题。
例:
看图中的这个例子:
矩阵中,描述了5个用户(U1,U2,U3,U4 ,U5)对4个物品(D1,D2,D3,D4)的评分(1-5分),- 表示没有评分,我们的目的是把没有评分的给预测出来,然后按预测的分数高低,给用户进行物品推荐。
将图中的数据看作是一个维度为N×M的评分矩阵R,则R可以看做两个矩阵P(N×K)和Q(K×M)的乘积。对于P,Q矩阵的解释,直观上,P矩阵是N个用户对K个主题的关系,Q矩阵是K个主题跟M个物品的关系,或者说,N个用户通过K个主题对M个物品进行评分,那么我们在模拟时,也要通过这K个主题进行模拟。至于K个主题具体是什么,在算法里面K是一个参数,需要调节的,通常10~100之间。
具体步骤:
矩阵的分解(R是真实的评分矩阵,R^是我们预测得到的矩阵。R与R^越接近,就证明我们取得两个矩阵P和Q的预测模型越好。N和M是确定的参数值不会改变,因此K的取值对整个模型的预测结果起着至关重要的作用)
另外在这里补充说明一下矩阵相乘的规则:R^矩阵的第i行第j列的元素 = P 矩阵第i行的元素×Q矩阵第j列的元素。注意P和Q不能颠倒。
损失函数的平方等于真实值与预测值之间差值的平方,可以直接把预测值替换成P和Q的乘积。所以损失函数的值越小,真实值与预测值之间的差距越小,也就意味着我们拟合的结果越接近真实值,也就是我们对矩阵分解的结果越好。所以接下来就是解决问题的核心:基于梯度下降的优化算法。利用梯度下降的方法,一步步逼近损失函数的最小值
但是在机器学习中,我们通常在损失函数的后面加上一个正则项,公式如下:
这样一来,我们也需要对算法做出一点改动
代码实现
import numpy as np
import matplotlib.pyplot as plt
def matrix(R, P, Q, K, alpha, beta):
result=[]
steps = 1
while 1 :
#使用梯度下降的一步步的更新P,Q矩阵直至得到最终收敛值
steps = steps + 1
eR = np.dot(P,Q)# dot(P,Q) 表示矩阵内积,即Pik和Qkj,
e=0
for i in range(len(R)):#len(R)代表的是R的行数
for j in range(len(R[i])):#这两行就是遍历R中的每一个元素
if R[i][j]>0:
#k由1到k的和,eij为真实值和预测值的之间的误差,
eij=R[i][j]-.dot(P[i,:],Q[:,j])
e=e+pow(R[i][j] - np.dot(P[i,:],Q[:,j]),2) #求误差函数值,我们在下面更新p和q矩阵的时候我们使用的是化简得到的最简式,较为简便,但下面我们仍久求误差函数值,这里e求的是每次迭代的误差函数值,用于绘制误差函数变化图
for k in range(K):#在上面的误差函数中加入正则化项防止过拟合
e=e+(beta/2)*(pow(P[i][k],2)+pow(Q[k][j],2))
for k in range(K):
#在更新p,q时我们使用化简得到了最简公式
P[i][k]=P[i][k]+alpha*(2*eij*Q[k][j]-beta*P[i][k])
Q[k][j]=Q[k][j]+alpha*(2*eij*P[i][k]-beta*Q[k][j])
#print('迭代轮次:', steps, ' e:', e)
result.append(e)#将每一轮更新的损失函数值添加到数组result末尾
#当损失函数小于一定值时,迭代结束
if eij<0.00001:
break
return P,Q,result
R=[
[5,3,0,1],
[4,0,0,1],
[1,1,0,5],
[1,0,0,4],
[0,1,5,4],
]
R=np.array(R)
alpha = 0.0001 #学习率
beta = 0.002
N = len(R) #表示行数
M = len(R[0]) #表示列数
K = 3 #3个因子
p = np.random.rand(N, K) #随机生成一个 N行 K列的矩阵
q = np.random.rand(K, M) #随机生成一个 M行 K列的矩阵
P, Q, result=matrix(R, p, q, K, alpha, beta)
print("矩阵R为:\n",R)
print("矩阵P为:\n",P)
print("矩阵Q为:\n",Q)
MF = np.dot(P,Q)
print("预测矩阵:\n",MF)
print()
#下面代码可以绘制损失函数的收敛曲线图
print("损失函数的收敛过程为:")
n=len(result)
x=range(n)
plt.plot(x, result,color='b',linewidth=3)
plt.xlabel("generation")
plt.ylabel("loss")
plt.show()
下面是运行的结果:
矩阵R为:
[[5 3 0 1]
[4 0 0 1]
[1 1 0 5]
[1 0 0 4]
[0 1 5 4]]
矩阵P为:
[[0.11646557 2.20824426 0.88565195]
[0.03596535 1.51168656 1.0372382 ]
[1.6897674 0.11533018 1.13699815]
[1.37709179 0.16820378 0.84690913]
[1.50038103 1.43928107 0.61271558]]
矩阵Q为:
[[-0.09628733 -0.1151309 1.07555694 2.32234973]
[ 1.97215153 0.90817549 1.96709116 -0.04825554]
[ 0.89544739 0.69563508 0.70044543 0.95484359]]
预测矩阵:
[[5.13683287 2.60815509 5.08943398 1.00957287]
[3.90660419 2.09027523 3.73883681 1.00097713]
[1.08286742 0.7011314 2.8407112 5.00432095]
[0.95748942 0.58335244 2.40522644 3.99863775]
[3.24266723 1.56060602 4.87411612 4.00000375]]
损失函数的收敛过程为: