NumPy 的 transpose() 方法如何排列数组的轴?

How does NumPy's transpose() method permute the axes of an array?

In [28]: arr = np.arange(16).reshape((2, 2, 4))

In [29]: arr
Out[29]: 
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])


In [32]: arr.transpose((1, 0, 2))
Out[32]: 
array([[[ 0,  1,  2,  3],
        [ 8,  9, 10, 11]],

       [[ 4,  5,  6,  7],
        [12, 13, 14, 15]]])

当我们将整数元组传递给 transpose() 函数时,会发生什么?

具体来说,这是一个 3D 数组:当我传递轴 (1, 0 ,2) 的元组时,NumPy 如何转换数组?你能解释一下这些整数指的是哪一行或哪一列吗? NumPy 上下文中的轴号是什么?

如解释的那样in the documentation

By default, reverse the dimensions, otherwise permute the axes according to the values given.

因此您可以传递一个可选参数 axes 来定义维度的新顺序。

例如转置 RGB VGA 像素阵列的前两个维度:

 >>> x = np.ones((480, 640, 3))
 >>> np.transpose(x, (1, 0, 2)).shape
 (640, 480, 3)

要转置数组,NumPy 只需交换每个轴的形状和步长信息。以下是进步:

>>> arr.strides
(64, 32, 8)

>>> arr.transpose(1, 0, 2).strides
(32, 64, 8)

请注意,转置操作交换了轴 0 和轴 1 的步幅。这些轴的长度也被交换了(本例中两个长度都是 2)。

无需复制任何数据即可实现; NumPy 可以简单地改变它查看底层内存的方式来构造新数组。


可视化进步

步幅值表示为了达到数组轴的下一个值必须在内存中移动的字节数。

现在,我们的 3D 数组 arr 看起来像这样(带有标记的轴):

这个数组存储在一个contiguous block of memory;本质上它是一维的。要将其解释为 3D 对象,NumPy 必须跳过一定数量的字节才能沿三个轴之一移动:

由于每个整数占用 8 个字节的内存(我们使用的是 int64 dtype),因此每个维度的步长值是我们需要跳转的值数的 8 倍。例如,要沿轴1移动,需要跳转4个值(32字节),要沿轴0移动,需要跳转8个值(64字节)。

当我们写 arr.transpose(1, 0, 2) 时,我们交换轴 0 和 1。转置数组如下所示:

NumPy 需要做的就是交换轴 0 和轴 1 的步幅信息(轴 2 不变)。现在我们必须跳得更远才能沿着轴 1 比轴 0 移动:

这个基本概念适用于数组轴的任何排列。处理转置的实际代码是用 C 语言编写的,可以在 here.

中找到

总结a.transpose()[i,j,k] = a[k,j,i]

a = np.array( range(24), int).reshape((2,3,4))
a.shape gives (2,3,4)
a.transpose().shape gives (4,3,2)  shape tuple is reversed.

当传递元组参数时,轴会根据元组进行排列。 例如

a = np.array(范围(24), int).reshape((2,3,4))

a[i,j,k] 等于 a.transpose((2,0,1))[k,i,j]

轴 0 获得第二名

轴1获得第3名

axis 2 故事第一名

当然我们需要注意传递给转置的元组参数中的值是唯一的并且在范围(轴数)内

在 C 表示法中,您的数组将是:

int arr[2][2][4]

这是一个具有 2 个 2D 数组的 3D 数组。每个二维数组都有 2 个一维数组,每个一维数组都有 4 个元素。

所以你有三个维度。坐标轴为 0、1、2,大小为 2、2、4。这正是 numpy 处理 N 维数组坐标轴的方式。

因此,arr.transpose((1, 0, 2)) 会将轴 1 放在位置 0,轴 0 放在位置 1,轴 2 放在位置 2。您实际上是在排列轴:

0 -\/-> 0
1 -/\-> 1
2 ----> 2

换句话说,1 -> 0, 0 -> 1, 2 -> 2。目标轴始终按顺序排列,因此您只需指定源轴即可。按顺序读取元组:(1, 0, 2).

在这种情况下,您的新数组维度又是 [2][2][4],只是因为轴 0 和 1 具有相同的大小 (2)。

更有趣的是 (2, 1, 0) 的转置,它给你一个 [4][2][2].

的数组
0 -\ /--> 0
1 --X---> 1
2 -/ \--> 2

换句话说,2 -> 0, 1 -> 1, 0 -> 2。按顺序读取元组:(2, 1, 0).

>>> arr.transpose((2,1,0))
array([[[ 0,  8],
        [ 4, 12]],

       [[ 1,  9],
        [ 5, 13]],

       [[ 2, 10],
        [ 6, 14]],

       [[ 3, 11],
        [ 7, 15]]])

你得到了 int[4][2][2]

如果所有维度的大小都不同,您可能会更好地理解,这样您就可以看到每个轴的去向。

为什么第一个内部元素是[0, 8]?因为如果将 3D 数组可视化为两张纸,08 排成一行,一张在一张纸上,一张在另一张纸上,都在左上角。通过调换 (2, 1, 0),您是说您希望纸到纸的方向现在沿着纸从左到右行进,而从左到右的方向现在从纸到纸。你有 4 个元素从左到右,所以现在你有四张纸。你有 2 篇论文,所以现在你有 2 个元素,从左到右。

抱歉糟糕的 ASCII 艺术。 ¯\_(ツ)_/¯

问题和示例似乎源自韦斯·麦金尼 (Wes McKinney) 的书 Python for Data Analysistranspose 的这个特性在 第 4.1 章中提到过。转置数组和交换轴

For higher dimensional arrays, transpose will accept a tuple of axis numbers to permute the axes (for extra mind bending).

这里“permute”的意思是“重新排列”,所以重新排列坐标轴的顺序。

.transpose(1, 0, 2)中的数字决定了轴的顺序与原始顺序相比如何变化。通过使用 .transpose(1, 0, 2),我们的意思是“用第二个轴改变第一个轴”。如果我们使用 .transpose(0, 1, 2),数组将保持不变,因为没有什么可以改变;这是默认顺序。

书中带有 (2, 2, 4) 大小数组的示例不是很清楚,因为第 1 轴和第 2 轴具有相同的大小。所以最终结果似乎没有改变,除了行 arr[0, 1]arr[1, 0].

的重新排序

如果我们尝试使用 3 维数组的不同示例,每个维度具有不同的大小,则重新排列部分会变得更加清晰。

In [2]: x = np.arange(24).reshape(2, 3, 4)

In [3]: x
Out[3]: 
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])

In [4]: x.transpose(1, 0, 2)
Out[4]: 
array([[[ 0,  1,  2,  3],
        [12, 13, 14, 15]],

       [[ 4,  5,  6,  7],
        [16, 17, 18, 19]],

       [[ 8,  9, 10, 11],
        [20, 21, 22, 23]]])

此处,原始数组大小为(2, 3, 4)。我们改变了 1st 和 2nd,所以它的大小变成了 (3, 2, 4)。如果我们仔细观察一下重排究竟是如何发生的;数字数组似乎以特定模式发生了变化。使用 @RobertB 的纸类比,如果我们要取 2 个数字块,并将每个数字写在 sheets 上,然后从每个 sheet 中取出一行来构造一维的数组,我们现在将有一个 3x2x4 大小的数组,从最外层到最内层计算。

[ 0,  1,  2,  3] \ [12, 13, 14, 15]

[ 4,  5,  6,  7] \ [16, 17, 18, 19]

[ 8,  9, 10, 11] \ [20, 21, 22, 23]

使用不同大小的数组并更改不同的轴以更好地了解其工作原理可能是个好主意。

我 运行 在 Python 的数据分析 中也是如此。

我将展示求解 3 维张量的最简单方法,然后描述可用于 n 维张量的一般方法。

简单的 3 维张量示例

假设你有 (2,2,4)-tensor

[[[ 0  1  2  3]
  [ 4  5  6  7]]

 [[ 8  9 10 11]
  [12 13 14 15]]]

如果我们查看每个点的坐标,它们如下:

[[[ (0,0,0)  (0,0,1)  (0,0,2)  (0,0,3)]
  [ (0,1,0)  (0,1,1)  (0,1,2)  (0,1,3)]]

 [[ (1,0,0)  (1,0,1) (1,0,2) (0,0,3)]
  [ (1,1,0)  (1,1,1) (1,1,2) (0,1,3)]]

现在假设上面的数组是example_array,我们要执行操作:example_array.transpose(1,2,0)

对于(1,2,0)-t运行sformation,我们将坐标打乱如下(注意这个特定的t运行sformation相当于“左移”:

(0,0,0)  ->  (0,0,0)
(0,0,1)  ->  (0,1,0)
(0,0,2)  ->  (0,2,0)
(0,0,3)  ->  (0,3,0)
(0,1,0)  ->  (1,0,0)
(0,1,1)  ->  (1,1,0)
(0,1,2)  ->  (1,2,0)
(0,1,3)  ->  (1,3,0)
(1,0,0)  ->  (0,0,1)
(1,0,1)  ->  (0,1,1)
(1,0,2)  ->  (0,2,1)
(0,0,3)  ->  (0,3,0)
(1,1,0)  ->  (1,0,1)
(1,1,1)  ->  (1,1,1)
(1,1,2)  ->  (1,2,1)
(0,1,3)  ->  (1,3,0)

现在,对于每个原始值,将其放入结果矩阵的移位坐标中。

例如,值 10 在原始矩阵中的坐标为 (1, 0, 2),在结果矩阵中的坐标为 (0, 2, 1)。它被放置在该子矩阵第三行的第一个二维张量子矩阵中,在该行的第二列中。

因此,得到的矩阵是:

array([[[ 0,  8],
        [ 1,  9],
        [ 2, 10],
        [ 3, 11]],

       [[ 4, 12],
        [ 5, 13],
        [ 6, 14],
        [ 7, 15]]])

一般n维张量方法

对于n维张量,算法相同。考虑原始矩阵中单个值的所有坐标。打乱那个单独坐标的轴。将值放入结果矩阵中的结果、混洗坐标中。对所有剩余值重复此操作。