云是雨的梦,雨是云的前生。
矩阵分解是将矩阵分解为数个矩阵的乘积,用矩阵分解做协同过滤是广泛使用的方法
常见的有三种:
1.三角分解法
2.QR分解法
3.奇异值分解法
原始的SVD又名奇异值分解,如果是用户评分矩阵,首先需要对缺失值进行简单的不全,比如用全局平均,然后用SVD进行分解
其中,R为原始的评分矩阵,维度是mn,U和V分贝是一个km和kn的正交矩阵,S为kk的对角矩阵,对角线上的每一个元素都是矩阵的奇异值。这种纯数学的方法计算量特别大,实际应用中的数据根本处理不了。Simon Funk的Funk-SVD方法解决了这个问题,思想很简单:直接通过训练集的观察值利用最小化RMSE学习P、Q矩阵,这就是机器学习的思想了。
SVD矩阵分解非常成功,有很多的迭代的方法,最有名的就是SVD++了。提SVD++之前,我们先看一个简单的BiasSVD:
如果将用户历史行为对用户评分预测影响考虑进来就是SVD++算法:
SVD++的核心思想是把基于领域的itemCF算法用矩阵分解的方法实现,转换的方法是这样的:
1.FM
2.隐式反馈矩阵分解
3.基于特征的矩阵分解
对于推荐系统来说存在两大场景即评分预测(rating prediction)与Top-N推荐(item recommendation,item ranking)。评分预测场景主要用于评价网站,比如用户给自己看过的电影评多少分(MovieLens),或者用户给自己看过的书籍评价多少分(Douban)。其中矩阵分解技术主要应用于该场景。Top-N推荐场景主要用于购物网站或者一般拿不到显式评分信息的网站,即通过用户的隐式反馈信息来给用户推荐一个可能感兴趣的列表以供其参考。
有如下R(5,4)的打分矩阵:(“-”表示用户没有打分),其中打分矩阵R(n,m)是n行和m列,n表示user个数,m表示iten个数
那么,如何根据目前的矩阵R(5,4)如何对未打分的商品进行评分的预测(如何得到分值为0的用户的打分值)?
——矩阵分解的思想可以解决这个问题,其实这种思想可以看作是有监督的机器学习问题(回归问题)。
矩阵R可以近似表示为P与Q的乘积:R(n,m)≈ P(n,K)*Q(K,m)
矩阵分解的过程中,将原始的评分矩阵分解成两个矩阵和的乘积:
矩阵P(n,K)表示n个user和K个特征之间的关系矩阵,这K个特征是一个中间变量,矩阵Q(K,m)的转置是矩阵Q(m,K),矩阵Q(m,K)表示m个item和K个特征之间的关系矩阵,这里的K值是自己控制的,可以使用交叉验证的方法获得最佳的K值。为了得到近似的R(n,m),必须求出矩阵P和Q,如何求它们呢?
步骤
1.首先令
2.损失函数: 使用原始的评分矩阵与重新构建的评分矩阵之间的误差的平方作为损失函数。
如果R(i,j)已知,则R(i,j)的误差平方和为
最终,需要求解所有的非“-”项的损失之和最小值:
3.使用梯度下降法获得修正的p和q分量:
根据梯度方向更新变量
4.不停迭代直至算法最终收敛(直到sum(e^2)<=阈值
加入正则项
1.第一步同上
2.在通常求解过程中,为了能够有较好的泛化能力,会在损失函数中加入正则项对参数进行约束
也就是
3.使用梯度下降法获得修正的p和q
4.不停迭代直至算法最终收敛(直到sum(e^2)<=阈值
【预测】利用上述的过程,我们可以得到矩阵,这样便可以为用户 i 对商品 j 进行打分
#导包
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
#参数设置
alph = 0.00049
step = 9000
beta = 0.05
K = 3
# MF
def MF(r,p,q,alph,step,beta):
result = []
count = 0
while(count<step):
count+=1
for i in range(len(r)):
for j in range(len(r)):
#构建损失函数
if r[i][j]>0:
eij = r[i][j] - np.dot(p[i,:],q[:,j])
for k in range(K):
pd_p = -2*eij*q[k][j]+beta * p[i][k]
pd_q = -2*eij*p[i][k]+beta * q[k][j]
p[i][k] -= alph*pd_p
q[k][j] -= alph*pd_q
e = 0
for i in range(len(r)):
for j in range(len(r)):
if r[i][j]>0 :
eij = r[i][j] - np.dot(p[i,:],q[:,j])
e += eij**2
for n in range(K):
e += (beta/2)*(p[i][k]**2+q[k][j]**2)
result.append(e)
# print(e)
return p , q , result
#原始矩阵
r = [
[1,0,3,0,4],
[0,2,1,4,0],
[1,0,0,2,3],
[2,0,1,0,0],
[0,0,2,0,0]
]
r = np.array(r)
print(f"输入矩阵为\n{r}")
p = np.random.rand(5,K)
q = np.random.rand(K,5)
new_p,new_q,result = MF(r,p,q,alph,step,beta)
print(f'输出矩阵为\n{np.dot(new_p,new_q)}' )
plt.plot(range(len(result)),result)
最后结果如图