非负矩阵分解NMF(2): 拟牛顿法及其他方法

写在前面

在之前的一篇文章中非负矩阵分解(1) 已经介绍了NMF的基本概念,数学基础以及代码实现,这里不再对NMF基本概念做更多讲解,建议先看完(1)再看这篇。

实现非负矩阵分解主要两种方法,一个是梯度下降法,一个是乘法迭代法,乘法迭代可以看作是梯度下降的一种变形。文章(1)中使用的就是乘法迭代,而这一篇的重点则是梯度下降

拟牛顿法(Quasi-Newton method)

公式推导

理论参考至《矩阵分析于应用(第二版》(张贤达 著)第373页。

无约束优化问题: m i n D E ( R ∣ ∣ P Q ) = 1 2 ∣ ∣ R − P Q ∣ ∣ 2 minD_E(R||PQ)=\frac{1}{2}||R-PQ||^2 minDE(RPQ)=21RPQ2

代价函数 D E ( R ∣ ∣ P Q ) D_E(R||PQ) DE(RPQ) 的黑塞(Hessian)矩阵 H A = ∇ P 2 ( D E ) = I I × I ⊗ Q Q T ∈ R I K × I K H_A=\nabla^2_P(D_E)=I_{I \times I}\otimes QQ^T\in R^{IK \times IK} HA=P2(DE)=II×IQQTRIK×IK 是一个分块对角矩阵,对角线上的块矩阵为 Q Q T QQ^T QQT

于是梯度下降更新法则为:

P = P − ∇ P ( D E ( R ∣ ∣ P Q ) ) H P − 1 P = P-\nabla_P(D_E(R||PQ))H_P^{-1} P=PP(DE(RPQ))HP1

以及

Q = Q − ∇ Q ( D E ( R ∣ ∣ P Q ) ) H Q − 1 Q = Q-\nabla_Q(D_E(R||PQ))H_Q^{-1} Q=QQ(DE(RPQ))HQ1

其中 ∇ P ( D E ( R ∣ ∣ P Q ) ) = ( P Q − R ) Q T \nabla_P(D_E(R||PQ))=(PQ-R)Q^T P(DE(RPQ))=(PQR)QT

可得:

P = P − ( P Q − R ) Q T ( Q Q T ) − 1 P = P-(PQ-R)Q^T(QQ^T)^{-1} P=P(PQR)QT(QQT)1

以及

Q = Q − ( P Q − R ) P T ( P P T ) − 1 Q = Q-(PQ-R)P^T(PP^T)^{-1} Q=Q(PQR)PT(PPT)1

为了防止矩阵 Q Q T QQ^T QQT奇异或者条件数很大,可以采用松弛法。于是最终更新公式为:

P = P − ( P Q − R ) Q T ( Q Q T + λ I K × K ) − 1 P=P-(PQ-R)Q^T(QQ^T+\lambda I_{K\times K})^{-1} P=P(PQR)QT(QQT+λIK×K)1

以及

Q = Q − ( P Q − R ) P T ( P P T + λ I K × K ) − 1 Q=Q-(PQ-R)P^T(PP^T+\lambda I_{K\times K})^{-1} Q=Q(PQR)PT(PPT+λIK×K)1

其中 I K × K I_{K \times K} IK×K 就是一个对角线全为1,大小为 K × K K \times K K×K的矩阵。

代码实现

导入package

!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 

导入data

faces = fetch_olivetti_faces()
data_faces = faces.data
images_faces = faces['images']
number_of_train = 100
images_faces_train = images_faces[:number_of_train,:,:]

data_faces_train = data_faces[:number_of_train,:]

n_samples = len(images_faces_train)
image_shape = images_faces_train[0].shape

注意这里和文章(1)的不同是没有reshape图像成(16,16)的格式,而是直接使用原图像(64,64)

查看数据

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

显示效果如下
在这里插入图片描述

算法实现
注意代码中的Q=公式推导中的Q.T。

P的大小是(N,K), Q的大小是(M,K)

def newton(R, P, Q, lamb, steps=5000):
    
    
    for step in range(steps):
        Pu = P-(P@Q.T-R)@Q@np.linalg.inv(Q.T@Q+lamb*np.eye(K))
        Qu = Q-(Pu@Q.T-R).T@Pu@np.linalg.inv(Pu.T@Pu+lamb*np.eye(K))
        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

测试,这里的 λ \lambda λ 设置的0.001

N = len(R)
M = len(R[0])
K = 2
rng = np.random.RandomState(1)
P = rng.rand(N,K)
Q = rng.rand(M,K)

P_estimate, Q_estimate = newton(R, P, Q, 0.001) 

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(64,64), cmap=plt.cm.gray)
    plt.xticks(())
    plt.yticks(())

结果
在这里插入图片描述
可以发现,这收敛速度非常快,只需6步即可完成,而文章(1)中则慢很多。

查看重组后的矩阵 R ^ \hat{R} R^中的图像

plt.imshow((P_estimate@Q_estimate.T)[0].reshape((64,64)), cmap=plt.cm.gray)

结果
在这里插入图片描述

其他方法

在《矩阵分析于应用(第二版》(张贤达 著)第372页还介绍了交替非负最小二乘算法(alternating nonnegative least squares, ANLS)

中间的公式推导不再赘述,其最终得到的是加上了约束项的乘法迭代,跟文章(1)的方式较为相似。

公式推导

更新公式:

P i k = P i k [ R Q T ] i k [ P Q Q T + α P ] i k P_{ik}=P_{ik}\frac{[RQ^T]_{ik}}{[PQQ^T+\alpha P]_{ik}} Pik=Pik[PQQT+αP]ik[RQT]ik

以及

Q k j = Q k j [ P T R ] k j [ P T P Q + β Q ] k j Q_{kj}=Q_{kj}\frac{[P^TR]_{kj}}{[P^TPQ+\beta Q]_{kj}} Qkj=Qkj[PTPQ+βQ]kj[PTR]kj

代码实现
def ALS(R, P, Q, alpha=0.001, beta=0.001, steps=5000):

    for step in range(steps):
        

        Pu = P*(R.dot(Q))/(P.dot(Q.T).dot(Q)+alpha*P)+1e-7
        Qu = (Q.T*(Pu.T.dot(R))/(Pu.T.dot(Pu).dot(Q.T))+beta*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

书中还提到过一些不错的方法,比如KL散度,AB散度等,由于水平有限没有写出code。想要对算法进行修改和优化,还可以从修改目标函数(Object function)入手。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值