如何在Python中将2D(m*n,m*n)矩阵分解为4D(m,m,n,n)矩阵?

How to decompose 2D (m*n, m*n) matrix into 4D (m, m, n, n) matrix in Python?

首先让我们考虑一个黑盒,其中 mn 是两个变量(其中 mn 的倍数)它输出形状为 (m*n 的二维矩阵, m*n)。现在,需要将这个 2D 矩阵转换为 4D 矩阵,形状为 (m, m, n, n).我不确定以书面形式描述这一点的最佳方式,但数据的结构方式是在 2D (m*n) x(m*n) 矩阵存在 m 很多 (nxn) 每个方向“平铺”。考虑一个示例数组 a,在这种情况下我们有 m = 3 n = 2 所以传入的二维矩阵是6x6:

print(a)
[[0  1  4  5  8  9 ]
 [2  3  6  7  10 11]
 [12 13 16 17 20 21]
 [14 15 18 19 22 23]
 [24 25 28 29 32 33]
 [26 27 30 31 34 35]]

然后将其传递给某个函数:

b = some_func(a)

所需的输出将是 4D 数组:

print(b)
[[[[ 0  1]
   [ 2  3]]

  [[ 4  5]
   [ 6  7]]

  [[ 8  9]
   [10 11]]]


 [[[12 13]
   [14 15]]

  [[16 17]
   [18 19]]

  [[20 21]
   [22 23]]]


 [[[24 25]
   [26 27]]

  [[28 29]
   [30 31]]

  [[32 33]
   [34 35]]]]

换句话说,我们需要在较大的二维数组中分离出“nxn”块。这种情况的实际意义是我们有mxm矩阵,其中每一项实际上是一个 nxn 矩阵,创建一个 4D 矩阵,然后我们可以进行以下工作。这是一个高度简化的示例,用于演示目的是在一个更复杂的系统中进行 lot。在我的例子中,还有一个额外的轴,m = 256,矩阵中的条目很复杂(64 位),我们非常关注性能,但这些细节与问题无关。如果它有帮助n = 2的情况是唯一我们关心的情况,不过我希望有一个更通用的解决方案。

我可以合理地设想一个解决方案,它使用循环、索引、模运算等,但是这在 Python 中效率非常低。

可能的解决方案?

  1. 大脑立即跳转到 np.reshape() 之类的东西,但是我们不能简单地使用 a.reshape(m, m, n, n),因为由于方法 np.reshape() 首先拆散数组,如 中所述,但是我坚信,在 much[= 之后,该问题的解决方案在这种情况下将不起作用96=] 审议。我认为一个 np.reshape()、一个 np.swapaxes()、另一个 np.reshape() 和一个 np.swapaxes() 可能会起作用,但唉,即使这样的方法会起作用看起来效率很低。完全可以想象,np.reshapes()、np.swapaxes() 的某些组合会提供解决方案,但我没有成功。
  2. 一位同事随口建议说 np.einsum() 是一种非常强大且可推广的矩阵运算方法(?),但我并没有成功。
  3. 最有可能的解决方案:有一种特定的“Pythonic”做事方式,但我不知道 - 一个我不知道的特定 numpy 函数将完成确切的技巧!

我希望我对问题的描述是充分的。围绕这个问题的背景非常复杂(射电天文图像处理)并且提供全部细节会非常麻烦,请在提供任何解决方案时以表面价值和初始假设作为给定的问题。

这是重现测试问题的代码行。

a = np.array([[0, 1, 4, 5, 8, 9], [2, 3, 6, 7, 10, 11], [12, 13, 16, 17, 20, 21], [14, 15, 18, 19, 22, 23], [24, 25, 28, 29, 32, 33], [26, 27, 30, 31, 34, 35]])

编辑:为进一步清晰起见,使用一点 TeX 重现了测试问题:

我将尝试说明评论中讨论的问题。

起始数组 - 一维排列的整形:

In [160]: arr = np.arange(16).reshape(4,4)
In [161]: arr
Out[161]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
In [162]: arr.ravel()
Out[162]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15])
In [163]: arr.strides
Out[163]: (32, 8)

进一步整形为4d。注意 ravel 是一样的。我还可以使用 arr2.__array_interface__ 来显示数据缓冲区 ID。

In [164]: arr1 = arr.reshape(2,2,2,2)
In [165]: arr1.ravel()
Out[165]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15])
In [166]: arr1.strides            
Out[166]: (64, 32, 16, 8)

最好测试一下您对步幅随体型变化而变化的理解。

现在交换:

In [167]: arr2 = arr1.swapaxes(1,2)
In [168]: arr2
Out[168]: 
array([[[[ 0,  1],
         [ 4,  5]],

        [[ 2,  3],
         [ 6,  7]]],


       [[[ 8,  9],
         [12, 13]],

        [[10, 11],
         [14, 15]]]])
In [169]: arr2.strides
Out[169]: (64, 16, 32, 8)

仍然是 (2,2,2,2) 但步幅已经改变。这也是一种看法。但是重塑这个(包括 ravel)将制作一个副本。元素已重新排序:

In [170]: arr2.ravel()
Out[170]: array([ 0,  1,  4,  5,  2,  3,  6,  7,  8,  9, 12, 13, 10, 11, 14, 15])
In [171]: arr3 = arr2.reshape(4,4)
In [172]: arr3
Out[172]: 
array([[ 0,  1,  4,  5],
       [ 2,  3,  6,  7],
       [ 8,  9, 12, 13],
       [10, 11, 14, 15]])
In [173]: arr3.ravel()
Out[173]: array([ 0,  1,  4,  5,  2,  3,  6,  7,  8,  9, 12, 13, 10, 11, 14, 15])

我们在更简单的 2d 转置中看到相同的步幅变化:

In [174]: arr4 = arr.T
In [175]: arr4.strides
Out[175]: (8, 32)
In [176]: arr4.ravel()
Out[176]: array([ 0,  4,  8, 12,  1,  5,  9, 13,  2,  6, 10, 14,  3,  7, 11, 15])

我们可以通过指定 'F' 列顺序来 view 解谜。尽管这可能对理解没有帮助。秩序不容易扩展到更高的维度,但大步可以。

In [177]: arr4.ravel(order='F')
Out[177]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15])

我不确定这是否是 better/faster 与 hpaulj 的回答,我想在性能上会相似。请检查自己的大阵列

a.reshape((m, m * n * n)).reshape(m, n, m * n).transpose(0, 2, 1).reshape(m, m, n, n).transpose(0, 1, 3, 2)