【问题标题】:How to decompose 2D (m*n, m*n) matrix into 4D (m, m, n, n) matrix in Python?如何在 Python 中将 2D (m*n, m*n) 矩阵分解为 4D (m, m, n, n) 矩阵?
【发布时间】:2022-01-17 10:58:27
【问题描述】:

首先让我们考虑一个黑盒子,其中 m 和 n 是两个变量(其中 m 是 n) 它输出一个形状为 (m*n, m*n) 的二维矩阵。现在,需要将这个 2D 矩阵转换为形状为 (m, m, n, n)。我不确定以书面形式描述这一点的最佳方式,但数据的结构方式是在 2D (m*n)x(m *n) 矩阵中存在 m 很多 (nxn) 个“瓦片”每个方向。考虑一个示例数组 a,在这种情况下,我们有 m = 3 和 n = 2 所以传入的 2D 矩阵是 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]]

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

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矩阵,其中每个条目实际上是一个n xn 矩阵,创建一个 4D 矩阵,然后我们可以做以下工作。这是一个高度简化的示例,用于演示一个更复杂的系统,其中还有很多事情要做。在我的例子中,还有一个额外的轴,m = 256,矩阵中的条目很复杂(64 位),我们非常关注性能,但是这些细节与问题无关。如果它有帮助n = 2的情况是唯一我们关心的情况,但是我希望有更通用的解决方案。

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

可能的解决方案?

  1. 头脑会立即跳到类似 np.reshape() 的东西上,但是我们不能简单地使用 a.reshape(m, m, n, n),因为 np.reshape() 的方式不会保留正确的顺序首先解开数组,如similar issue 中所述,但我坚信在经过大量 审议之后,该问题的解决方案在这种情况下将不起作用。我认为一个 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 重现了测试问题:

【问题讨论】:

  • 使用 reshape a.reshape(3, 2, -1, 2).swapaxes(1,2).reshape(3, 3, 2, 2),但我认为 np.einsum 可能更简单。据我了解,reshape 和 swapaxes 是 O(1) 操作且高效。如果我错了,请纠正我。
  • 操作 reshape 和 swapaxes,如果可能,不要执行复制操作。在 Michael 描述的操作中,创建了数组 a 的视图,以防万一具有恒定的复杂性。可以检查例如通过修改结果中的值,并查看值是否也在 a 中被修改。 np.einsum 确实是一个非常强大的操作,但它并不能解决手头的问题。这对于对整形结果执行的操作可能很有用,我需要更多信息来评论。
  • @LiamRyan 实际上,如果输入数组是连续的,您不会通过a.reshape(3, 2, -1, 2).swapaxes(1,2).reshape(3, 3, 2, 2) 创建数组(只是视图)。因此,性能与数组大小无关。对于这个相当小的示例,您不会看到复制和视图之间有太大区别。如果您将示例放大 10 000 倍,您肯定会看到差异。
  • einsum 解决方案看起来像A=A.reshape(3, 2, -1, 2) #view array as (m,n,m,n) ,B=np.einsum('ijkl->ikjl',A) #the same as the swap axis. 最简单的方法是查看数组的标志方法。如果它不拥有数据,它只是一个视图。
  • @max9111,swapaxes 之后的重塑将(通常)创建一个副本。 reshape 说把它想象成首先做一个解谜。在交换之后执行ravel,您会看到不同的元素顺序(与原始顺序相比)。 swapaxes/transpose 通过更改 shape 和 strides 工作,不需要复制。但由于值不再是“c”连续的,因此进一步处理通常会生成副本。

标签: python arrays numpy performance matrix


【解决方案1】:

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

一个起始数组 - 一维数组的重塑:

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),但步幅已经改变。这也是一种看法。但是对此的重塑(包括拉威尔)将复制。元素已重新排序:

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 ravel。虽然这可能对理解没有帮助。秩序不容易扩展到更高的维度,但跨步可以。

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])

【讨论】:

  • 但是是最后的解开,导致了版权。我对 Michaels 代码做了一个小基准测试,时间与数组大小无关(我也对这种行为感到有些惊讶)。2
  • 重塑后跟交换是一个视图,与大小无关。可能需要复制的是后续操作。
  • 重塑后跟交换是一个视图,与大小无关。可能需要复制的是后续操作。
【解决方案2】:

我不确定这是否比 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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-18
    • 2021-12-31
    • 1970-01-01
    • 2012-03-25
    相关资源
    最近更新 更多