基于矩阵分解的推荐算法

描述

我们都熟知在一些软件中常常有评分系统,但并不是所有的用户user都会对项目item进行评分,因此评分系统所收集到的用户评分信息必然是不完整的矩阵。那如何跟据这个不完整矩阵中已有的评分来预测未知评分呢。使用矩阵分解的思想很好地解决了这一问题。

假如我们现在有一个用户-项目的评分矩阵R(n,m)是nm列的矩阵,n表示user个数,m行表示item个数

i1i2i3i4
u153-1
u24--1
u311-5
u41--4
u5-154

其中,u1⋯u5表示的是5个不同的用户,I1⋯i4表示的是4个不同的商品,这样便构成了用户-商品矩阵,在该矩阵中,有用户对每一件商品的打分,其中“-”表示的是用户未对该商品进行打分。

在推荐系统中有一类问题是对未打分的商品进行评分的预测。

基于矩阵分解的推荐算法

矩阵分解的一般形式

矩阵分解是指将一个矩阵分解成两个或者多个矩阵的乘积。对于上述的用户-商品矩阵(评分矩阵),记为 R m × n R_{m\times n} Rm×n 。可以将其分解成两个或者多个矩阵的乘积,假设分解成两个矩阵 P m × k P_{m\times k} Pm×k Q k × n Q_{k\times n} Qk×n ,我们要使得矩阵 P m × k P_{m\times k} Pm×k Q k × n Q_{k\times n} Qk×n的乘积能够还原原始的矩阵 R m × n R_{m\times n} Rm×n

R m × n ≈ P m × k × Q k × n = R ^ m × n R_{m\times n}\approx P_{m\times k}\times Q_{k\times n}=\hat{R}_{m\times n} Rm×nPm×k×Qk×n=R^m×n

其中,矩阵 P m × k P_{m\times k} Pm×k 表示的是m个用户与k个主题之间的关系,而矩阵 Q k × n Q_{k\times n} Qk×n表示的是 k k k个主题与 n n n个商品之间的关系。

利用矩阵分解进行预测

在上述的矩阵分解的过程中,将原始的评分矩阵 R m × n R_{m\times n} Rm×n分解成两个矩阵 P m × k P_{m\times k} Pm×k Q k × n Q_{k\times n} Qk×n的乘积:

R m × n ≈ P m × k × Q k × n = R ^ m × n R_{m\times n}\approx P_{m\times k}\times Q_{k\times n}=\hat{R}_{m\times n} Rm×nPm×k×Qk×n=R^m×n

那么接下来的问题是如何求解矩阵 P m × k P_{m\times k} Pm×k Q k × n Q_{k\times n} Qk×n的每一个元素,可以将这个问题转化成机器学习中的回归问题进行求解。

损失函数

可以使用原始的评分矩阵 R m × n R_{m\times n} Rm×n与重新构建的评分矩阵 R ^ m × n \hat{R}_{m\times n} R^m×n之间的误差的平方作为损失函数,即:

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_{i,j}^2=\left ( r_{i,j}-\hat{r}_{i,j} \right )^2=\left (r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )^2 ei,j2=(ri,jr^i,j)2=(ri,jk=1Kpi,kqk,j)2

最终,需要求解所有的非“-”项的损失之和的最小值:

m i n    l o s s = ∑ r i , j ≠ − e i , j 2 min\; loss= \sum_{r_{i,j}\neq -}e_{i,j}^2 minloss=ri,j=ei,j2

损失函数的求解

对于上述的平方损失函数,可以通过梯度下降法求解,梯度下降法的核心步骤是

  • 求解损失函数的负梯度:

∂ ∂ p i , k e i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) q k , j = − 2 e i , j q k , j \frac{\partial }{\partial p_{i,k}}e_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )q_{k,j}=-2e_{i,j}q_{k,j} pi,kei,j2=2(ri,jk=1Kpi,kqk,j)qk,j=2ei,jqk,j

∂ ∂ q k , j e i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) p i , k = − 2 e i , j p i , k \frac{\partial }{\partial q_{k,j}}e_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )p_{i,k}=-2e_{i,j}p_{i,k} qk,jei,j2=2(ri,jk=1Kpi,kqk,j)pi,k=2ei,jpi,k

  • 根据负梯度的方向更新变量:

p i , k ′ = p i , k − α ∂ ∂ p i , k e i , j 2 = p i , k + 2 α e i , j q k , j {p_{i,k}}'=p_{i,k}-\alpha \frac{\partial }{\partial p_{i,k}}e_{i,j}^2=p_{i,k}+2\alpha e_{i,j}q_{k,j} pi,k=pi,kαpi,kei,j2=pi,k+2αei,jqk,j

q k , j ′ = q k , j − α ∂ ∂ q k , j e i , j 2 = q k , j + 2 α e i , j p i , k {q_{k,j}}'=q_{k,j}-\alpha \frac{\partial }{\partial q_{k,j}}e_{i,j}^2=q_{k,j}+2\alpha e_{i,j}p_{i,k} qk,j=qk,jαqk,jei,j2=qk,j+2αei,jpi,k

通过迭代,直到算法最终收敛。

加入正则项的损失函数即求解方法

通常在求解的过程中,为了能够有较好的泛化能力,会在损失函数中加入正则项,以对参数进行约束,加入 L 2 L_2 L2正则的损失函数为:

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 k , j 2 ) E_{i,j}^2=\left (r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )^2+\frac{\beta }{2}\sum_{k=1}^{K}\left ( p_{i,k}^2+q_{k,j}^2 \right ) Ei,j2=(ri,jk=1Kpi,kqk,j)2+2βk=1K(pi,k2+qk,j2)

利用梯度下降法的求解过程为:

  • 求解损失函数的负梯度:

∂ ∂ p i , k E i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) q k , j + β p i , k = − 2 e i , j q k , j + β p i , k \frac{\partial }{\partial p_{i,k}}E_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )q_{k,j}+\beta p_{i,k}=-2e_{i,j}q_{k,j}+\beta p_{i,k} pi,kEi,j2=2(ri,jk=1Kpi,kqk,j)qk,j+βpi,k=2ei,jqk,j+βpi,k

∂ ∂ q k , j E i , j 2 = − 2 ( r i , j − ∑ k = 1 K p i , k q k , j ) p i , k + β q k , j = − 2 e i , j p i , k + β q k , j \frac{\partial }{\partial q_{k,j}}E_{i,j}^2=-2\left ( r_{i,j}-\sum_{k=1}^{K}p_{i,k}q_{k,j} \right )p_{i,k}+\beta q_{k,j}=-2e_{i,j}p_{i,k}+\beta q_{k,j} qk,jEi,j2=2(ri,jk=1Kpi,kqk,j)pi,k+βqk,j=2ei,jpi,k+βqk,j

  • 根据负梯度的方向更新变量:

p i , k ′ = p i , k − α ( ∂ ∂ p i , k e i , j 2 + β p i , k ) = p i , k + α ( 2 e i , j q k , j − β p i , k ) {p_{i,k}}'=p_{i,k}-\alpha \left ( \frac{\partial }{\partial p_{i,k}}e_{i,j}^2+\beta p_{i,k} \right )=p_{i,k}+\alpha \left ( 2e_{i,j}q_{k,j}-\beta p_{i,k} \right ) pi,k=pi,kα(pi,kei,j2+βpi,k)=pi,k+α(2ei,jqk,jβpi,k)

q k , j ′ = q k , j − α ( ∂ ∂ q k , j e i , j 2 + β q k , j ) = q k , j + α ( 2 e i , j p i , k − β q k , j ) {q_{k,j}}'=q_{k,j}-\alpha \left ( \frac{\partial }{\partial q_{k,j}}e_{i,j}^2+\beta q_{k,j} \right )=q_{k,j}+\alpha \left ( 2e_{i,j}p_{i,k}-\beta q_{k,j} \right ) qk,j=qk,jα(qk,jei,j2+βqk,j)=qk,j+α(2ei,jpi,kβqk,j)

通过迭代,直到算法最终收敛。

预测

利用上述的过程,我们可以得到矩阵 P m × k P_{m\times k} Pm×k Q k × n Q_{k\times n} Qk×n,这样便可以为用户 i i i对商品 j j j进行打分:

∑ k = 1 K p i , k q k , j \sum_{k=1}^{K}p_{i,k}q_{k,j} k=1Kpi,kqk,j

代码实现

import numpy as np
from math import pow
import matplotlib.pyplot as plt
%matplotlib inline

def mf(R,P,Q,K):
    alpha = 0.0002
    beta = 0.02
    times = 5000
    eplison = 0.001
    result = []
    #将矩阵Q进行转置
    Q = Q.T
    #将生成的矩阵相乘
    for time in range(times):
        #求R尖的值
        for i in range(len(R)):
            for j in range(len(R[i])):
                eij = R[i][j] - np.dot(P[i,:],Q[:,j])
                for k in range(K):
                    P[i][k] = P[i][k] + 2*alpha*eij*Q[k][j]
                    Q[k][j] = Q[k][j] + 2*alpha*eij*P[i][k]
        eR = np.dot(P,Q)
        #求eij的平方
        eij_2 = 0
        for i in range(len(R)):
            for j in range(len(R[i])):
                for k in range(K):
                    eij_2 = eij_2 + pow((R[i][j] - np.dot(P[i,:],Q[:,j])),2) + 0.5*beta*(pow(P[i][k],2))+pow(Q[k][j],2)
        result.append(eij_2)
        if eij_2 < eplison:
            break
    return P,Q.T,result 



def main():
    R = [
        [5,3,0,1],
        [4,0,0,1],
        [1,1,0,5],
        [1,0,0,4],
        [0,1,5,4]
    ]
    #将R列表转化为矩阵
    R = np.array(R)
    #获取该矩阵的大小
    N = len(R)
    M = len(R[0])

    print(N)
    print(M)
    ''' 
    如何预测缺失的评分呢?对于缺失的评分,可以转化为基于机器学习的回归问题,也就是连续值的预测,
    对于矩阵分解有如下式子,R是类似图1的评分矩阵,假设N*M维(N表示行数,M表示列数),可以分解为P跟Q矩阵,
    其中P矩阵维度N*K,P矩阵维度K*M。
    对于P,Q矩阵的解释,直观上,P矩阵是N个用户对K个主题的关系,Q矩阵是K个主题跟M个物品的关系,至于K个
    主题具体是什么,在算法里面K是一个参数,需要调节的,通常10~100之间。
    '''
    K = 2
    P = np.random.rand(N,K)
    Q = np.random.rand(M,K)
    #调用矩阵分解的函数
    mf_P,mf_Q,result = mf(R,P,Q,K)
    print("原始矩阵:",R)
    mf_R = np.dot(mf_P,mf_Q.T)
    print("经过MF后:",mf_R)
    #做出图像
    for t in range(5000):
        plt.scatter(t,result[t])
    plt.show()
main()
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落难Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值