Numpy Einsum计算c距离非常慢

Numpy Einsum计算c距离非常慢,numpy,numpy-einsum,Numpy,Numpy Einsum,我通过np.einsum计算马氏距离: np.einsum('nj,jk,nk->n', delta, VI, delta) 其中,协方差矩阵的逆VI为783 x 783,δ为6000 x 783。在我的2016 Macbook Pro上,这一行需要10秒才能执行。我怎样才能让这更快 我必须计算这条线200k到300k次。矢量化可能不是一个选项,因为每个类的VI都不同。不需要Einsum,您可以使用点积和元素积,以及求和: VI = np.random.rand(783, 783) de

我通过np.einsum计算马氏距离:

np.einsum('nj,jk,nk->n', delta, VI, delta)
其中,协方差矩阵的逆VI为
783 x 783
,δ为
6000 x 783
。在我的2016 Macbook Pro上,这一行需要10秒才能执行。我怎样才能让这更快


我必须计算这条线200k到300k次。矢量化可能不是一个选项,因为每个类的VI都不同。

不需要Einsum,您可以使用点积和元素积,以及求和:

VI = np.random.rand(783, 783)
delta = np.random.rand(6000, 783)

%timeit np.einsum('nj,jk,nk->n', delta, VI, delta)
# 7.05 s ± 89.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit np.sum((delta @ VI) * delta, axis=-1)
# 90 ms ± 4.72 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

out_1 = np.einsum('nj,jk,nk->n', delta, VI, delta)
out_2 = np.sum((delta @ VI) * delta, axis=-1)
np.allclose(out_1, out_2)
# True
我是怎么做到的?
nj,jk->nk
是一种网络产品:

tmp_1 = np.einsum('nj,jk->nk', delta, VI)
tmp_2 = delta @ VI
np.allclose(tmp_1, tmp_2)  # True
nk,nk->nk
是一种元素产品:

tmp_3 = np.einsum('nk,nk->nk', tmp_1, delta)
tmp_4 = tmp_2 * delta
np.allclose(tmp_3, tmp_4)  # True
nk->n
是最后一个轴的总和:

tmp_5 = np.einsum('nk->n', tmp_3)
tmp_6 = np.sum(tmp_4, axis=-1)
np.allclose(tmp_5, tmp_6)  # True
矢量化
VI
您会注意到,沿第一个轴进行矢量化
VI
,将只起作用:

矢量化
VI
delta
elementwise 与矢量化
VI
delta
相同,只需在
VI
delta
的开头添加相同数量的元素即可

# Vectorized `VI`
nd_VI = np.random.rand(3, 783, 783)
# Unvectorized `VI`, for comparison
VI = nd_VI[0, ...]
# Vectorized `delta`
nd_delta = np.random.rand(3, 6000, 783)
# Unvectorized `delta`, for comparison
delta = nd_delta[0, ...]

out = np.sum((delta @ VI) * delta, axis=-1)
out.shape
# (6000,)

nd_out = np.sum((nd_delta @ nd_VI) * nd_delta, axis=-1)
nd_out.shape
# (3, 6000)

# Result of vectorized and unvectorized `IV` are the same
np.allclose(out, nd_out[0, ...])
# True
独立地向量化
VI
delta
或者,如果要计算
VI
中每个元素与
delta
中每个可能元素的马氏距离,可以使用广播:

# Vectorized `VI`, note the extra empty dimension (where `delta` has 3)
nd_VI = np.random.rand(4, 1, 783, 783)
# Unvectorized `VI`, for comparison
VI = nd_VI[0, 0, ...]
# Vectorized `delta`, note the extra empty dimension (where `VI` has 4)
nd_delta = np.random.rand(1, 3, 6000, 783)
# Unvectorized `delta`, for comparison
delta = nd_delta[0, 0, ...]

out = np.sum((delta @ VI) * delta, axis=-1)
out.shape
# (6000,)

nd_out = np.sum((nd_delta @ nd_VI) * nd_delta, axis=-1)
nd_out.shape
# (4, 3, 6000)

# Result of vectorized and unvectorized `IV` are the same
np.allclose(out, nd_out[0, 0, ...])
# True

无需Einsum,您可以使用点积和元素积,以及总和:

VI = np.random.rand(783, 783)
delta = np.random.rand(6000, 783)

%timeit np.einsum('nj,jk,nk->n', delta, VI, delta)
# 7.05 s ± 89.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit np.sum((delta @ VI) * delta, axis=-1)
# 90 ms ± 4.72 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

out_1 = np.einsum('nj,jk,nk->n', delta, VI, delta)
out_2 = np.sum((delta @ VI) * delta, axis=-1)
np.allclose(out_1, out_2)
# True
我是怎么做到的?
nj,jk->nk
是一种网络产品:

tmp_1 = np.einsum('nj,jk->nk', delta, VI)
tmp_2 = delta @ VI
np.allclose(tmp_1, tmp_2)  # True
nk,nk->nk
是一种元素产品:

tmp_3 = np.einsum('nk,nk->nk', tmp_1, delta)
tmp_4 = tmp_2 * delta
np.allclose(tmp_3, tmp_4)  # True
nk->n
是最后一个轴的总和:

tmp_5 = np.einsum('nk->n', tmp_3)
tmp_6 = np.sum(tmp_4, axis=-1)
np.allclose(tmp_5, tmp_6)  # True
矢量化
VI
您会注意到,沿第一个轴进行矢量化
VI
,将只起作用:

矢量化
VI
delta
elementwise 与矢量化
VI
delta
相同,只需在
VI
delta
的开头添加相同数量的元素即可

# Vectorized `VI`
nd_VI = np.random.rand(3, 783, 783)
# Unvectorized `VI`, for comparison
VI = nd_VI[0, ...]
# Vectorized `delta`
nd_delta = np.random.rand(3, 6000, 783)
# Unvectorized `delta`, for comparison
delta = nd_delta[0, ...]

out = np.sum((delta @ VI) * delta, axis=-1)
out.shape
# (6000,)

nd_out = np.sum((nd_delta @ nd_VI) * nd_delta, axis=-1)
nd_out.shape
# (3, 6000)

# Result of vectorized and unvectorized `IV` are the same
np.allclose(out, nd_out[0, ...])
# True
独立地向量化
VI
delta
或者,如果要计算
VI
中每个元素与
delta
中每个可能元素的马氏距离,可以使用广播:

# Vectorized `VI`, note the extra empty dimension (where `delta` has 3)
nd_VI = np.random.rand(4, 1, 783, 783)
# Unvectorized `VI`, for comparison
VI = nd_VI[0, 0, ...]
# Vectorized `delta`, note the extra empty dimension (where `VI` has 4)
nd_delta = np.random.rand(1, 3, 6000, 783)
# Unvectorized `delta`, for comparison
delta = nd_delta[0, 0, ...]

out = np.sum((delta @ VI) * delta, axis=-1)
out.shape
# (6000,)

nd_out = np.sum((nd_delta @ nd_VI) * nd_delta, axis=-1)
nd_out.shape
# (4, 3, 6000)

# Result of vectorized and unvectorized `IV` are the same
np.allclose(out, nd_out[0, 0, ...])
# True

使用
optimize
flag?,将
optimize=True
时间从10秒降至400毫秒。当
optimize=True
einsum计算贪婪路径时。当
optimize='optimal'
需要更长的时间,但计算出更好的计算路径(在这种情况下可能相同)。但是,如果数组的维度没有改变并提供
optimize=path
,那么您可以预先计算该
路径,这可能是这里的最佳用例,因此它不需要反复计算相同的内容。参见numpy einsum文档中的最后一个示例。使用
optimize
flag?和
optimize=True
时间从10秒下降到400毫秒。当
optimize=True
einsum计算贪婪路径时。当
optimize='optimal'
需要更长的时间,但计算出更好的计算路径(在这种情况下可能相同)。但是,如果数组的维度没有改变并提供
optimize=path
,那么您可以预先计算该
路径,这可能是这里的最佳用例,因此它不需要反复计算相同的内容。请看numpy einsum文档中的最后一个例子。想一想,我们可以使用
np.einsum('nk,nk->n',delta.dot(VI),delta)
@NilsWerner是nd_VI持有3个不同的协方差矩阵吗?@saad是的,它的形状是3x783x783I,我的意思是
np.einsum('nk,nk->,n',delta.dot(VI),delta)
可能比使用
np.sum(…)更有效一些
version.@Divakar啊好的!我检查过了,它们的速度完全一样。想一想,我们可以用
np.einsum('nk,nk->n',delta.dot(VI),delta)
@NilsWerner是nd_VI持有3个不同的协方差矩阵吗?@saad是的,它的形状是3x783x783我的意思是
np.einsum('nk,nk->,n',delta.dot(VI),delta)
可能比使用
np.sum(…)更有效一些
version.@Divakar啊好的!我查过了,他们的速度完全一样。