文章目录

  • 1. 矩阵分解(Matrix Factorization):
  • 1.1 公式推导
  • 1.2 代码实现
  • 1.3 在图像数据下的效果
  • 2. 非负矩阵分解(Non-negative Matrix Factorization)
  • 2.1 迭代公式
  • 2.2 代码部分
  • 2.3 在图像数据下的效果



在实现NMF(Non-negative Matrix Factorization)之前,先看普通的MF是怎么进行的,从而可以直观地理解为什么一定要非负矩阵分解。

1. 矩阵分解(Matrix Factorization):
1.1 公式推导

该方法由Albert Au Yeung 提供: 原文链接

首先矩阵分解的目的在于将大小为N x M的矩阵R分解成一个大小为N x K的矩阵P以及K x M的矩阵Q,使得:

python 非负张量分解算法 python非负矩阵分解_算法

原文中R是一个推荐系统中关于user用户和物品item的关系的矩阵,所以P的每一行都表示user和特征的相关度(Strength of Association),而Q的每一行(或python 非负张量分解算法 python非负矩阵分解_算法_02的每一列)就表示item和特征的相关度。

这里不用理解他们表示的含义,只需要知道python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_03中每个元素python 非负张量分解算法 python非负矩阵分解_矩阵_04

那么预测值和真实值之间的error就等于:
python 非负张量分解算法 python非负矩阵分解_算法_05

python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_06关于python 非负张量分解算法 python非负矩阵分解_矩阵_07求导/梯度就等于:

python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_08

同理关于python 非负张量分解算法 python非负矩阵分解_机器学习_09求导/梯度就等于:
python 非负张量分解算法 python非负矩阵分解_矩阵_10

根据梯度更新python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_11

python 非负张量分解算法 python非负矩阵分解_python_12

python 非负张量分解算法 python非负矩阵分解_python_13

所以代码实现就很容易了,只需要提供两个随机初始化矩阵P,Q,以及R,学习率python 非负张量分解算法 python非负矩阵分解_算法_14,迭代次数以及一个K用来表示潜在的feature(它相当于限定分解之后的矩阵的大小)

当然,到这里其实已经可以实现代码了,不过原文中还加了正则Regularization用来防止过拟合。

添加了正则项之后的

python 非负张量分解算法 python非负矩阵分解_机器学习_15

梯度更新为:

python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_16

同理:
python 非负张量分解算法 python非负矩阵分解_算法_17

1.2 代码实现

python代码如下,和原文链接中代码有所不同

def matrix_factorization(R, P, Q, K, steps=500, 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: #计算error
                    eij = R[i][j] - np.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])
    return P, Q.T

拿一个矩阵试验一下:

R = np.array([
    [5, 3, 0, 1],
    [4, 0, 0, 1],
    [1, 1, 0, 5],
    [1, 0, 0, 4],
    [0, 1, 5, 4],
])

N = len(R)
M = len(R[0])
K = 2

# initialize 
rng = np.random.RandomState(1)
P = rng.rand(N,K)
Q = rng.rand(M,K)

P_estimate, Q_estimate = matrix_factorization(R, P, Q, K)
print(P_estimate,"\n")
print(Q_estimate)

结果就不展示了,会生成一个5x2(python 非负张量分解算法 python非负矩阵分解_算法_18)和4x2(python 非负张量分解算法 python非负矩阵分解_算法_02)的矩阵

1.3 在图像数据下的效果

建议在colab下运行,本地可能会有skimage报错问题。。

#导入相应的包
!pip install update scikit-image

import numpy as np
from sklearn.datasets import fetch_olivetti_faces
import matplotlib.pyplot as plt
from time import time
from skimage.transform import resize 
#导入图像数据,具体过程可以暂时不用理解
faces = fetch_olivetti_faces()
data_faces = faces.data
images_faces = faces['images']
number_of_train = 100
images_faces_train = images_faces[:number_of_train,:,:]
images_faces_train = np.transpose(resize(np.transpose(images_faces_train,[1,2,0]),[16,16]),[2,0,1])
data_faces_train = data_faces[:number_of_train,:]
data_faces_train = np.transpose(resize(np.transpose(data_faces_train.reshape(number_of_train,64,64),[1,2,0]),[16,16]),[2,0,1])
data_faces_train = data_faces_train.reshape(number_of_train,-1)
n_samples = len(images_faces_train)
image_shape = images_faces_train[0].shape
#查看一下图片
data_faces_centered = data_faces_train - data_faces_train.mean(axis=0)

# local centering
data_faces_centered -= data_faces_centered.mean(axis=1).reshape(n_samples, -1)

# Let's show some centered faces
plt.figure(figsize=(20, 2))
plt.suptitle("Centered Olivetti Faces", size=16)
for i in range(10):
    plt.subplot(1, 10, i+1)
    plt.imshow(data_faces_centered[i].reshape(image_shape), cmap=plt.cm.gray)
    plt.xticks(())
    plt.yticks(())
    
R = data_faces_centered

结果如下:

python 非负张量分解算法 python非负矩阵分解_矩阵_20


resize的作用可以使图像变模糊,使得后续的MF算法执行更快

测试MF代码:

N = len(R)
M = len(R[0])
K = 2
#图像太清晰了打印有点久
# initialize 
rng = np.random.RandomState(1)
P = rng.rand(N,K)
Q = rng.rand(M,K)

P_estimate, Q_estimate = matrix_factorization(R, P, Q, K)

#show the learned basis
plt.figure(figsize=(6, 3))
plt.suptitle("learned basises from MF", size=16)
for i in range(K):
    plt.subplot(1, K, i+1)
    plt.imshow(Q[:,i].reshape(16,16), cmap=plt.cm.gray)
    plt.xticks(())
    plt.yticks(())

结果如下:

python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_21

可以看出经过分解之后的矩阵所包含的信息是不太具有分析价值的,这也是为什么要进行非负矩阵分解(NMF)的原因。

2. 非负矩阵分解(Non-negative Matrix Factorization)
2.1 迭代公式

理论参考至《矩阵分析于应用(第二版》(张贤达 著)第365页, 网上书籍链接

这里的实现是基于365页的“平方Euclidean距离最小化的乘法算法”,其他实现方式不在这里描述。

愿问题就变成了使用典型的平方欧式距离作为代价函数的无约束优化问题python 非负张量分解算法 python非负矩阵分解_python_22(书中R,P,Q分别用X,A,S表示)

说的更好理解一点就是一个关于python 非负张量分解算法 python非负矩阵分解_机器学习_23python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_03的最小二乘问题,使得分解之后由python 非负张量分解算法 python非负矩阵分解_矩阵_25构成的python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_03python 非负张量分解算法 python非负矩阵分解_机器学习_23更接近。

其梯度下降公式为:
python 非负张量分解算法 python非负矩阵分解_python 非负张量分解算法_28

同理:
python 非负张量分解算法 python非负矩阵分解_算法_29

其中python 非负张量分解算法 python非负矩阵分解_矩阵_30都是学习率。

关键一步,如何把梯度下降变乘法迭代,令:

python 非负张量分解算法 python非负矩阵分解_机器学习_31

代入原式,发现前面的原梯度python 非负张量分解算法 python非负矩阵分解_矩阵_07正好可以和后面负号部分python 非负张量分解算法 python非负矩阵分解_算法_33抵消,则两个梯度更新公式变为:

python 非负张量分解算法 python非负矩阵分解_机器学习_34

同理:

python 非负张量分解算法 python非负矩阵分解_python_35

2.2 代码部分

将迭代改为矩阵形式的乘法算法(365页注释2),而不用对矩阵每个元素遍历(即三层for循环没了):

python 非负张量分解算法 python非负矩阵分解_算法_36

python 非负张量分解算法 python非负矩阵分解_机器学习_37

代码如下:

def mf_multiplicative_update(R, P, Q, steps=5000):
   
    for step in range(steps):
        
        Pu = P*(R.dot(Q))/(P.dot(Q.T).dot(Q))+1e-7
        Qu = (Q.T*(Pu.T.dot(R))/(Pu.T.dot(Pu).dot(Q.T))).T+1e-7
        
        e_P = np.sqrt(np.sum((Pu-P)**2, axis=(0,1)))/P.size
        e_Q = np.sqrt(np.sum((Qu-Q)**2, axis=(0,1)))/Q.size
        if e_P<0.001 and e_Q<0.001:
            print("step is:",step)
            break
        P = Pu
        Q = Qu
    return P, Q

在P,Q更新的时候加入1e-7防止分母在下次迭代中变的很小,其中e_P和e_Q分别都是算上一次的P和这次的P(Pu)以及上次的Q和这次的Q(Qu)的距离差(python 非负张量分解算法 python非负矩阵分解_机器学习_38,即欧式距离最小化),如果他们的变化都小于0.001了即已经达到(接近)一个极值点,可以终止迭代。

2.3 在图像数据下的效果
rng = np.random.RandomState(1)
P = rng.rand(N,K)
Q = rng.rand(M,K)

P_estimate, Q_estimate = mf_multiplicative_update(R, P, Q)
#print(P_estimate.dot(Q_estimate.T))
plt.figure(figsize=(6, 3))
plt.suptitle("learned basises from NMF", size=16)
for i in range(K):
    plt.subplot(1, K, i+1)
    plt.imshow(Q_estimate[:,i].reshape(16,16), cmap=plt.cm.gray)
    plt.xticks(())
    plt.yticks(())

结果如下:

python 非负张量分解算法 python非负矩阵分解_算法_39


可以发现该NMF算法可以有效分解矩阵提取特征。

本文讲述的是乘法迭代的实现过程,其他方法,比如梯度下降法可以参考另一篇非负矩阵分解(2): 拟牛顿法和其他方法。