NumPy的ndarray广播——形状(X,)与(X,1)在与(X,Y)运算时的区别

3
我有一个NumPy的ndarray,形状为(32, 1024),保存着32个信号测量值。我想将它们结合成一个长度为1024的数组,并且每个信号都有不同的权重。我原本使用numpy.average函数,但我的权重是复杂的,average函数会基于权重总和进行归一化,导致结果出错。
查看average函数的代码后,我意识到可以通过将权重乘以信号数组,然后对第一轴求和来实现相同的功能。但是,当我尝试将(32,)的权重数组乘以(32,1024)的信号数组时,由于(32,)无法广播到(32,1024),所以维度不匹配。如果我将权重数组重新调整为(32,1),那么一切都符合预期,但这会导致代码变得非常丑陋:
avg = (weights.reshape((32, 1)) * data).sum(axis=0)

有人能解释一下为什么 NumPy 不允许我的 (32,) 数组进行广播到 (32, 1024) 吗?或者建议一个替代方案,更加简洁地执行加权平均?


1
numpy可以将(32,)扩展为(1, 32)(1024,32);但是你必须允许它将(32,)扩展为(32,1)。这避免了其他情况下的歧义,例如当乘以(32,)和(1024,)时。更多信息请参见我的最近回答,http://stackoverflow.com/a/39238203/901925。 - hpaulj
1个回答

8

(X,)(X,Y) 形状的数组进行对齐的通用设置

关于为什么 (32,) 无法广播到 (32, 1024),原因是形状没有正确对齐。简单来说,我们有:

weights :         32
data    :  32 x 1024 

我们需要将唯一的轴与 data 的第一个轴对齐,该轴是 weights 的第一个轴。因此,正如您发现的一种方法是通过reshape2D,从而我们将以第二个轴作为单例维度结束。这可以通过引入一个新轴来实现: None/np.newaxis weights [:,np.newaxis]weights [:,None]或简单的重塑:weights.reshape(-1,1)。因此,回到示意图,使用修改后的版本,我们将有:
weights[:,None] :  32 x    1
data            :  32 x 1024

现在,形状已经对齐,我们可以在这两个元素之间执行任何常规的逐元素操作,并且结果原理图看起来像这样 -
weights[:,None] :  32 x    1
data            :  32 x 1024
result          :  32 x 1024

这将广播 weights 并使用 data 进行相关的逐元素操作,最终得到 result

解决我们特定情况及其替代方案

根据前面的讨论,要解决我们的逐元素乘法问题,可以使用 weights[:,None]*data,然后沿着 axis=0 求和,即 -

(weights[:,None]*data).sum(axis=0)

让我们寻找整洁的替代方案!

一种整洁且可能直观的方法是使用np.einsum -

np.einsum('i,ij->j',weights,data)

另一种方法是使用矩阵乘法,使用np.dot函数。因为我们将weights的第一个轴与data的第一个轴相匹配,所以需要这样做-
weights.dot(data)

非常清晰和详细的回答,谢谢。我之前不知道 np.einsum,虽然它确实是一种巧妙的方法,但我认为对于不熟悉语法的人来说并不明显。np.dot 的风格更接近于某些东西“很有意义”,而且非常紧凑。 - Ben Rowland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接