现给出例子:
8 | 3 | - | 2 | |
9 | - | - | 5 | |
7 | - | - | 8 | |
- | 1 | 6 | 9 |
假设
代表用户代号,
代表商品编号,而表格中的的数值则为用户对商品的评分,“ - ”代表未对其评分。现要求预测出用户对商品未评价的分数是多少?这里就需要用到矩阵分解对其进行预测,我们可将上述表格看作一个矩阵对其求解。
一、矩阵分解原理
矩阵分解原理很简单,例如有一矩阵R,求两个矩阵P、Q,使P、Q的矩阵乘法等于R,
即
这里P、Q称为R的分解矩阵,我们可通过预测R的分解矩阵来预测R,而预测方式需要用到梯度下降。
二、梯度下降进行矩阵分解
(1)公式讲解
①建立损失函数
同时为了防止过拟合我们需要对损失函数加上正则化函数:
PS:关于过拟合的概念和解决方式可以移步这位大佬的文章,很容易理解
其中R为初始矩阵,P、Q为其分解矩阵,m、n为R的长和宽、b为正则系数。
为了能够用代码实现需要对矩阵的每一元素依次求解,需要对损失函数作进一步改进可得:
即
其中
代表R的第i行第j列的元素的损失函数,
代表R的第i行第j列的元素,
同理,n既为P的宽又为Q的长。
②损失函数的求导
关于p的求导:
关于q的求导:
③更新P矩阵与Q矩阵
其中lr为迭代系数,即学习率
④检测损失函数是否小于设定误差值
若
则停止迭代,
为提前的设定允许误差范围。
反正继续重复执行上述步骤。
三、代码实现(python)
(1)必要的库的导入以及一些参数的设置
创建矩阵时未评分的用0代替
import numpy as np
import matplotlib.pyplot as plt
lr = 0.05 #学习率
e=0.01 #误差值
b=0.005 #正则系数
maxloop=15000 #最大迭代次数
k = 2 #分解矩阵阶数
#创建初始矩阵,0代表未评分
score=np.array([[8,3,0,2],
[9,0,0,5],
[7,0,0,8],
[0,1,6,9]])
#作图数据收集用的列表
D_Loss = []
V_Loss = []
(2)矩阵分解(MF 函数)
def MF(R, k): #矩阵分解
①创建随机R的俩个分解矩阵以及它们的偏导矩阵
#创建分解矩阵
P = np.random.rand(R.shape[0], k)
Q = np.random.rand(k, R.shape[1])
#k为提前设定的矩阵阶数,k既等于P的宽又等于Q的长,这样才能P、Q才能满足矩阵乘法
#偏导矩阵
dP = np.zeros((R.shape[0], k))
dQ = np.zeros((k, R.shape[1]))
②求偏导以及更新P,Q
for m in range(maxloop):
count += 1
D_Value_Sum = 0
#求偏导矩阵
for i in range(R.shape[0]):
for j in range(R.shape[1]):
D_Value = R[i, j] - np.dot(P[i, :], Q[:, j])
D_Value_Sum += abs(D_Value)
if R[i, j] > 0:
for k in range(P.shape[1]):
dP[i,k] = -2 * Q[k,j] * D_Value + b * P[i,k] #损失函数关于P的偏导
dQ[k,j] = -2 * P[i,k] * D_Value + b * Q[k,j] #损失函数关于Q的偏导
for k in range(P.shape[1]):
P[i, k] = P[i, k] - lr * dP[i, k] # 更新P
Q[k, j] = Q[k, j] - lr * dQ[k, j] # 更新Q
③误差值的判断
D_Value_Sum = D_Value_Sum /(R.shape[0] * R.shape[1])#损失函数的值的求解
#作图时的数据收集
D_Value_Loss = pow(D_Value_Sum,2)
D_Loss.append(D_Value_Sum)
V_Loss.append(D_Value_Loss)
if m % 1000 == 0: print(D_Value_Sum)#每迭代1000次输出误差值
if D_Value_Sum < e: break#迭代条件判断
return P,Q,count
(3)结果输出以及作图
P,Q,count= MF(score,k)
print("P=\n",P)
print()
print("Q=\n",Q)
print("score=\n",np.dot(P,Q))
print("共迭代{}次".format(count))
#创建画布
plt.figure()
# 想用中文必须进行设置RC参数
plt.rcParams['font.sans-serif'] = 'SimHei'
# 设置RC参数字体,让其支持中文
plt.rcParams['axes.unicode_minus'] = False
plt.subplot(1,2,1)
plt.plot(D_Loss,color="red")
plt.xlabel("迭代次数")
plt.ylabel("误差值")
plt.title("误差值变化曲线")
plt.subplot(1,2,2)
plt.plot(V_Loss,color="blue")
plt.xlabel("迭代次数")
plt.ylabel("误差值")
plt.title("损失函数变化曲线")
plt.show()
(4)运行结果展示
P=
[[2.41059748 0.57011407]
[2.17149241 1.59128246]
[0.88111496 2.65739805]
[0.13628212 3.02581629]]Q=
[[2.92288979 1.17734687 0.1111894 0.12739064]
[1.66424432 0.27737902 1.97788322 2.96891167]]
score=
[[7.99471986 2.99624707 1.39565193 1.99970585]
[8.99531581 2.99798816 3.38881781 5.00100486]
[6.99796153 1.7744844 5.35399365 8.00182587]
[5.43403521 0.99974928 5.9998644 9.00074238]]
共迭代15000次
误差值变化
四、完整代码
import numpy as np
import matplotlib.pyplot as plt
lr = 0.05 #学习率
e=0.01 #误差值
b=0.005 #正则系数
maxloop=15000 #最大迭代次数
k = 2 #分解矩阵阶数
#创建初始矩阵,0代表未评分
score=np.array([[8,3,0,2],
[9,0,0,5],
[7,0,0,8],
[0,1,6,9]])
#作图数据收集用的列表
D_Loss = []
V_Loss = []
def MF(R, k): #矩阵分解
#创建两个分解矩阵
P = np.random.rand(R.shape[0], k)
Q = np.random.rand(k, R.shape[1])
#偏导矩阵
dP = np.zeros((R.shape[0], k))
dQ = np.zeros((k, R.shape[1]))
#开始模拟
count = 0
for m in range(maxloop):
count += 1
D_Value_Sum = 0
#求偏导矩阵
for i in range(R.shape[0]):
for j in range(R.shape[1]):
D_Value = R[i, j] - np.dot(P[i, :], Q[:, j])
D_Value_Sum += abs(D_Value)
if R[i, j] > 0:#若评分不等于0则
for k in range(P.shape[1]):
dP[i,k] = -2 * Q[k,j] * D_Value + b * P[i,k] #损失函数关于P的偏导
dQ[k,j] = -2 * P[i,k] * D_Value + b * Q[k,j] #损失函数关于Q的偏导
for k in range(P.shape[1]):
P[i, k] = P[i, k] - lr * dP[i, k] # 更新P
Q[k, j] = Q[k, j] - lr * dQ[k, j] # 更新Q
D_Value_Sum = D_Value_Sum /(R.shape[0] * R.shape[1])#损失函数的值的求解
#作图时的数据收集
D_Value_Loss = pow(D_Value_Sum,2)
D_Loss.append(D_Value_Sum)
V_Loss.append(D_Value_Loss)
if m % 1000 == 0: print(D_Value_Sum)#每迭代1000次输出误差值
if D_Value_Sum < e: break
return P,Q,count
P,Q,count= MF(score,k)
print("P=\n",P)
print()
print("Q=\n",Q)
print("score=\n",np.dot(P,Q))
print("共迭代{}次".format(count))
#创建画布
plt.figure()
# 想用中文必须进行设置RC参数
plt.rcParams['font.sans-serif'] = 'SimHei'
# 设置RC参数字体,让其支持中文
plt.rcParams['axes.unicode_minus'] = False
plt.subplot(1,2,1)
plt.plot(D_Loss,color="red")
plt.xlabel("迭代次数")
plt.ylabel("误差值")
plt.title("误差值变化曲线")
plt.subplot(1,2,2)
plt.plot(V_Loss,color="blue")
plt.xlabel("迭代次数")
plt.ylabel("误差值")
plt.title("损失函数变化曲线")
plt.show()