使用排列在Python中进行广播

时间:2016-09-09 21:16:33

标签: python matlab vectorization broadcasting permute

我知道transpose上的ndarray旨在等同于matlab的permute功能但是我有一个特定的用例并不能简单地工作。在matlab中我有以下内容:

C = @bsxfun(@times, permute(A,[4,2,5,1,3]), permute(B, [1,6,2,7,3,4,5])

其中A是形状NxNxM的3D张量,B是形状NxNxMxPxP的5D张量。上述功能旨在对循环kronecker产品进行矢量化。我假设Matlab为A和B添加了2个单独维度,这就是为什么它能够重新排列它们的原因。我希望将此代码移植到Python ,但我认为它没有能力添加这些额外的维度。。我发现this成功添加了额外的尺寸,但是广播并没有使用相同的matlab bsxfun。我尝试了明显的翻译(是的,我正在为这些ndarray和函数使用numpy):

A = A[...,None,None]
B = B[...,None,None]
C = transpose(A,[3,1,4,0,2])*transpose(B,[0,5,1,6,2,3,4])

,我收到以下错误:

return transpose(axes)
ValueError: axes don't match array

我的第一个猜测是在A和B上做reshape以添加这些单例尺寸?

我现在收到以下错误:

mults = transpose(rho_in,[3,1,4,0,2])*transpose(proj,[0,5,1,6,2,3,4])
ValueError: operands could not be broadcast together with shapes (1,9,1,9,8) (9,1,9,1,8,40,40)

编辑:修改了我的问题,不仅仅是添加单例维度,而是更多关于在python中正确地广播这个matlab乘法。

2 个答案:

答案 0 :(得分:6)

MATLAB和numpy之间的巨大差异在于前者使用列主要格式作为其数组,而后者使用行主要格式。推论是隐式单例维度的处理方式不同。

具体来说,MATLAB明确忽略尾随单例维度:rand(3,3,1,1,1,1,1)实际上是3x3矩阵。除此之外,如果前导维度匹配,您可以使用bsxfun对两个数组进行操作:NxNxM隐式NxNxMx1x1与{{1}兼容}。

另一方面,

Numpy allows implicit singletons up front。您需要以尾随尺寸匹配的方式NxNxMxPxP数组,例如形状为permute的形状(40,40,9,1,9,1,8),结果应该是形状的(1,9,1,9,8)

虚拟例子:

(40,40,9,9,9,9,8)

请注意,您尝试执行的操作可能可以使用numpy.einsum完成。我建议仔细看看。我的意思的一个例子:根据您的问题,我收集了您要执行的操作:获取元素>>> import numpy as np >>> (np.random.rand(40,40,9,1,9,1,8)+np.random.rand(1,9,1,9,8)).shape (40, 40, 9, 9, 9, 9, 8) A[1:N,1:N,1:M]并构建新数组B[1:N,1:N,1:M,1:P,1:P],以便

C[1:N,1:N,1:N,1:N,1:M,1:P,1:P]

(您的具体索引顺序可能会有所不同)。如果这是正确的,您确实可以使用C[i1,i2,i3,i4,i5,i6,i7] = A[i2,i4,i5]*B[i1,i3,i5,i6,i7]

numpy.einsum()

但是应该注意两件事。首先,上面的操作将非常耗费内存,这应该适用于矢量化的情况(你通常以牺牲内存需求为代价来赢得CPU时间)。其次,您应该认真考虑在移植代码时重新安排数据模型。广播在两种语言中的工作方式不同的原因是与列主要/行主要区别错综复杂地联系在一起。这也意味着在MATLAB中你应该首先使用前导索引,因为>>> a = np.random.rand(3,3,2) >>> b = np.random.rand(3,3,2,4,4) >>> np.einsum('ijk,lmkno->limjkno',a,b).shape (3, 3, 3, 3, 2, 4, 4) 对应于一个连续的内存块,而A(:,i2,i3)则没有。相反,numpy A(i1,i2,:)是连续的,而A[i1,i2,:]则不是。

这些考虑建议您应该设置数据的后勤,使得向量化操作最好与MATLAB中的前导索引和numpy中的尾随索引一起使用。您仍然可以使用A[:,i2,i3]来执行操作本身,但是与MATLAB相比,您的维度应该是不同的(可能是反向的)顺序,至少如果我们假设两个版本的代码都使用最佳设置。

答案 1 :(得分:2)

查看您的MATLAB代码,您有 -

C = bsxfun(@times, permute(A,[4,2,5,1,3]), permute(B, [1,6,2,7,3,4,5])

所以,实质上 -

B : 1 , 6 , 2 , 7 , 3 , 4 , 5 
A : 4 , 2 , 5 , 1 , 3

现在,在MATLAB中,我们不得不从较高的维度中借用单个维度,这就是为67引入变暗B4的所有麻烦的原因。 5的{​​{1}}。

在NumPy中,我们使用np.newaxis/None明确地引入了这些内容。因此,对于NumPy,我们可以这样说 -

A

,其中B : 1 , N , 2 , N , 3 , 4 , 5 A : N , 2 , N , 1 , 3 , N , N 代表新轴。请注意,我们需要在N的末尾添加新轴以推进其他维度以进行对齐。相反,默认情况下会在MATLAB中发生这种情况。

使A看起来很容易,因为尺寸似乎是有序的,我们只需要在适当的位置添加新轴 - B

创建这样的B[:,None,:,None,:,:,:]看起来并不直截了当。忽略A中的N's,我们会有 - A。因此,起点是置换维度,然后添加被忽略的两个新轴 - A : 2 , 1 , 3

到目前为止,我们有 -

A.transpose(1,0,2)[None,:,None,:,:,None,None]

在NumPy中,我们可以跳过领先的新轴和尾随非单一的dims。所以,我们可以像这样简化 -

B (new): B[:,None,:,None,:,:,:]
A (new): A.transpose(1,0,2)[None,:,None,:,:,None,None]

最终输出将是这两个扩展版本之间的乘法 -

B (new): B[:,None,:,None]
A (new): A.transpose(1,0,2)[:,None,...,None,None]

运行时测试

我相信@Andras的帖子意味着等效的C = A.transpose(1,0,2)[:,None,...,None,None]*B[:,None,:,None] 实现类似于:np.einsum

np.einsum('ijk,lmkno->ljmikno',A,B)