NMF算法简介及python实现

基本原理

NMF,非负矩阵分解,它的目标很明确,就是将大矩阵分解成两个小矩阵,使得这两个小矩阵相乘后能够还原到大矩阵。而非负表示分解的矩阵都不包含负值。 从应用的角度来说,矩阵分解能够用于发现两种实体间的潜在特征,一个最常见的应用就是协同过滤中的预测打分值,而从协同过滤的这个角度来说,非负也很容易理解:打分都是正的,不会出现负值。

在例如Netflix或MovieLens这样的推荐系统中,有用户和电影两个集合。给出每个用户对部分电影的打分,我们希望预测该用户对其他没看过电影的打分值,这样可以根据打分值为其做出推荐。用户和电影的关系,可以用一个矩阵来表示,每一行表示用户,每一列表示电影,每个元素的值表示用户对已经看过的电影的打分,矩阵看起来如下:

D1D2D3D4
U153-1
U24--1
U311-5
U41--4
U5-154

而使用矩阵分解来预测评分的思想来源于,我们可以通过矩阵分解来发现一些用户打分的潜在特征。比如两个人都喜欢某一演员,那他们就倾向于给TA演的电影打高分;或者两个人都喜欢动作片。假如我们能够发现这些特征,我们就能够预测特定用户对特定电影的打分。

为了发现不同的特征,我们假设特征的数量少于用户和电影的数量(要是每个用户都有一个独立特征,那代价也太大啦)。

数学基础

首先,我们定义U为用户的集合,D为电影的集合,R = U * D,为评分的集合。假设我们需要寻找K个特征,则我们的目标是,找到两个矩阵P和Q,使得它们相乘近似等于R。即:


这样P的每一行表示用户,每一列表示一个特征,它们的值表示用户与某一特征的相关性,值越大,表明特征越明显。同理,Q的每一行表示电影,每一列表示电影与特征的关联。最后为了预测用户ui对特定电影dj的评分,我们可以直接计算ui和dj对应的特征向量的点积,即:


现在我们就来计算P和Q。最简单的方法就是梯度下降,该方法先初始化P和Q为特定的值,计算它们的乘积与真实矩阵的误差,然后通过迭代,逐渐减小误差直至收敛。

由于误差可大可小,这里使用平方根误差(squared error)来计算,计算公式如下:


即循环地计算每一条目的误差,最后相加。

为了最小化误差,我们需要知道怎么改变Pik和Qkj的值(在梯度下降中表现为下降的方向)。我们对这个公式求偏微分,即得:


计算出梯度之后,我们逐步更新Pik和Qkj:


上面公式中,为梯度下降常数,通常取一个较小的值(防止无法收敛),如0.0002。

有人可能会问一个问题:假如我们计算出P和Q,使得P*Q近似等于R,那么那些未评分的不全是0了么?首先,我们并不要求P*Q精确等于R;其次,我们输入的数据是所有已评分的数据(或它的子集),即训练集,而并不包含未评分的数据。因此,它能够对未评分的做出不等于0的预测。

通过上面的更新规则,我们就可以逐步减小误差,直至收敛:


规范化

上面的算法只是最简单的一个实现,实际使用中可能复杂得多。一个最常见的修改就是引入规范化,以防止过度拟合。这通过加入另外一个参数来修改误差公式:


参数用来控制用户特征向量与条目特征向量的比例,以避免出现特征向量中出现特别大的值。实际应用中,通常设置为0~0.02之间的值。因此更新公式变成:


一个简单的python实现如下(需要安装numpy)

<span style="font-size:14px;">import numpy
 
def matrix_factorisation(R, P, Q, K, steps=5000, alpha=0.0002, beta=0.02):
    Q = Q.T
    for step in range(steps):
        for i in range(len(R)):
            for j in range(len(R[i])):
                if R[i][j] > 0:
                    eij = R[i][j] - numpy.dot(P[i,:],Q[:,j])
                    for k in range(K):
                        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])
        eR = numpy.dot(P,Q)
        e = 0
        for i in range(len(R)):
            for j in range(len(R[i])):
                if R[i][j] > 0:
                    e = e + pow(R[i][j] - numpy.dot(P[i,:],Q[:,j]), 2)
                    for k in range(K):
                        e = e + (beta/2) * (pow(P[i][k],2) + pow(Q[k][j],2))
        if e < 0.001:
            break
    return P, Q.T</span>

使用示例如下:

<span style="font-size:14px;">R = [
     [5,3,0,1],
     [4,0,0,1],
     [1,1,0,5],
     [1,0,0,4],
     [0,1,5,4],
    ]
 
R = numpy.array(R)
 
N = len(R)
M = len(R[0])
K = 2
 
P = numpy.random.rand(N,K)
Q = numpy.random.rand(M,K)
 
nP, nQ = matrix_factorisation(R, P, Q, K)
nR = numpy.dot(nP, nQ.T)
print(nR)</span>

最后P*Q还原出的矩阵如下:

D1D2D3D4
U14.972.982.180.98
U23.972.401.970.99
U31.020.935.324.93
U41.000.854.593.93
U51.361.074.894.12

可以看到,还原后的矩阵跟原矩阵很接近,并且对原来空缺的值作出了预测。在这个例子中,我们可以看到U1和U2口味比较接近,他们都喜欢D1和D2。而其他的用户则喜欢D3,D4和D5。

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
NMF算法非负矩阵分解)是一种常用的数据降维方法,可以将一个大矩阵分解为两个小矩阵的乘积,其中这两个小矩阵中的元素都是非负的。NMF算法的一个应用是图像压缩,可以将一张彩色图片分解为三个非负矩阵,分别表示红、绿、蓝三个通道的像素值。 以下是NMF算法Python实现: ```python import numpy as np def nmf(X, k, max_iter=1000, tol=1e-6): """ 非负矩阵分解 :param X: 待分解矩阵,shape为(m, n) :param k: 分解后矩阵的秩 :param max_iter: 最大迭代次数 :param tol: 收敛阈值 :return: 分解后的矩阵W和H """ m, n = X.shape # 随机初始化W和H W = np.random.rand(m, k) H = np.random.rand(k, n) # 迭代更新W和H for i in range(max_iter): # 更新H numerator = np.dot(W.T, X) denominator = np.dot(np.dot(W.T, W), H) + 1e-9 # 避免除0错误 H *= numerator / denominator # 更新W numerator = np.dot(X, H.T) denominator = np.dot(W, np.dot(H, H.T)) + 1e-9 # 避免除0错误 W *= numerator / denominator # 计算误差 err = np.sum((X - np.dot(W, H)) ** 2) if err < tol: break return W, H ``` 使用示例: ```python import matplotlib.pyplot as plt from sklearn.datasets import load_digits # 加载手写数字数据集 digits = load_digits() X = digits.data # 非负矩阵分解 W, H = nmf(X.T, k=30) # 显示分解后的结果 plt.figure(figsize=(10, 4)) for i in range(30): plt.subplot(3, 10, i + 1) plt.imshow(W[:, i].reshape(8, 8), cmap='gray') plt.axis('off') plt.show() ``` 上述示例代码中,使用sklearn.datasets模块加载手写数字数据集,并对其进行非负矩阵分解,得到分解后的矩阵W和H。最后将W中的每一列作为一张图片进行展示。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值