【问题标题】:How can I vectorize this triple-loop over 2d arrays in numpy?如何在 numpy 中对二维数组上的这个三重循环进行矢量化?
【发布时间】:2012-07-01 04:54:03
【问题描述】:

我能否消除此计算中的所有 Python 循环:

result[i,j,k] = (x[i] * y[j] * z[k]).sum()

其中x[i]y[j]z[k] 是长度为Nxyz 的向量具有长度为AB、@9876543 的第一个维度英石输出是形状(A,B,C),每个元素都是 三重乘积的总和(按元素)。

我可以将它从 3 个循环减少到 1 个循环(下面的代码),但我一直在尝试 消除最后一个循环。

如有必要,我可以制作A=B=C(通过少量填充)。

# Example with 3 loops, 2 loops, 1 loop (testing omitted)

N = 100 # more like 100k in real problem
A =   2 # more like 20 in real problem
B =   3 # more like 20 in real problem
C =   4 # more like 20 in real problem

import numpy
x = numpy.random.rand(A, N)
y = numpy.random.rand(B, N)
z = numpy.random.rand(C, N)

# outputs of each variant
result_slow = numpy.empty((A,B,C))
result_vec_C = numpy.empty((A,B,C))
result_vec_CB = numpy.empty((A,B,C))

# 3 nested loops
for i in range(A):
    for j in range(B):
        for k in range(C):
            result_slow[i,j,k] = (x[i] * y[j] * z[k]).sum()

# vectorize loop over C (2 nested loops)
for i in range(A):
    for j in range(B):
        result_vec_C[i,j,:] = (x[i] * y[j] * z).sum(axis=1)

# vectorize one C and B (one loop)
for i in range(A):
    result_vec_CB[i,:,:] = numpy.dot(x[i] * y, z.transpose())

numpy.testing.assert_almost_equal(result_slow, result_vec_C)
numpy.testing.assert_almost_equal(result_slow, result_vec_CB)

【问题讨论】:

  • 很遗憾,这不是作业问题。实际上,如果有关于“我如何矢量化”这一一般主题的课程/教科书,我会很高兴!

标签: python numpy linear-algebra vectorization


【解决方案1】:

如果您使用的是 numpy > 1.6,则有很棒的 np.einsum 函数:

np.einsum('im,jm,km->ijk',x,y,z)

这相当于您的循环版本。我不确定一旦您在实际问题中达到阵列的大小(当我移动到这些大小时,我的机器上实际上出现了段错误),这将如何提高效率。对于这类问题,我经常喜欢的另一种解决方案是使用 cython 重写方法。

【讨论】:

    【解决方案2】:

    在您的情况下,使用 einsum 很有意义;但是你可以很容易地手动完成。诀窍是使数组可以相互广播。这意味着重新塑造它们,使每个阵列沿着自己的轴独立变化。然后将它们相乘,让numpy 负责广播;然后沿最后一个(最右边的)轴求和。

    >>> x = numpy.arange(2 * 4).reshape(2, 4)
    >>> y = numpy.arange(3 * 4).reshape(3, 4)
    >>> z = numpy.arange(4 * 4).reshape(4, 4)
    >>> (x.reshape(2, 1, 1, 4) * 
    ...  y.reshape(1, 3, 1, 4) *
    ...  z.reshape(1, 1, 4, 4)).sum(axis=3)
    array([[[  36,   92,  148,  204],
            [  92,  244,  396,  548],
            [ 148,  396,  644,  892]],
    
           [[  92,  244,  396,  548],
            [ 244,  748, 1252, 1756],
            [ 396, 1252, 2108, 2964]]])
    

    您可以通过使用切片表示法、newaxis 值(等于 None,因此下面也适用于 None)以及 sum接受负轴值(-1 表示最后一个,-2 表示倒数第二个,依此类推)。这样,您不必知道数组的原始形状;只要它们的最后一个轴兼容,就会将前三个轴一起广播:

    >>> (x[:, numpy.newaxis, numpy.newaxis, :] *
    ...  y[numpy.newaxis, :, numpy.newaxis, :] *
    ...  z[numpy.newaxis, numpy.newaxis, :, :]).sum(axis=-1)
    array([[[  36,   92,  148,  204],
            [  92,  244,  396,  548],
            [ 148,  396,  644,  892]],
    
           [[  92,  244,  396,  548],
            [ 244,  748, 1252, 1756],
            [ 396, 1252, 2108, 2964]]])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-09
      • 2016-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      相关资源
      最近更新 更多