文章目录
- 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,使得:
原文中R是一个推荐系统中关于user用户和物品item的关系的矩阵,所以P的每一行都表示user和特征的相关度(Strength of Association),而Q的每一行(或的每一列)就表示item和特征的相关度。
这里不用理解他们表示的含义,只需要知道中每个元素
那么预测值和真实值之间的error就等于:
对关于
求导/梯度就等于:
同理关于求导/梯度就等于:
根据梯度更新
所以代码实现就很容易了,只需要提供两个随机初始化矩阵P,Q,以及R,学习率,迭代次数以及一个K用来表示潜在的feature(它相当于限定分解之后的矩阵的大小)
当然,到这里其实已经可以实现代码了,不过原文中还加了正则Regularization用来防止过拟合。
添加了正则项之后的
梯度更新为:
同理:
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()和4x2(
)的矩阵
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
结果如下:
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(())
结果如下:
可以看出经过分解之后的矩阵所包含的信息是不太具有分析价值的,这也是为什么要进行非负矩阵分解(NMF)的原因。
2. 非负矩阵分解(Non-negative Matrix Factorization)
2.1 迭代公式
理论参考至《矩阵分析于应用(第二版》(张贤达 著)第365页, 网上书籍链接
这里的实现是基于365页的“平方Euclidean距离最小化的乘法算法”,其他实现方式不在这里描述。
愿问题就变成了使用典型的平方欧式距离作为代价函数的无约束优化问题(书中R,P,Q分别用X,A,S表示)
说的更好理解一点就是一个关于和
的最小二乘问题,使得分解之后由
构成的
和
更接近。
其梯度下降公式为:
同理:
其中都是学习率。
关键一步,如何把梯度下降变乘法迭代,令:
代入原式,发现前面的原梯度正好可以和后面负号部分
抵消,则两个梯度更新公式变为:
同理:
2.2 代码部分
将迭代改为矩阵形式的乘法算法(365页注释2),而不用对矩阵每个元素遍历(即三层for循环没了):
代码如下:
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)的距离差(,即欧式距离最小化),如果他们的变化都小于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(())
结果如下:
可以发现该NMF算法可以有效分解矩阵提取特征。
本文讲述的是乘法迭代的实现过程,其他方法,比如梯度下降法可以参考另一篇非负矩阵分解(2): 拟牛顿法和其他方法。