一、矩阵分解回顾
在博文推荐算法——基于矩阵分解的推荐算法中,提到了将用户-商品矩阵进行分解,从而实现对未打分项进行打分。矩阵分解是指将一个矩阵分解成两个或者多个矩阵的乘积。对于上述的用户-商品矩阵(评分矩阵),记为
Vm×n
,可以将其分解成两个或者多个矩阵的乘积,假设分解成两个矩阵
Wm×k
和
Hk×n
,我们要使得矩阵
Wm×k
和
Hk×n
的乘积能够还原原始的矩阵
Vm×n
:
Vm×n≈Wm×k×Hk×n=V̂ m×n
其中,矩阵
Wm×k
表示的是
m
个用户与
k
个主题之间的关系,而矩阵
Hk×n
表示的是
k
个主题与
n
个商品之间的关系。
通常在用户对商品进行打分的过程中,打分是非负的,这就要求:
Wm×k⩾0
Hk×n⩾0
这便是非负矩阵分解(Non-negtive Matrix Factorization, NMF)的来源。
二、非负矩阵分解
2.1、非负矩阵分解的形式化定义
上面简单介绍了非负矩阵分解的基本含义,简单来讲,非负矩阵分解是在矩阵分解的基础上对分解完成的矩阵加上非负的限制条件,即对于用户-商品矩阵
Vm×n
,找到两个矩阵
Wm×k
和
Hk×n
,使得:
Vm×n≈Wm×k×Hk×n=V̂ m×n
同时要求:
Wm×k⩾0
Hk×n⩾0
2.2、损失函数
为了能够定量的比较矩阵
Vm×n
和矩阵
V̂ m×n
的近似程度,在参考文献1中作者提出了两种损失函数的定义方式:
∥A−B∥2=∑i,j(Ai,j−Bi,j)2
D(A∥B)=∑i,j(Ai,jlogAi,jBi,j−Ai,j+Bi,j)
在KL散度的定义中,
D(A∥B)⩾0
,当且仅当
A=B
时取得等号。
当定义好损失函数后,需要求解的问题就变成了如下的形式,对应于不同的损失函数:
求解如下的最小化问题:
-
minimize∥V−WH∥2s.t.W⩾0,H⩾0
-
minimizeD(V∥WH)s.t.W⩾0,H⩾0
2.3、优化问题的求解
在参考文献1中,作者提出了乘法更新规则(multiplicative update rules),具体的操作如下:
对于平方距离的损失函数:
Wi,k=Wi,k(VHT)i,k(WHHT)i,k
Hk,j=Hk,j(WTV)k,j(WTWH)k,j
对于KL散度的损失函数:
Wi,k=Wi,k∑uHk,uVi,u/(WH)i,u∑vHk,v
Hk,j=Hk,j∑uWu,kVu,j/(WH)u,j)∑vWv,k
上述的乘法规则主要是为了在计算的过程中保证非负,而基于梯度下降的方法中,加减运算无法保证非负,其实上述的乘法更新规则与基于梯度下降的算法是等价的,下面以平方距离为损失函数说明上述过程的等价性:
平方损失函数可以写成:
l=∑i=1m∑j=1n[Vi,j−(∑k=1rWi,k⋅Hk,j)]2
使用损失函数对
Hk,j
求偏导数:
∂l∂Hk,j=∑i=1m∑j=1n[2(Vi,j−(∑k=1rWi,k⋅Hk,j))⋅(−Wi,k)]=−2[(WTV)k,j−(WTWH)k,j]
则按照梯度下降法的思路:
Hk,j=Hk,j−ηk,j∂l∂Hk,j
即为:
Hk,j=Hk,j+ηk,j[(WTV)k,j−(WTWH)k,j]
令
ηk,j=Hk,j(WTWH)k,j
,即可以得到上述的乘法更新规则的形式。
2.4、非负矩阵分解的实现
对于如下的矩阵:
通过非负矩阵分解,得到如下的两个矩阵:
对原始矩阵的还原为:
实现的代码
from numpy import *
def load_data(file_path):
f = open(file_path)
V = []
for line in f.readlines():
lines = line.strip().split("\t")
data = []
for x in lines:
data.append(float(x))
V.append(data)
return mat(V)
def train(V, r, k, e):
m, n = shape(V)
W = mat(random.random((m, r)))
H = mat(random.random((r, n)))
for x in xrange(k):
V_pre = W * H
E = V - V_pre
err = 0.0
for i in xrange(m):
for j in xrange(n):
err += E[i,j] * E[i,j]
print err
if err < e:
break
a = W.T * V
b = W.T * W * H
for i_1 in xrange(r):
for j_1 in xrange(n):
if b[i_1,j_1] != 0:
H[i_1,j_1] = H[i_1,j_1] * a[i_1,j_1] / b[i_1,j_1]
c = V * H.T
d = W * H * H.T
for i_2 in xrange(m):
for j_2 in xrange(r):
if d[i_2, j_2] != 0:
W[i_2,j_2] = W[i_2,j_2] * c[i_2,j_2] / d[i_2, j_2]
return W,H
if __name__ == "__main__":
file_path = "./data1"
V = load_data(file_path)
W, H = train(V, 2, 100, 1e-5 )
print V
print W
print H
print W * H
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
收敛曲线如下图所示:
'''
Date:20160411
@author: zhaozhiyong
'''
from pylab import *
from numpy import *
data = []
f = open("result_nmf")
for line in f.readlines():
lines = line.strip()
data.append(lines)
n = len(data)
x = range(n)
plot(x, data, color='r',linewidth=3)
plt.title('Convergence curve')
plt.xlabel('generation')
plt.ylabel('loss')
show()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
参考文献