涉及将填充放在哪里的广播问题

A broadcasting issue involving where to put the padding

简介

我有一个可向量化的函数 func,我使用 np.frompyfunc 对其进行向量化。我不想使用嵌套的 for 循环,而是只想调用它一次,因此我需要用 np.newaxis 填充输入。

我的目标是摆脱两个嵌套的 for 循环并改用 numpy.array 广播功能。

这是 MWE for 循环(我想摆脱 for 循环,而是填充变量 c_0c_1rn_1rn_2, 和 factor 当调用 myfunc.

感兴趣问题的MWE

for i, b_0 in enumerate(b_00):
    for j, b_1 in enumerate(b_11):
        factor = b_0 + b_1
        rn = (b_0 * coord + b_1 * coord2) / factor
        rn_1 = coord - rn
        rn_2 = coord2 - rn
        results = np.add( results,np.prod(myfunc(c_0[:,None,:], c_1[None,:,:], rn_1, rn_2, factor), axis=2) )

上述显式for循环是正确的,包含在代码块中以保证质量。

我目前的努力

factor = b_00[:, None] + b_11[None, :]
rn = np.add( (b_00[:,None] * coord[None,:])[:, None, :],  (b_11[:,None] * coord2[None,:])[None, :, :] ) / factor[:,:,None]
rn_1 = coord - rn
rn_2 = coord2 - rn

results2 = np.prod(myfunc(c_0[:,None,:, None, None], c_1[None,:,:, None, None], rn_1[:,:,:],rn_2[:,:, :], factor[None,:, :, None]), axis=2)
results2 = np.squeeze(results2)
results2 = np.sum(results2, axis=(2,3))

整体代码块

    import numpy as np 

myfunc = np.frompyfunc(func,5,1)
################################################################
# Prep arrays needed for MWE
################################################################    
results = np.zeros((5,3))
coord = np.array([1,1,2])
coord2 = np.array([3,3,3])
c_0 = np.array([[0,0,2],[0,2,0],[2,0,0],[1,1,0], [1,0,1]])
c_1 = np.array([[0,0,2],[0,2,0],[2,0,0]])
b_00 = np.array([2.2, 1.1, 4.4]) # np.array([2.2, 3.3, 40.4])
b_11 = np.array([1.2, 3.3]) # np.array([1.2, 5.3])

################################################################
# This is only for comparison. `results` is the correct answer
################################################################
for i, b_0 in enumerate(b_00):
    for j, b_1 in enumerate(b_11):
        factor = b_0 + b_1
        rn = (b_0 * coord + b_1 * coord2) / factor
        rn_1 = coord - rn
        rn_2 = coord2 - rn
        results = np.add( results,np.prod(myfunc(c_0[:,None,:], c_1[None,:,:], rn_1, rn_2, factor), axis=2) )

################################################################
# Prep for broadcasting   (My attempt)
################################################################
factor = b_00[:, None] + b_11[None, :]
rn = np.add( (b_00[:,None] * coord[None,:])[:, None, :],  (b_11[:,None] * coord2[None,:])[None, :, :] ) / factor[:,:,None]
rn_1 = coord - rn
rn_2 = coord2 - rn

# The following all get the same *wrong* answer
# results2 = np.prod(myfunc(c_0[:,None,:,None, None], c_1[None,:,:, None, None], rn_1[:, None, None],rn_2[:,None, None], factor[None,:, :]), axis=2) 
# results2 = np.prod(myfunc(c_0[:,None,:, None, None, None], c_1[None,:,:, None, None, None], rn_1[None, None,:,:,:, None],rn_2[None, None,:,:, :, None], factor[None,:, :, None, None]), axis=2) 
# results2 = np.prod(myfunc(c_0[:,None,:, None, None], c_1[None,:,:, None, None], rn_1[None, None,:,:,:],rn_2[None, None,:,:, :], factor[None,:, :, None]), axis=2)

# this should be the only line needing work!
results2 = np.prod(myfunc(c_0[:,None,:, None, None], c_1[None,:,:, None, None], rn_1[:,:,:],rn_2[:,:, :], factor[None,:, :, None]), axis=2)

results2 = np.squeeze(results2)
results2 = np.sum(results2, axis=(2,3))

assert np.allclose(results, results2)

# Vectorized function to be sent broadcasted arrays
def func(r0, r1, x, y, at):
    val = 0.0 
    for i in range(r0+1):
        for j in range(r1+1):
            val += x + i*j + at * y
    return val

问题

  1. 通过上面的代码,我得到了结果数组的正确形状(results2 是我尝试广播,而 results 是给出正确答案的慢循环),但是它有错误的值。
  2. 正如 @hpaulj 指出的那样,如果我将 b_00 的尺寸更改为长度 4(或任何更大的尺寸),我的解决方案甚至无法获得正确的形状。

更新

请确保它适用于当前的 b_00 = np.array([2.2, 1.1, 4.4]) 以及更通用的 b_00 = np.array([2.2, 1.1, 4.4, 5.1, 6.2])。我想要一个广播解决方案,但会接受一个比 for loops.

更快的解决方案

这应该可以解决您的问题!

#Definitions
coord = np.array([1,1,2])
coord2 = np.array([3,3,3])
c_0 = np.array([[0,0,2],[0,2,0],[2,0,0],[1,1,0], [1,0,1]])
c_1 = np.array([[0,0,2],[0,2,0],[2,0,0]])
b_00 = np.array([2.2, 1.1, 4.4]) # np.array([2.2, 3.3, 40.4])
b_11 = np.array([1.2, 3.3]) # np.array([1.2, 5.3])

#Vectorized code for prep 
b_0 = np.outer(b_00, coord)
b_1 = np.outer(b_11, coord2)
factor = (b_00+b_11.reshape(-1,1)).T[:,:,None]
rn = np.divide((b_0[:,None]+b_1), factor)
rn_1 = coord-rn
rn_2 = coord2-rn

#THIS IS YOUR INTERESTING PART
results = np.prod(myfunc(c_0[:,None,:,None,None], c_1[None,:,:,None,None], rn_1.transpose(2,0,1), rn_2.transpose(2,0,1), factor.transpose(2,0,1)).transpose(3,4,0,1,2), axis=4)
results = np.add.reduce(results, axis=(0,1))
results
array([[6707.793061061082, 5277.838468285241, 5277.838468285241],
       [5277.838468285241, 5992.8157646731615, 5277.838468285241],
       [5277.838468285241, 5277.838468285241, 5992.8157646731615],
       [7037.117957713655, 7513.7694886389345, 7513.7694886389345],
       [7990.421019564216, 7037.117957713655, 7513.7694886389345]],
      dtype=object)

仅供理解,因为在旧的循环解决方案中,myfunc 在 rn_1 和 rn_2 的第一个轴上运行,广播频道需要是最后 2 个而不是第一个。所以,我在 c_0 和 c_1 的末尾添加了 2 个通道,然后我将最后一个轴放在前面,使 (3,2,3) rn_1 变成 (3, 3,2).对于因子也是如此。所以现在 myfunc 可以在突出显示广播频道的张量上运行 - (5,1,3,1,1), (1,3,3,1,1), (3,3,2), (3,3,2), (1,3,2)

最后,我再次转置将广播频道放在前面,这样我们就有了一个 (3,2,5,3,3) 形状的张量,其中第一个 2频道是 3,2 嵌套循环的广播版本,而 5,3,3 是您现在需要 np.prod over axis = 4 而不是 axis = 2.

的矩阵

Post,使用 np.add.reduce 对 0,1 轴求和将结果带到 5,3 矩阵是一件简单的事情。最后的结果应该和循环的结果完全一样。

我也有点冒昧地修改了第一部分,因为这让我看起来更舒服,但请忽略那部分。

PS。检查它是否适用于您提到的示例

b_00 = np.array([2.2, 1.1, 4.4, 5.1, 6.2])