矩阵分解与梯度下降算法—推荐系统基础算法(含python代码实现以及详细例子讲解)

一、问题导入

1.1 理论概述

矩阵分解通俗来说就是矩阵相乘的逆运算。即通过一个数据并不完全的矩阵,分解为两个数据完全的子矩阵,其中第一个子矩阵的列等于第二个子矩阵的行。再由两个数据完全的子矩阵相乘得到包含预测值数据完全的原矩阵,在推荐系统中对于用户-物品这个评分矩阵R,我们可以将其分解为用户-特性矩阵P,以及特性-物品矩阵Q。

1.2 具体实例

举个例子,比如音乐推荐。我们比如有3个用户(u表示),4首音乐(m表示)。
在这里插入图片描述

用户对歌曲的评分矩阵R

可以想到,在真实的场景中,这个矩阵是极其稀疏的,对于庞大的音乐库来说,每个用户听过的曲目都是极小的一部分。并且由于用户数量和音乐数量极大,所以这个R实际上是一个极大的矩阵。
现在假设音乐的风格有以下几类:摇滚,重金属,民谣,说唱,轻音乐(五种音乐,相当于五个主题,也就是所谓k=5,即隐空间的维度)。分别用s1,s2,s3,s4,s5来表示。那么我们希冀得到用户对于音乐风格偏好的矩阵Q,以及每个风格在歌曲中所占比重的矩阵P。
在这里插入图片描述

用户对于音乐风格偏好的矩阵Q 每个音乐风格在歌曲中所占比重的矩阵P

通过这两个矩阵,我们就可以计算出 u i u_{i} ui m j m_{j} mj 的评分: w i j u m = ∑ h = 1 5 w i h u s w h j s m w_{ij}^{um}=\sum_{h=1}^{5}w_{ih}^{us}w_{hj}^{sm} wijum=h=15wihuswhjsm ,5是音乐风格种类的数量(k=5),通过这种方式,我们即可补全矩阵R,从而实现对用户评分的预测。

1.3 问题分析

现在的问题是,如何求出矩阵Q和P呢?其实我们可以给矩阵Q和P设置一个初始值,通过迭代的方式,逐步使得 R ^ = Q P T \hat{R}=QP^{T} R^=QPT与R之间的距离变小。其中设置loss函数,然后使用梯度下降法不断迭代,并且加入了防止过拟合问题的正则化项。来较好的解决这一问题。

二、梯度下降

2.1 梯度下降原理概述

梯度下降法(gradient descent)是一个最优化算法,也称为最速下降法(steepest descent)核心公式为: θ = θ 0 − α ∂ ∂ θ 0 J ( θ 0 ) \theta=\theta_{0}-\alpha\frac{\partial}{\partial \theta_{0}}J(\theta_{0}) θ=θ0αθ0J(θ0)(其中α为步长,也叫学习率)

2.2 梯度下降的理论解释

2.2.1 问题导入

如下图所示,假设左侧曲线为一个山坡,某人在A点,他被困在山上,需要从山上下来(找到山的最低点,也就是山谷I)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。
在这里插入图片描述

2.2.2 问题分析

这个时候,他就可以利用梯度下降算法来帮助自己下山。具体来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走,同理,如果我们的目标是上山,也就是爬到山顶,那么此时应该是朝着最陡峭的方向往上走。然后每走一段距离,都反复采用同一个方法,最后就能成功的抵达山谷I。
我们同时可以假设这座山最陡峭的地方是无法通过肉眼立马观察出来的,而是需要一个复杂的工具来测量,同时,这个人此时正好拥有测量出最陡峭方向的能力。所以,此人每走一段距离,都需要一段时间来测量所在位置最陡峭的方向,这是比较耗时的。那么为了在太阳下山之前到达山底,就要尽可能的减少测量方向的次数。这是一个两难的选择,如果测量的频繁,可以保证下山的方向是绝对正确的,但又非常耗时,如果测量的过少,又有偏离轨道的风险。所以需要找到一个合适的测量方向的频率,来确保下山的方向不错误,同时又不至于耗时太多!而这里所说的每走一段距离,就是前面公式中提到的参数α(也叫学习率),可以理解步长越长,α越大,测量的频率越小,越易偏离轨道。同理步长越短,α越小,虽然在保证下山方向正确的前提,但是非常耗时。(这就要涉及一个问题调参,他在人工智能算法里很常见。是根据实验原理与结果不断分析调整的常数)而这座山每个点最陡峭的地方就是函数每个点的切线方向,其数值等于函数在该点的导数值。由此,我们可以简单理解梯度下降的核心公式: θ = θ 0 − α ∂ ∂ θ 0 J ( θ 0 ) \theta=\theta_{0}-\alpha\frac{\partial}{\partial \theta_{0}}J(\theta_{0}) θ=θ0αθ0J(θ0)(其中α为步长,也叫学习率)。当我们到达山谷时,此时山谷的导数值为零即θ不再变化。
在这里插入图片描述
或许你会担心如果步长α较大,会不会越过山谷直接跑到对面的山坡上,如下图所示,此时仍可以到达山谷,但是可以想象当步长α非常大时,梯度下降的迭代在两曲线间剧烈震荡,可能会导致无法到达山谷。所以我们要合理的调整步长α。从而避免这类问题。
在这里插入图片描述

当步长α较大时的梯度下降过程

2.3 梯度下降的具体实现

2.3.1梯度下降法的一般步骤

假设函数 y = f ( θ 1 , θ 2 , . . . , θ n ) y=f(\theta_{1},\theta_{2},...,\theta_{n}) y=f(θ1,θ2,...,θn)只有一个极小点。
初始给定参数为 θ 0 = ( θ 10 , θ 20 , . . . , θ n 0 ) \theta_{0}=(\theta_{10},\theta_{20},...,\theta_{n0}) θ0=(θ10,θ20,...,θn0)从这个点如何搜索才能找到原函数的极小值点?
①首先设定一个较小的正数 α \alpha α ε \varepsilon ε
②求当前位置出处的各个偏导数:
f ′ ( x m 0 ) x = ∂ y ∂ x m ( x m 0 ) , m = 1 , 2 , . . . , n f'(x_{m0})x=\frac{\partial y}{\partial x_{m}}(x_{m0}),m=1,2,...,n f(xm0)x=xmy(xm0),m=1,2,...,n
③修改当前函数的参数值,公式如下:
x m ′ = x m − α ∂ y ∂ x m ( x m 0 ) , m = 1 , 2 , . . . , n x'_{m}=x_{m}-\alpha\frac{\partial y}{\partial x_{m}}(x_{m0}),m=1,2,...,n xm=xmαxmy(xm0),m=1,2,...,n
④如果参数变化量小于 ε \varepsilon ε,退出;否则返回第2步。

2.3.2梯度下降的两个参数(步长、终止条件之变量)

梯度下降有个重要的参数 α \alpha α ε \varepsilon ε(自己设置的,其中 α \alpha α>0, ε \varepsilon ε>0),其中 α \alpha α为步长, ε \varepsilon ε为终止条件之变量,一般非常小接近于0,即当计算后的 与已知的 差值的绝对值 小于 ε \varepsilon ε时终止计算,此时的 即为要求的最小值点。(理论上可以达到0,这就要求我们设置的 ε \varepsilon ε非常小)

2.3.3算法终止的条件

算法终止的条件有两个,一是迭代次数,二是终止条件的变化量 ε \varepsilon ε
(1)当程序运行次数达到迭代次数时,此时结束程序,获得结果
(2) Δ θ = θ − θ 1 \Delta\theta=\theta -\theta_{1} Δθ=θθ1小于 ε \varepsilon ε时,结束程序,获得结果。

三、用梯度下降来解决矩阵分解问题

3.1预测矩阵

预测矩阵一般用符号 R ^ \hat{R} R^来表示,有 r ^ = p i T q j = ∑ k = 1 k p i k q k j \hat{r}=p_{i}^{T}q_{j}=\sum_{k=1}^{k}p_{ik}q_{kj} r^=piTqj=k=1kpikqkj R ^ \hat{R} R^与R的维度相同,其中 r i j ^ \hat{r_{ij}} rij^ R ^ \hat{R} R^第i行第j列的元素值)

3.2构造损失函数(平方法)

使用原始的评分矩阵R与重新构建的评分矩阵 R ^ \hat{R} R^之间的误差的平方作为损失函数,即:
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_{ij}^{2}=(r_{ij}-\hat{r_{ij}})^{2}=(r_{ij}-\sum_{k=1}^{k}p_{ik}q_{kj})^{2} eij2=(rijrij^)2=(rijk=1kpikqkj)2
通过梯度下降法,更新变量:
求导:
∂ e i j 2 ∂ p i k = − 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 e_{ij}^{2}}{\partial p_{ik}}=-2(r_{ij}-\sum_{k=1}^{k}p_{ik}q_{kj})q_{kj}=-2e_{ij}q_{kj} pikeij2=2(rijk=1kpikqkj)qkj=2eijqkj

∂ e i j 2 ∂ p k j = − 2 ( r i j − ∑ k = 1 k p i k q k j ) q i k = − 2 e i j q i k \frac{\partial e_{ij}^{2}}{\partial p_{kj}}=-2(r_{ij}-\sum_{k=1}^{k}p_{ik}q_{kj})q_{ik}=-2e_{ij}q_{ik} pkjeij2=2(rijk=1kpikqkj)qik=2eijqik
根据负梯度的方向更新变量:
p i k ′ = p i k − α ∂ e i j 2 ∂ p i k = p i k + 2 α e i j q k j p'_{ik}=p_{ik}-\alpha\frac{\partial e_{ij}^{2}}{\partial p_{ik}}=p_{ik}+2\alpha e_{ij}q_{kj} pik=pikαpikeij2=pik+2αeijqkj
q k j ′ = q k j − α ∂ e i j 2 ∂ p k j = p k j + 2 α e i j q i k q'_{kj}=q_{kj}-\alpha\frac{\partial e_{ij}^{2}}{\partial p_{kj}}=p_{kj}+2\alpha e_{ij}q_{ik} qkj=qkjαpkjeij2=pkj+2αeijqik

四、过拟合问题

4.1 理论概述

过拟合问题(over-fitting)指为了得到一致的假设而使训练过程变得过度严格(这里的假设可以理解为构建的函数模型)。简单来说就是在在数据量相对较小的训练集中训练出过于完美的模拟,而在测试集中失效的问题。通俗来说就是拟合的越好,但相应预测的能力可能变得很差。
具体解决方法为正则化。即在损失函数加入正则项。即(权重衰减:减小参数θ)
将loss函数 L o s s 0 ( θ ) = 1 2 m ∑ i = 1 m ( h θ ( x i ) − y i ) 2 Loss_{0}(\theta)=\frac{1}{2m}\sum_{i=1}^{m}(h_{\theta}(x^{i})-y^{i})^2 Loss0(θ)=2m1i=1m(hθ(xi)yi)2
化为 L o s s 0 ( θ ) = 1 2 m [ ∑ i = 1 m ( h θ ( x i ) − y i ) 2 + λ ∑ j = 1 n θ j 2 ] Loss_{0}(\theta)=\frac{1}{2m}[\sum_{i=1}^{m}(h_{\theta}(x^{i})-y^{i})^2+\lambda\sum_{j=1}^{n}\theta_{j}^{2}] Loss0(θ)=2m1[i=1m(hθ(xi)yi)2+λj=1nθj2]

对于一元线性方程 y = θ 0 + θ 1 x y= \theta_{0}+\theta_{1}x y=θ0+θ1x
在这里插入图片描述

现在 θ 1 \theta_{1} θ1前面系数小于1,(类似减小高阶参数),这也就是权重衰减的由来。

类似的,我们可以根据(7)(8)来类比出正则化后的第j的参数 的梯度下降算法公式(9)(10)
在这里插入图片描述

4.2 矩阵分解的正则化

在损失函数中加入正则化惩罚项:通常在求解的过程中,为了能够有较好的泛化能力,会在损失函数中加入正则项,以对参数进行约束。加入正则项后的计算过程如下:
在这里插入图片描述

通过梯度下降法,更新变量:
求导:
在这里插入图片描述

根据负梯度的方向更新变量:
在这里插入图片描述

5.算法终止:
每次更新完 R ^ \hat{R} R^后,计算一次loss值,若loss值非常小或者到达最大迭代次数,结束算法。于是就得到了我们最终的预测矩阵 R ^ \hat{R} R^

五、矩阵分解与基于用户的协同过滤比较

假设有如下电子商务评分数据集,预测用户 C 对商品 4 的评分。
表3-1 电子商务评分数据集

用户商品1商品2商品3商品4
用户A435
用户B54
用户C542
用户D243
用户E345

表中 ?表示评分未知。
方法一:矩阵分解法

用户商品1商品2商品3商品4
用户A44.52335
用户B4.131545
用户C5424.015
用户D243.0193
用户E3454.499

方法二:协同过滤算法

用户商品1商品2商品3商品4
用户A44.30535
用户B4.730545
用户C5424,272
用户D243.4813
用户E3454.000

六、python代码实现
★ 最简单的矩阵分解代码:

import numpy as np
import math
import matplotlib.pyplot as plt
#定义矩阵分解函数
def Matrix_decomposition(R,P,Q,N,M,K,alpha=0.0002,beta=0.02):
    Q = Q.T #Q矩阵转置
    loss_list = [] #存储每次迭代计算的loss值
    for step in range(5000):
        #更新R^
        for i in range(N):
            for j in range(M):
                if R[i][j] != 0:
                    #计算损失函数
                    error = R[i][j]
                    for k in range(K):
                        error -= P[i][k]*Q[k][j]
                    #优化P,Q矩阵的元素
                    for k in range(K):
                        P[i][k] = P[i][k] + alpha*(2*error*Q[k][j]-beta*P[i][k])
                        Q[k][j] = Q[k][j] + alpha*(2*error*P[i][k]-beta*Q[k][j])
                    
        loss = 0.0
        #计算每一次迭代后的loss大小,就是原来R矩阵里面每个非缺失值跟预测值的平方损失
        for i in range(N):
            for j in range(M):
                if R[i][j] != 0:
                    #计算loss公式加号的左边
                    data = 0
                    for k in range(K):
                        data = data + P[i][k]*Q[k][j]
                    loss = loss + math.pow(R[i][j]-data,2)
                    #得到完整loss值
                    for k in range(K):
                        loss = loss + beta/2*(P[i][k]*P[i][k]+Q[k][j]*Q[k][j])
                    loss_list.append(loss)
        plt.scatter(step,loss)
        #输出loss值
        if (step+1) % 1000 == 0:
            print("loss={:}".format(loss))
        #判断
        if loss < 0.001:
            print(loss)
            break
    plt.show()    
    return P,Q
if __name__ == "__main__":
    N = 5
    M = 4
    K = 5
    R = np.array([[4,0,3,5],
                [0,5,4,0],
                [5,4,2,0],
                [2,4,0,3],
                [3,4,5,0]]) #N=5,M=4
    print("初始评分矩阵:")
    print(R)
    #定义P和Q矩阵
    P = np.random.rand(N,K) #N=5,K=2
    Q = np.random.rand(M,K) #M=4,K=2
    print("开始矩阵分解:")
    P,Q = Matrix_decomposition(R,P,Q,N,M,K)
    print("矩阵分解结束。")
    print("得到的预测矩阵:")
print(np.dot(P,Q))
运行结果:
初始评分矩阵:
[[4 0 3 5]
 [0 5 4 0]
 [5 4 2 0]
 [2 4 0 3]
 [3 4 5 0]]
得到的预测矩阵:
[[4.06834534 4.52341056 3.10195085 4.83546909]
 [4.13169592 4.93988458 4.06582962 4.45104043]
 [4.78293337 4.2357189  1.90296833 4.01598995]
 [2.46890971 3.29231753 3.01998102 3.2421622 ]
 [2.74180245 4.33160503 4.85526021 4.4990544 ]]

六、矩阵分解相关理论知识

6.1 基本理论知识

矩阵分解就是把原来的大矩阵,近似的分解成小矩阵的乘积,在实际推荐计算时不再使用大矩阵,而是使用分解得到的两个小矩阵具体来说就是,假设用户物品的评分矩阵 A A A m m m n n n维,即一共有 m m m个用户, n n n个物品.通过一套算法转化为两个矩阵 U U U V V V,矩阵 U U U的维度是 m m m k k k,矩阵 V V V的维度是 n n n k k k。这两个矩阵的要求就是通过下面这个公式可以复原矩阵A: U m × n V n × k T = A ^ m × n ≈ A m × n U_{m\times n}V_{n \times k}^{T}=\hat{A}_{m \times n} \approx A_{m \times n} Um×nVn×kT=A^m×nAm×n而直观上说 U U U矩阵就是 m m m个用户对 k k k个主题的关系, Q Q Q矩阵就是 K K K个主题对 M M M个物品的关系,至于说 K K K个主题具体是矩阵分解算法里的一个参数,一般取 10 10 10 100 100 100之间。如何得到每一个用户,每一个物品的 k k k个主题(k维向量),一般考虑两个要素:1)、损失函数 2)、优化算法。

6.2 矩阵分解的学习过程

1)准备好用户物品的评分矩阵,每一条评分数据看作是一条训练样本;
2)给分解后的 U U U矩阵和 V V V矩阵随机初始化元素值;
3)用 U U U V V V计算预测后的分数;
4) 计算预测的分数和实际的分数误差;
5)按照梯度下降的方向更行 U U U V V V中的元素值;
6)重复步骤3到5,直到达到停止条件。
得到分解后的矩阵之后,实质上就是得到每个用户和每个物品的隐因子向量,拿着这个向量再做推荐计算更加简单,简单来说就是拿着物品和用户两个向量,计算点积就是推荐分数。

6.3 矩阵分解过程的理论分析

在一个标准推荐任务中,我们有 m m m个用户(user), n n n个物品(item),以及一个稀疏评分矩阵R( R ∈ R m × n R\in R_{m \times n} RRm×n )。R中每个 R i j R_{ij} Rij表示用户i对于物品j的评分。如果 R i j R_{ij} Rij≠0,那么说明用户 i i i有对物品 j j j的评分,反之则没有。每一个用户 i i i可以用向量 S i u = ( R i 1 , R i 2 , . . . , R i n ) S_{i}^{u}=(R_{i1},R_{i2},...,R_{in}) Siu=(Ri1,Ri2,...,Rin)表示,同样地,每一个物品 j j j可以用向量 S j i = ( R 1 j , R 2 j , . . . , R m j ) S_{j}^{i}=(R_{1j},R_{2j},...,R_{mj}) Sji=(R1j,R2j,...,Rmj)表示。对于用户和物品各自的边信息(side information)矩阵,则分别用 X ∈ R m × p X\in R_{m \times p} XRm×p Y ∈ R n × q Y\in R_{n \times q} YRn×q表示。
设ui,vj∈Rk,其中ui为用户i的隐因子向量(latent factor vector),vj则是物品j的隐因子向量(latent factor vector),k是隐空间的维度。因而,对于用户和物品来说,对应的隐因子向量形式分别是 U = u 1 : m U=u1:m U=u1:m V = v 1 : n V=v1:n V=v1:n。由于 R = U V R=UV R=UV,所以如果能够求出 U U U V V V的话,那么我们可以求出一个非稀疏的评分矩阵 R R R出来。
给定一个稀疏的评分矩阵 R R R,以及边信息矩阵 X X X Y Y Y,我们的目标是学习出 U U U V V V,从而预测出 R R R中缺失的评分。(这也叫矩阵的 U V UV UV分解)

七、参考文章资料来源

1、 https://jiang-hs.github.io/post/YSulIRDP7/
2、 https://blog.csdn.net/GZHermit/article/details/73920755
3、 https://www.bilibili.com/video/BV1wy4y1p7rx

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用Python代码梯度下降算法实现线性回归的示例: 首先,我们需要导入所需的包: ```python import numpy as np import matplotlib.pyplot as plt ``` 然后,我们定义一个函数来计算误差,即损失函数: ```python def compute_cost(X, y, theta): m = len(y) predictions = X.dot(theta) square_err = (predictions - y) ** 2 J = 1 / (2 * m) * np.sum(square_err) return J ``` 其中,X是一个m行n列的特征矩阵,y是一个m行1列的目标向量,theta是一个n行1列的参数向量,m是样本数量,n是特征数量。 接下来,我们定义一个函数来执行梯度下降算法: ```python def gradient_descent(X, y, theta, alpha, num_iters): m = len(y) J_history = np.zeros((num_iters, 1)) for i in range(num_iters): predictions = X.dot(theta) errors = np.subtract(predictions, y) delta = (alpha / m) * X.transpose().dot(errors) theta = theta - delta J_history[i] = compute_cost(X, y, theta) return theta, J_history ``` 其中,alpha是学习率,num_iters是迭代次数,J_history记录了每次迭代后的损失函数值。 最后,我们可以使用上述函数来拟合一个简单的线性模型: ```python # 生成随机数据 np.random.seed(0) X = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1) # 对特征矩阵X添加一列全为1的向量,以便于计算截距 X_b = np.c_[np.ones((100, 1)), X] # 初始化参数向量theta theta = np.random.randn(2, 1) # 执行梯度下降算法 alpha = 0.1 num_iters = 1000 theta, J_history = gradient_descent(X_b, y, theta, alpha, num_iters) # 绘制拟合直线 plt.scatter(X, y) plt.plot(X, X_b.dot(theta), 'r') plt.show() ``` 这里我们生成了一个简单的一维数据集,然后对其进行线性回归拟合并绘制出拟合直线。 完整代码如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值