`numpy.einsum`中的`out`参数无法按预期工作

Hua*_*Wei 5 numpy python-3.5

我有两个码.第一个是:

A = np.arange(3*4*3).reshape(3, 4, 3)
P = np.arange(1, 4)
A[:, 1:, :] = np.einsum('j, ijk->ijk', P, A[:, 1:, :])
Run Code Online (Sandbox Code Playgroud)

结果A是:

array([[[  0,   1,   2],
        [  6,   8,  10],
        [ 18,  21,  24],
        [ 36,  40,  44]],

       [[ 12,  13,  14],
        [ 30,  32,  34],
        [ 54,  57,  60],
        [ 84,  88,  92]],

       [[ 24,  25,  26],
        [ 54,  56,  58],
        [ 90,  93,  96],
        [132, 136, 140]]])
Run Code Online (Sandbox Code Playgroud)

第二个是:

A = np.arange(3*4*3).reshape(3, 4, 3)
P = np.arange(1, 4)
np.einsum('j, ijk->ijk', P, A[:, 1:, :], out=A[:,1:,:])
Run Code Online (Sandbox Code Playgroud)

结果A是:

array([[[ 0,  1,  2],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0]],

       [[12, 13, 14],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0]],

       [[24, 25, 26],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0]]])
Run Code Online (Sandbox Code Playgroud)

所以结果是不同的.这里我想out用来节省内存.这是一个错误numpy.einsum吗?或者我错过了什么?

顺便说一下,我的numpy版本是1.13.3.

hpa*_*ulj 4

我以前没有使用过这个新参数,但过去out曾使用过,并且对它的工作原理(或至少习惯于)有一个大致的了解。einsum

在我看来,它out在迭代开始之前将数组初始化为零。这将解释该块中的所有 0 A[:,1:,:]。如果我初始化单独的out数组,则会插入所需的值

In [471]: B = np.ones((3,4,3),int)
In [472]: np.einsum('j, ijk->ijk', P, A[:, 1:, :], out=B[:,1:,:])
Out[472]: 
array([[[  3,   4,   5],
        [ 12,  14,  16],
        [ 27,  30,  33]],

       [[ 15,  16,  17],
        [ 36,  38,  40],
        [ 63,  66,  69]],

       [[ 27,  28,  29],
        [ 60,  62,  64],
        [ 99, 102, 105]]])
In [473]: B
Out[473]: 
array([[[  1,   1,   1],
        [  3,   4,   5],
        [ 12,  14,  16],
        [ 27,  30,  33]],

       [[  1,   1,   1],
        [ 15,  16,  17],
        [ 36,  38,  40],
        [ 63,  66,  69]],

       [[  1,   1,   1],
        [ 27,  28,  29],
        [ 60,  62,  64],
        [ 99, 102, 105]]])
Run Code Online (Sandbox Code Playgroud)

的 Python 部分einsum并没有告诉我太多,除了它如何决定将out数组传递给该c部分(作为 的列表之一tmp_operands):

c_einsum(einsum_str, *tmp_operands, **einsum_kwargs)

我知道它设置了一个c-api等价的np.nditer,使用str来定义轴和迭代。

它迭代类似于迭代教程中的此部分的内容:

https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.nditer.html#reduction-iteration

特别注意it.reset()步骤。这会out在迭代之前将缓冲区设置为 0。然后,它迭代输入数组和输出数组的元素,将计算值写入输出元素。由于它正在做产品的总和(例如out[:] += ...),因此它必须从头开始。

我猜测了一下实际发生的情况,但对我来说,它应该首先将输出缓冲区清零似乎是合乎逻辑的。如果该数组与输入之一相同,则最终会扰乱计算。

所以我认为这种方法不会起作用并节省你的记忆。它需要一个干净的缓冲区来累积结果。完成后,或者您可以将值写回A。但考虑到类似产品的性质dot,您不能使用相同的数组作为输入和输出。

In [476]: A[:,1:,:] = np.einsum('j, ijk->ijk', P, A[:, 1:, :])
In [477]: A
Out[477]: 
array([[[  0,   1,   2],
        [  3,   4,   5],
        [ 12,  14,  16],
        [ 27,  30,  33]],
        ....)
Run Code Online (Sandbox Code Playgroud)