我正在尝试使用Kullback-Liebler散度作为相似性度量来实现非负矩阵分解。该算法在:http://hebb.mit.edu/people/seung/papers/nmfconverge.pdf中描述。下面是我的python / numpy实现,带有一个运行它的示例矩阵。
简而言之,该算法应该学习矩阵W(n乘以r)和H(r乘以m),使得V(n乘以m)近似为WH。你从W和H中的随机值开始,并按照Seung和Lee论文中描述的更新规则,你应该越来越接近W和H的良好近似值。
该算法被证明可以单调地减少分歧度量,但这不是我的实现中发生的事情。相反,它稳定在两个发散值之间的交替。如果你看一下W和H,你会发现结果分解不是特别好。
我想知道在计算W的更新时是使用更新的还是旧的H.我尝试了两种方式,并没有改变实现的行为。
我已经多次检查了我对文件的执行情况,而且我看不出我做错了什么。任何人都可以对这个问题有所了解吗?
import numpy as np
def update(V, W, H, r, n, m):
n,m = V.shape
WH = W.dot(H)
# equation (5)
H_coeff = np.zeros(H.shape)
for a in range(r):
for mu in range(m):
for i in range(n):
H_coeff[a, mu] += W[i, a] * V[i, mu] / WH[i, mu]
H_coeff[a, mu] /= sum(W)[a]
H = H * H_coeff
W_coeff = np.zeros(W.shape)
for i in range(n):
for a in range(r):
for mu in range(m):
W_coeff[i, a] += H[a, mu] * V[i, mu] / WH[i, mu]
W_coeff[i, a] /= sum(H.T)[a]
W = W * W_coeff
return W, H
def factor(V, r, iterations=100):
n, m = V.shape
avg_V = sum(sum(V))/n/m
W = np.random.random(n*r).reshape(n,r)*avg_V
H = np.random.random(r*m).reshape(r,m)*avg_V
for i in range(iterations):
WH = W.dot(H)
divergence = sum(sum(V * np.log(V/WH) - V + WH)) # equation (3)
print "At iteration " + str(i) + ", the Kullback-Liebler divergence is", divergence
W,H = update(V, W, H, r, n, m)
return W, H
V = np.arange(0.01,1.01,0.01).reshape(10,10)
W, H = factor(V, 6)
答案 0 :(得分:7)
如何消除交替效应:
定理证明2的最后一行读出,
通过反转H和W的角色,W的更新规则可以类似 被证明是非增加的。
因此,我们可以推测,更新H
可以独立于更新W
而完成。这意味着在更新H
之后:
H = H * H_coeff
我们还应该在更新WH
之前更新中间值W
:
WH = W.dot(H)
W = W * W_coeff
这两项更新都会减少分歧。
尝试一下:在计算WH = W.dot(H)
之前粘贴W_coeff
,交替效果消失。
简化代码:
在处理NumPy数组时,请使用他们的mean
和sum
方法,并避免使用Python sum
函数:
avg_V = sum(sum(V))/n/m
可以写成
avg_V = V.mean()
和
divergence = sum(sum(V * np.log(V/WH) - V + WH)) # equation (3)
可以写成
divergence = ((V * np.log(V_over_WH)) - V + WH).sum()
避免使用Python内置sum
函数,因为
sum
方法慢,sum
方法那样多才多艺。 (它
不允许您指定要求和的轴。我们设法通过一次调用NumPy的sum
或sum
方法来消除对Python mean
的两次调用。)消除三重for循环:
但是,通过替换
,可以在速度和可读性方面取得更大的进步H_coeff = np.zeros(H.shape)
for a in range(r):
for mu in range(m):
for i in range(n):
H_coeff[a, mu] += W[i, a] * V[i, mu] / WH[i, mu]
H_coeff[a, mu] /= sum(W)[a]
H = H * H_coeff
带
V_over_WH = V/WH
H *= (np.dot(V_over_WH.T, W) / W.sum(axis=0)).T
<强>解释强>
如果查看H
的等式5更新规则,请首先注意V
和(W H)
的索引是相同的。因此,您可以将V / (W H)
替换为
V_over_WH = V/WH
接下来,请注意在分子中我们对索引i求和,这是W
和V_over_WH
中的第一个索引。我们可以表示为矩阵乘法:
np.dot(V_over_WH.T, W).T
分母只是:
W.sum(axis=0).T
如果我们除以分子和分母
(np.dot(V_over_WH.T, W) / W.sum(axis=0)).T
我们得到一个由两个剩余索引(alpha和mu)按顺序索引的矩阵。这与H
的索引相同。因此,我们希望将H乘以该比率元素。完善。默认情况下,NumPy以元素方式乘以数组。
因此,我们可以将H
的整个更新规则表示为
H *= (np.dot(V_over_WH.T, W) / W.sum(axis=0)).T
所以,把它们放在一起:
import numpy as np
np.random.seed(1)
def update(V, W, H, WH, V_over_WH):
# equation (5)
H *= (np.dot(V_over_WH.T, W) / W.sum(axis=0)).T
WH = W.dot(H)
V_over_WH = V / WH
W *= np.dot(V_over_WH, H.T) / H.sum(axis=1)
WH = W.dot(H)
V_over_WH = V / WH
return W, H, WH, V_over_WH
def factor(V, r, iterations=100):
n, m = V.shape
avg_V = V.mean()
W = np.random.random(n * r).reshape(n, r) * avg_V
H = np.random.random(r * m).reshape(r, m) * avg_V
WH = W.dot(H)
V_over_WH = V / WH
for i in range(iterations):
W, H, WH, V_over_WH = update(V, W, H, WH, V_over_WH)
# equation (3)
divergence = ((V * np.log(V_over_WH)) - V + WH).sum()
print("At iteration {i}, the Kullback-Liebler divergence is {d}".format(
i=i, d=divergence))
return W, H
V = np.arange(0.01, 1.01, 0.01).reshape(10, 10)
# V = np.arange(1,101).reshape(10,10).astype('float')
W, H = factor(V, 6)