我正在尝试将上述公式实现为矢量化形式。
K=3
此处X
为150x4
numpy数组。 mu
是3x4
numpy数组。 Gamma
是一个150x3
numpy数组。 Sigma
是一个kx4x4
numpy数组。因此Sigma[k]
是一个4x4
numpy数组。 N=150
N_k = np.sum(Gamma, axis=0)
for k in range(K): # Correct
x_new = X - mu[k] #Correct
a = np.dot(x_new.T, x_new) #Incorrect from here I feel
for i in range(len(data)):
sigma[k] = Gamma[i][k] * a
sigma[k]=sigma[k]/N_k #totally incorrect
如何解决这个问题?
答案 0 :(得分:8)
产品总和?听起来像是np.einsum的工作:
import numpy as np
N = 150
K = 3
M = 4
x = np.random.random((N,M))
mu = np.random.random((K,M))
gamma = np.random.random((N,K))
xbar = x-mu[:,None,:] # shape (3, 150, 4)
sigma = np.einsum('nk,knm,kno->kmo', gamma, xbar, xbar)
sigma /= gamma.sum(axis=0)[:,None,None]
解码'nk,knm,kno->kmo'
:
此下标规范在数组左侧有三个组件(->
),后面是右侧的一个组件。
左侧的三个组件对应gamma
,xbar
和xbar
的下标,操作数将传递给np.einsum
。
gamma
有下标nk
,就像您发布的公式一样。
xbar
有形状(3,150,4)。您可以将其视为具有下标knm
,其中k
和n
具有与您发布的公式中相同的含义,m
是表示轴的下标长度4,未在公式中明确提及,但显然已经给出了对数组形状的描述。
现在第三个下标组件是kno
。使用o
下标是因为o
扮演的角色与m
下标相同,但我们不希望求m
。实际上,我们希望m
和o
下标可以独立迭代,而不是锁定步骤。因此我们给第三个下标不同的字母。
请注意n
出现在左侧的下标(nk, knm, kno
)中,但未出现在右侧(kmo
中)。这告诉np.einsum
总结n
。
k
出现在左侧的下标和右侧的下标中。这告诉np.einsum
我们希望在锁定步骤中推进k
下标,但是(因为它出现在右侧)我们不想总结k
。
由于kmo
出现在右侧,因此这些下标仍保留在结果中。这导致sigma
具有形状(K,M,M)
(即(3,4,4))。
答案 1 :(得分:2)
在unubtu的最佳答案之上只有几个性能指标。
np.einsum
无法使用两个以上的参数优化调用。只要有可能,通常将计算手动拆分为两个参数组就会更快,例如:
def unubtu():
xbar = x-mu[:,None,:] # shape (3, 150, 4)
sigma = np.einsum('nk,knm,kno->kmo', gamma, xbar, xbar)
sigma /= gamma.sum(axis=0)[:,None,None]
return sigma
def faster():
xbar = x-mu[:,None,:] # shape (3, 150, 4)
sigma = np.einsum('knm,kno->kmo', gamma.T[..., None] * xbar, xbar)
sigma /= gamma.sum(axis=0)[:,None,None]
return sigma
In [50]: %timeit unubtu()
10000 loops, best of 3: 147 µs per loop
In [51]: %timeit faster()
10000 loops, best of 3: 129 µs per loop
12%的改善并不多,但是对于更大的阵列,差异会更大(更大)。
此外,尽管np.einsum
是一个很好的工具,但这使得非常简单的事情变得非常简单,如果你的numpy是用一个好的线性代数库构建的,它绝不像np.dot
那样优化。在您的情况下,假设K
很小,使用np.dot
和Python循环甚至更快:
def even_faster():
sigma = np.empty((K, M, M))
for k in xrange(K):
x_ = x - mu[k]
sigma[k] = np.dot((x_ * gamma[:, k, None]).T, x_)
sigma /= gamma.sum(axis=0)[:,None,None]
return sigma
In [52]: %timeit even_faster()
10000 loops, best of 3: 101 µs per loop