NumPy 多维数组转置方法:transpose()
深度教程
NumPy 是 Python 中用于数值计算的核心库,其最重要的特点之一就是提供了高效的多维数组(ndarray
)对象。在处理多维数据时,我们经常需要改变数组的维度顺序,这项操作在数学上被称为转置。NumPy 提供了多种方式进行转置,其中最灵活和强大的是 transpose()
方法或对应的函数 np.transpose()
。
本教程将带您深入了解 NumPy 数组的转置,从基本的二维数组转置到复杂的高维数组轴(axes)重排,并详细解释 transpose()
方法的使用、参数以及其背后的原理。
1. 什么是数组转置?
在数学中,矩阵的转置是将矩阵的行变成列,列变成行。如果原始矩阵 $A$ 的维度是 $m \times n$,其转置矩阵 $A^T$ 的维度就是 $n \times m$,且 $A^T_{ij} = A_{ji}$。
对于多维数组,转置的概念被推广为改变数组的轴的顺序。一个 $N$ 维数组有 $N$ 个轴,通常从 0 到 $N-1$ 进行索引。例如,一个三维数组的形状可能是 (d0, d1, d2)
,它有轴 0、轴 1 和轴 2。转置操作就是根据指定的顺序重新排列这些轴。
理解“轴”是理解 NumPy 高维数组操作的关键。您可以将轴想象成数组的各个维度:
* 二维数组 (shape (rows, columns)
): 轴 0 是行方向,轴 1 是列方向。
* 三维数组 (shape (depth, rows, columns)
或 (layers, height, width)
): 轴 0 是深度或层方向,轴 1 是行或高度方向,轴 2 是列或宽度方向。
* 四维数组 (shape (time, depth, rows, columns)
): 轴 0 是时间步,轴 1 是深度,轴 2 是行,轴 3 是列。
transpose()
方法允许我们通过指定一个轴的新顺序来重新排列数组的维度。
2. NumPy 中的转置方法概览
NumPy 提供了几种进行转置的方式:
.T
属性:这是一个快捷方式,主要用于执行“默认”转置。对于二维数组,它执行标准的矩阵转置(交换轴 0 和轴 1)。对于更高维度的数组,它会反转轴的顺序。ndarray.transpose(*axes)
方法:这是数组对象自身的方法。它接受一个可选的参数axes
,用来指定轴的新顺序。如果不提供axes
,则执行默认转置(与.T
相同)。numpy.transpose(a, axes=None)
函数:这是一个顶级 NumPy 函数,功能与ndarray.transpose()
方法完全相同。它接受数组a
作为第一个参数。
在这几种方法中,ndarray.transpose()
方法和 numpy.transpose()
函数是最通用和灵活的,尤其是在处理高维数组并需要自定义轴顺序时。本教程将主要聚焦于 transpose()
方法/函数,并也会介绍 .T
属性作为对比。
3. 二维数组的转置 (矩阵转置)
让我们从最简单的二维数组开始,这对应于标准的矩阵转置。
“`python
import numpy as np
创建一个二维数组 (2×3 矩阵)
arr_2d = np.array([[1, 2, 3],
[4, 5, 6]])
print(“原始二维数组:”)
print(arr_2d)
print(“原始形状:”, arr_2d.shape)
print(“-” * 20)
方法 1: 使用 .T 属性
arr_2d_T = arr_2d.T
print(“使用 .T 属性转置:”)
print(arr_2d_T)
print(“转置后形状:”, arr_2d_T.shape)
print(“-” * 20)
方法 2: 使用 transpose() 方法 (默认参数)
arr_2d_transpose_default = arr_2d.transpose()
print(“使用 transpose() 方法 (默认) 转置:”)
print(arr_2d_transpose_default)
print(“转置后形状:”, arr_2d_transpose_default.shape)
print(“-” * 20)
方法 3: 使用 transpose() 方法 (指定 axes)
对于二维数组,默认转置是将轴 0 和轴 1 互换
原始轴顺序是 (0, 1),转置后变成 (1, 0)
arr_2d_transpose_axes = arr_2d.transpose((1, 0))
print(“使用 transpose() 方法 (指定 axes=(1, 0)) 转置:”)
print(arr_2d_transpose_axes)
print(“转置后形状:”, arr_2d_transpose_axes.shape)
print(“-” * 20)
方法 4: 使用 np.transpose() 函数
arr_2d_np_transpose = np.transpose(arr_2d) # 默认
arr_2d_np_transpose_axes = np.transpose(arr_2d, axes=(1, 0)) # 指定 axes
print(“使用 np.transpose() 函数转置:”)
print(arr_2d_np_transpose)
print(“转置后形状:”, arr_2d_np_transpose.shape)
print(“-” * 20)
print(“使用 np.transpose() 函数 (指定 axes=(1, 0)) 转置:”)
print(arr_2d_np_transpose_axes)
print(“转置后形状:”, arr_2d_np_transpose_axes.shape)
“`
输出解释:
原始数组 arr_2d
的形状是 (2, 3)
。它有 2 行和 3 列。
转置后,数组的形状变为 (3, 2)
。原始的第 0 行 [1, 2, 3]
变成了新的第 0 列,原始的第 1 行 [4, 5, 6]
变成了新的第 1 列。原始的列 [1, 4]
, [2, 5]
, [3, 6]
变成了新的行。
可以看到,对于二维数组,.T
属性、transpose()
方法的默认调用以及 transpose(axes=(1, 0))
都实现了标准的矩阵转置。这是因为对于二维数组,默认转置就是将原始的轴顺序 (0, 1)
变为 (1, 0)
。
4. 理解 transpose()
方法和 axes
参数
arr.transpose(*axes)
或 np.transpose(arr, axes=None)
是进行多维数组转置的核心工具。
arr
: 需要转置的 NumPy 数组。-
axes
: 这是一个可选参数,可以是一个整数元组或列表。它指定了数组轴的新顺序。- 如果
axes
为None
(默认值),则transpose()
会反转数组轴的顺序。例如,一个形状为(d0, d1, d2)
的三维数组,默认转置后形状变为(d2, d1, d0)
,新的轴顺序是(2, 1, 0)
。一个形状为(d0, d1, d2, d3)
的四维数组,默认转置后形状变为(d3, d2, d1, d0)
,新的轴顺序是(3, 2, 1, 0)
。 - 如果提供了
axes
参数,它必须是一个包含从 0 到N-1
(其中N
是数组的维度)的所有整数的排列的元组或列表。这个元组的长度必须等于数组的维度。元组中元素的顺序定义了新的轴顺序。- 例如,对于一个形状为
(d0, d1, d2)
的三维数组,原始轴顺序是(0, 1, 2)
。 - 如果我们指定
axes=(1, 0, 2)
:- 这意味着原始的轴 1 变成了新的轴 0。新的数组的第一个维度将对应于原始数组的轴 1 的大小 (
d1
)。 - 原始的轴 0 变成了新的轴 1。新的数组的第二个维度将对应于原始数组的轴 0 的大小 (
d0
)。 - 原始的轴 2 变成了新的轴 2。新的数组的第三个维度将对应于原始数组的轴 2 的大小 (
d2
)。 - 转置后的数组形状将是
(d1, d0, d2)
。
- 这意味着原始的轴 1 变成了新的轴 0。新的数组的第一个维度将对应于原始数组的轴 1 的大小 (
- 如果我们指定
axes=(2, 0, 1)
:- 原始的轴 2 变成新的轴 0 (大小
d2
)。 - 原始的轴 0 变成新的轴 1 (大小
d0
)。 - 原始的轴 1 变成新的轴 2 (大小
d1
)。 - 转置后的数组形状将是
(d2, d0, d1)
。
- 原始的轴 2 变成新的轴 0 (大小
- 例如,对于一个形状为
- 如果
理解 axes
元组的工作原理是关键:axes[i]
指定了原始数组中的哪个轴将成为转置后数组的第 i
个轴。
5. 高维数组的转置 (详细示例)
现在,让我们看一些三维和更高维数组的转置示例,重点演示 axes
参数的作用。
5.1 三维数组转置 (默认)
默认转置反转轴顺序。
“`python
import numpy as np
创建一个三维数组 (形状 2x3x4)
轴 0 长度为 2 (想象成 2 个“层”)
轴 1 长度为 3 (每层有 3 行)
轴 2 长度为 4 (每行有 4 列)
arr_3d = np.arange(2 * 3 * 4).reshape((2, 3, 4))
print(“原始三维数组 (形状 2x3x4):”)
print(arr_3d)
print(“原始形状:”, arr_3d.shape)
print(“原始轴顺序:”, tuple(range(arr_3d.ndim)))
print(“-” * 20)
默认转置 (反转轴顺序: 0, 1, 2 -> 2, 1, 0)
等价于 arr_3d.transpose() 或 arr_3d.transpose((2, 1, 0))
arr_3d_T = arr_3d.T
print(“默认转置 (使用 .T 属性):”)
print(arr_3d_T)
print(“转置后形状:”, arr_3d_T.shape)
print(“新的轴顺序对应原始轴:”, (2, 1, 0)) # 原始轴 2 变成新轴 0, 原始轴 1 变成新轴 1, 原始轴 0 变成新轴 2
print(“-” * 20)
验证默认转置等同于指定 axes=(2, 1, 0)
arr_3d_transpose_default = arr_3d.transpose()
arr_3d_transpose_axes_reversed = arr_3d.transpose((2, 1, 0))
print(“使用 transpose() 默认转置:”)
print(arr_3d_transpose_default)
print(“形状:”, arr_3d_transpose_default.shape)
print(“-” * 10)
print(“使用 transpose((2, 1, 0)) 转置:”)
print(arr_3d_transpose_axes_reversed)
print(“形状:”, arr_3d_transpose_axes_reversed.shape)
print(“-” * 20)
检查结果是否相同
print(“默认转置与 axes=(2, 1, 0) 转置结果是否相同:”, np.array_equal(arr_3d_transpose_default, arr_3d_transpose_axes_reversed))
“`
输出解释:
原始数组 arr_3d
的形状是 (2, 3, 4)
。轴顺序是 (0, 1, 2)
。
默认转置后,形状变为 (4, 3, 2)
。这是因为轴顺序被反转为 (2, 1, 0)
:
* 新的轴 0 对应于原始的轴 2 (大小 4)。
* 新的轴 1 对应于原始的轴 1 (大小 3)。
* 新的轴 2 对应于原始的轴 0 (大小 2)。
.T
属性对于二维数组是标准的矩阵转置 (0, 1 -> 1, 0),但对于高维数组是反转轴顺序 (0, 1, …, N-1 -> N-1, …, 1, 0)。这一点需要注意。
5.2 三维数组转置 (指定 axes
)
现在,让我们通过指定 axes
参数来尝试不同的轴顺序。
“`python
import numpy as np
沿用上面创建的三维数组 arr_3d (形状 2x3x4)
arr_3d = np.arange(2 * 3 * 4).reshape((2, 3, 4))
print(“原始三维数组 (形状 2x3x4):”)
print(arr_3d)
print(“原始形状:”, arr_3d.shape)
print(“原始轴顺序:”, tuple(range(arr_3d.ndim)))
print(“-” * 20)
示例 1: 将原始的轴 1 和轴 2 互换,轴 0 不变
原始顺序 (0, 1, 2) -> 新顺序 (0, 2, 1)
arr_3d_transposed_021 = arr_3d.transpose((0, 2, 1))
print(“转置 (axes=(0, 2, 1)):”)
print(arr_3d_transposed_021)
print(“转置后形状:”, arr_3d_transposed_021.shape)
print(“新的轴顺序对应原始轴:”, (0, 2, 1))
新形状: 原始轴 0 的大小 (2), 原始轴 2 的大小 (4), 原始轴 1 的大小 (3)
print(“预期新形状:”, (arr_3d.shape[0], arr_3d.shape[2], arr_3d.shape[1]))
print(“-” * 20)
示例 2: 将原始的轴 0 变成新轴 1,原始的轴 1 变成新轴 0,轴 2 不变
原始顺序 (0, 1, 2) -> 新顺序 (1, 0, 2)
arr_3d_transposed_102 = arr_3d.transpose((1, 0, 2))
print(“转置 (axes=(1, 0, 2)):”)
print(arr_3d_transposed_102)
print(“转置后形状:”, arr_3d_transposed_102.shape)
print(“新的轴顺序对应原始轴:”, (1, 0, 2))
新形状: 原始轴 1 的大小 (3), 原始轴 0 的大小 (2), 原始轴 2 的大小 (4)
print(“预期新形状:”, (arr_3d.shape[1], arr_3d.shape[0], arr_3d.shape[2]))
print(“-” * 20)
示例 3: 完全不同的顺序
原始顺序 (0, 1, 2) -> 新顺序 (2, 0, 1)
arr_3d_transposed_201 = arr_3d.transpose((2, 0, 1))
print(“转置 (axes=(2, 0, 1)):”)
print(arr_3d_transposed_201)
print(“转置后形状:”, arr_3d_transposed_201.shape)
print(“新的轴顺序对应原始轴:”, (2, 0, 1))
新形状: 原始轴 2 的大小 (4), 原始轴 0 的大小 (2), 原始轴 1 的大小 (3)
print(“预期新形状:”, (arr_3d.shape[2], arr_3d.shape[0], arr_3d.shape[1]))
print(“-” * 20)
“`
输出解释:
通过 axes
参数,我们可以任意指定原始轴到新轴的映射关系,从而实现灵活的维度重排。转置后的数组形状总是由原始数组对应轴的大小按 axes
指定的顺序排列构成。
axes=(0, 2, 1)
意味着新数组的轴 0 是原始数组的轴 0,新轴 1 是原始轴 2,新轴 2 是原始轴 1。形状从(2, 3, 4)
变为(2, 4, 3)
。axes=(1, 0, 2)
意味着新数组的轴 0 是原始数组的轴 1,新轴 1 是原始轴 0,新轴 2 是原始轴 2。形状从(2, 3, 4)
变为(3, 2, 4)
。axes=(2, 0, 1)
意味着新数组的轴 0 是原始数组的轴 2,新轴 1 是原始轴 0,新轴 2 是原始轴 1。形状从(2, 3, 4)
变为(4, 2, 3)
。
5.3 跟踪元素位置
为了更好地理解 axes
参数如何影响元素位置,我们来跟踪一个具体的元素。
考虑原始数组 arr_3d
(形状 2x3x4)。元素 arr_3d[i, j, k]
表示位于:
* 第 i
个“层” (索引 i
在轴 0 上)
* 第 j
行 (索引 j
在轴 1 上)
* 第 k
列 (索引 k
在轴 2 上)
假设我们选择元素 arr_3d[0, 1, 2]
。它的值是 arr_3d[0][1][2]
,即 0 * 12 + 1 * 4 + 2 = 6 (从 0 开始计数)。
现在考虑 axes=(1, 0, 2)
的转置结果 arr_3d_transposed_102
(形状 3x2x4)。
新的轴顺序是 (原始轴 1, 原始轴 0, 原始轴 2)。
一个元素在转置后的数组中的新索引 (new_i, new_j, new_k)
与它在原始数组中的旧索引 (old_i, old_j, old_k)
之间的关系是:
new_i
对应原始轴 1 的旧索引 (old_j
)
new_j
对应原始轴 0 的旧索引 (old_i
)
new_k
对应原始轴 2 的旧索引 (old_k
)
所以,原始元素 arr_3d[old_i, old_j, old_k]
将位于转置后的数组的 arr_3d_transposed_102[old_j, old_i, old_k]
。
让我们跟踪 arr_3d[0, 1, 2]
(值为 6):
* 原始索引 (old_i, old_j, old_k)
是 (0, 1, 2)
。
* 使用 axes=(1, 0, 2)
的映射规则:
* 新轴 0 是原始轴 1,其索引是 old_j
= 1。
* 新轴 1 是原始轴 0,其索引是 old_i
= 0。
* 新轴 2 是原始轴 2,其索引是 old_k
= 2。
* 因此,元素 6 将位于 arr_3d_transposed_102[1, 0, 2]
。
让我们验证一下:
“`python
import numpy as np
arr_3d = np.arange(2 * 3 * 4).reshape((2, 3, 4))
arr_3d_transposed_102 = arr_3d.transpose((1, 0, 2))
print(“原始数组元素 arr_3d[0, 1, 2]:”, arr_3d[0, 1, 2])
print(“转置后数组元素 arr_3d_transposed_102[1, 0, 2]:”, arr_3d_transposed_102[1, 0, 2])
也可以直接查看转置后数组的切片来验证
原始轴 1 的第一个切片 (索引 1) 变成了新轴 0 的第二个切片 (索引 1)
这个切片对应原始数组的 arr_3d[:, 1, :]
print(“\n原始数组在轴 1 索引 1 处的切片 (形状 2×4):”)
print(arr_3d[:, 1, :])
print(“\n转置后数组在新轴 0 索引 1 处的切片 (形状 2×4):”)
print(arr_3d_transposed_102[1, :, :]) # 新轴 0 索引 1, 对应原始轴 1 索引 1
在这个切片 (形状 2×4) 中,原始轴 0 (旧的行) 变成了新轴 1 (新的行),原始轴 2 (旧的列) 变成了新轴 2 (新的列)
原始切片 arr_3d[:, 1, :] 的形状是 (2, 4),其轴是 (0, 2)
转置后 arr_3d_transposed_102[1, :, :] 的形状是 (2, 4),其轴是 (0, 2) 在 (1, 0, 2) 的规则下
原始索引 (0, 1, 2) -> 新索引 (1, 0, 2)
元素 arr_3d[0, 1, 2] 在切片 arr_3d[:, 1, :] 中的索引是 (0, 2)。
在转置后切片 arr_3d_transposed_102[1, :, :] 中,这个元素的新索引是 (0, 2)。
这是因为在 axes=(1, 0, 2) 中,原始轴 0 变成新轴 1 (切片中的新轴 0),原始轴 2 变成新轴 2 (切片中的新轴 1)。
原始切片 (轴 0, 轴 2) -> 新切片 (轴 0 对应原始轴 0, 轴 1 对应原始轴 2)
所以元素在切片内的索引 (i, k) 保持不变。
元素的全局索引 (old_i, old_j, old_k) -> (new_i, new_j, new_k)
(0, 1, 2) -> (1, 0, 2)
在切片 arr_3d[:, 1, :] (轴 0, 轴 2) 中,索引是 (0, 2)
在切片 arr_3d_transposed_102[1, :, :] (新轴 1, 新轴 2) 中,索引是 (0, 2)
因为新轴 1 对应原始轴 0 (索引 0), 新轴 2 对应原始轴 2 (索引 2)
``
arr_3d[0, 1, 2]
从输出可以看到,的值确实是 6,而
arr_3d_transposed_102[1, 0, 2]` 的值也是 6。这验证了我们的元素位置映射规则。
简而言之,如果原始数组的维度是 $N$,形状是 $(d_0, d_1, \dots, d_{N-1})$,转置时使用了 axes=(p_0, p_1, \dots, p_{N-1})
,其中 $(p_0, p_1, \dots, p_{N-1})$ 是 $(0, 1, \dots, N-1)$ 的一个排列。那么:
* 转置后数组的形状是 $(d_{p_0}, d_{p_1}, \dots, d_{p_{N-1}})$。
* 原始数组中的元素位于索引 $(i_0, i_1, \dots, i_{N-1})$,在转置后数组中的新索引将是 $(i_{p_0}, i_{p_1}, \dots, i_{p_{N-1}})$。请注意,这里的索引是按照原始轴的顺序提取的! 也就是说,如果 axes=(1, 0, 2)
, 原始索引 (i, j, k)
,新的索引是 (i_1, i_0, i_2)
其中 i_0=i, i_1=j, i_2=k
。所以新索引是 (j, i, k)
。这个理解更容易一些。
让我们再次用这个更直观的理解来验证 axes=(1, 0, 2)
和原始索引 (0, 1, 2)
:
* 原始索引 (i, j, k) = (0, 1, 2)
* axes=(1, 0, 2)
告诉我们新轴 0 对应原始轴 1 (其索引是 j=1
),新轴 1 对应原始轴 0 (其索引是 i=0
),新轴 2 对应原始轴 2 (其索引是 k=2
)。
* 因此,元素的新索引是 (j, i, k) = (1, 0, 2)
。这与我们之前的验证一致。
5.4 四维数组示例
“`python
import numpy as np
创建一个四维数组 (形状 1x2x3x4)
轴 0: 1 (例如,批次大小)
轴 1: 2 (例如,通道数)
轴 2: 3 (例如,高度)
轴 3: 4 (例如,宽度)
arr_4d = np.arange(1 * 2 * 3 * 4).reshape((1, 2, 3, 4))
print(“原始四维数组 (形状 1x2x3x4):”)
print(arr_4d)
print(“原始形状:”, arr_4d.shape)
print(“原始轴顺序:”, tuple(range(arr_4d.ndim)))
print(“-” * 20)
默认转置 (反转轴顺序: 0, 1, 2, 3 -> 3, 2, 1, 0)
arr_4d_T = arr_4d.T
print(“默认转置 (使用 .T 属性):”)
print(arr_4d_T.shape) # 形状 (4, 3, 2, 1)
print(“-” * 20)
指定 axes=(0, 3, 1, 2)
原始顺序 (0, 1, 2, 3) -> 新顺序 (0, 3, 1, 2)
新形状: 原始轴 0 大小 (1), 原始轴 3 大小 (4), 原始轴 1 大小 (2), 原始轴 2 大小 (3)
arr_4d_transposed_0312 = arr_4d.transpose((0, 3, 1, 2))
print(“转置 (axes=(0, 3, 1, 2)):”)
print(arr_4d_transposed_0312.shape) # 形状 (1, 4, 2, 3)
print(“新的轴顺序对应原始轴:”, (0, 3, 1, 2))
print(“预期新形状:”, (arr_4d.shape[0], arr_4d.shape[3], arr_4d.shape[1], arr_4d.shape[2]))
print(“-” * 20)
跟踪元素:原始 arr_4d[0, 1, 2, 3] (值为 123*4-1 = 23)
原始索引 (i, j, k, l) = (0, 1, 2, 3)
axes=(0, 3, 1, 2)
新索引: (原始轴 0 索引, 原始轴 3 索引, 原始轴 1 索引, 原始轴 2 索引)
新索引: (i, l, j, k) = (0, 3, 1, 2)
print(“原始数组元素 arr_4d[0, 1, 2, 3]:”, arr_4d[0, 1, 2, 3])
print(“转置后数组元素 arr_4d_transposed_0312[0, 3, 1, 2]:”, arr_4d_transposed_0312[0, 3, 1, 2])
“`
输出解释:
四维数组的转置遵循同样的规则。通过 axes=(0, 3, 1, 2)
,我们将原始形状 (1, 2, 3, 4)
的数组变成了形状 (1, 4, 2, 3)
的数组。这在处理图像数据时非常有用,例如将常见的 (batch_size, height, width, channels)
格式转换为某些深度学习框架或库所需的 (batch_size, channels, height, width)
格式。这可以通过 axes=(0, 3, 1, 2)
来实现。
6. .T
属性 vs transpose()
方法
总结一下 .T
属性和 transpose()
方法(或 np.transpose()
函数)的区别:
.T
属性:只能执行默认转置。对于二维数组是标准的矩阵转置(交换轴 0 和轴 1)。对于更高维度的数组,它总是反转轴的顺序。transpose()
方法/函数:可以执行默认转置(当axes=None
时),也可以通过axes
参数执行任意自定义的轴重排。
推荐使用:
* 对于二维数组的标准矩阵转置,使用 .T
属性更简洁。
* 对于高维数组需要反转轴顺序,使用 .T
属性也很方便。
* 对于高维数组需要进行非默认的、自定义顺序的轴重排,必须使用 transpose()
方法或 np.transpose()
函数并指定 axes
参数。
“`python
import numpy as np
二维数组
arr_2d = np.arange(6).reshape(2, 3)
print(“2D 数组:”)
print(arr_2d)
print(“.T 结果:”)
print(arr_2d.T)
print(“transpose() 结果 (默认):”)
print(arr_2d.transpose())
print(“transpose((1, 0)) 结果:”)
print(arr_2d.transpose((1, 0)))
print(“-” * 30)
三维数组
arr_3d = np.arange(24).reshape(2, 3, 4)
print(“3D 数组:”)
print(arr_3d.shape)
print(“.T 形状:”, arr_3d.T.shape) # 反转 (0, 1, 2) -> (2, 1, 0)
print(“transpose() 形状 (默认):”, arr_3d.transpose().shape) # 反转
print(“transpose((2, 1, 0)) 形状:”, arr_3d.transpose((2, 1, 0)).shape) # 反转
print(“transpose((0, 2, 1)) 形状:”, arr_3d.transpose((0, 2, 1)).shape) # 自定义
“`
7. transpose()
的返回值:视图还是副本?
transpose()
方法通常返回原始数组的一个视图 (view)。这意味着转置后的数组并没有开辟新的内存来存储数据,而是共享原始数组的数据。修改转置后的数组通常会影响原始数组的数据。
然而,在某些复杂的情况下,如果新的轴顺序导致数据的内存布局变得不连续,NumPy 可能会在内部创建一个数据的副本 (copy) 以确保正确的内存访问。但即使返回了副本,transpose()
的主要设计理念是提供一个数据的不同“视角”。
重要的是要记住:
1. transpose()
不会修改原始数组(它返回一个新的数组,这个新数组是视图或副本)。
2. 修改 transpose()
返回的数组通常会修改原始数组(因为它们共享数据)。
“`python
import numpy as np
arr = np.array([[1, 2], [3, 4]])
arr_T = arr.T
print(“原始数组:”)
print(arr)
print(“转置数组:”)
print(arr_T)
print(“-” * 20)
修改转置后的数组的一个元素
arr_T[0, 1] = 99
print(“修改转置数组后:”)
print(“原始数组:”)
print(arr) # 原始数组也被修改了
print(“转置数组:”)
print(arr_T)
print(“-” * 20)
使用 np.shares_memory() 检查是否共享内存 (通常会是 True)
print(“原始数组和转置数组是否共享内存:”, np.shares_memory(arr, arr_T))
“`
这个特性对于处理大型数组非常重要,因为它避免了不必要的数据复制,提高了效率。但同时也要求我们在修改转置结果时要小心,以免意外修改了原始数据。
8. transpose()
的应用场景
转置是多维数组操作中的基本功,广泛应用于各种数值计算和数据处理任务:
- 线性代数:
- 矩阵乘法:许多线性代数运算(如矩阵乘法
@
或np.dot()
) 在输入矩阵需要特定方向(例如行向量或列向量)时,会用到转置。例如,计算矩阵 $A$ 和矩阵 $B$ 的转置的乘积 $A B^T$ 需要对 $B$ 进行转置。
- 矩阵乘法:许多线性代数运算(如矩阵乘法
- 数据预处理和特征工程:
- 改变数据维度顺序以符合算法输入要求。例如,某些机器学习库可能要求数据形状是
(n_samples, n_features)
,而您的数据可能是(n_features, n_samples)
,这时就需要转置。
- 改变数据维度顺序以符合算法输入要求。例如,某些机器学习库可能要求数据形状是
- 图像处理:
- 图像数据通常以
(height, width, channels)
或(batch_size, height, width, channels)
的形式存储。某些图像处理操作或深度学习模型可能需要(channels, height, width)
或(batch_size, channels, height, width)
的格式。这时就需要使用transpose()
来调整轴的顺序。例如,将(H, W, C)
转为(C, H, W)
可以使用arr.transpose((2, 0, 1))
。
- 图像数据通常以
- 广播 (Broadcasting) 的辅助:
- 在某些需要广播操作的场景下,通过转置可以调整数组的形状,使其能够与另一个数组进行兼容的广播运算。
- 物理和工程模拟:
- 处理多维物理场数据、网格数据等时,经常需要改变数据的维度布局以方便计算或可视化。
9. transpose()
与 reshape()
的区别
新手有时会混淆 transpose()
和 reshape()
。虽然它们都能改变数组的形状,但它们的原理和用途是不同的:
transpose()
: 通过改变轴的顺序来重新排列数组的维度。它保留了沿每个轴的元素的原始相对顺序。元素的总数和原始数据没有变化,只是从不同的“视角”去看待它。它通常返回一个视图。reshape()
: 通过重新解释数组的基础数据来改变数组的形状。它需要指定一个新的形状,这个新形状的总元素数必须与原始数组的总元素数相同。reshape()
可以返回一个视图或一个副本,取决于是否能够以新形状和原始内存布局兼容。reshape()
不关心原始的轴顺序,它只是将一维数据(或者说线性化的数据)按照新的维度划分。
举例说明:
“`python
import numpy as np
arr = np.arange(6).reshape(1, 2, 3)
print(“原始数组 (1x2x3):”)
print(arr)
转置 (axes=(0, 2, 1)): (1, 2, 3) -> (1, 3, 2)
arr_transposed = arr.transpose((0, 2, 1))
print(“\n转置后数组 (1x3x2, axes=(0, 2, 1)):”)
print(arr_transposed)
原始 arr[0, 0, 1] = 1
新索引 (0, 1, 0) -> arr_transposed[0, 0, 1]
原始 arr[0, 1, 2] = 5
新索引 (0, 2, 1) -> arr_transposed[0, 2, 1]
print(“arr[0, 0, 1] =”, arr[0, 0, 1])
print(“arr_transposed[0, 1, 0] =”, arr_transposed[0, 1, 0]) # 原始轴 2 索引 0, 原始轴 1 索引 1
print(“arr_transposed[0, 2, 1] =”, arr_transposed[0, 2, 1]) # 原始轴 2 索引 1, 原始轴 1 索引 2
原始 arr[0, 0, 1] 索引 (0,0,1) -> 新索引 (0,1,0) 根据 axes=(0,2,1) 的映射
原始 arr[0, 1, 2] 索引 (0,1,2) -> 新索引 (0,2,1)
print(“-” * 20)
重塑: 将 (1, 2, 3) 重塑为 (3, 2)
arr_reshaped = arr.reshape(3, 2)
print(“\n重塑后数组 (3×2):”)
print(arr_reshaped)
注意看元素顺序,它只是将 arr.ravel() (即 [0, 1, 2, 3, 4, 5]) 按 3×2 排列
比较转置和重塑的输出可以看出它们是不同的操作。
转置是维度意义上的“旋转”或轴交换,而重塑是简单地将元素重新排列成新的维度结构。
“`
10. 总结
numpy.transpose()
方法/函数是 NumPy 中一个强大且灵活的工具,用于重新排列多维数组的轴。理解“轴”的概念以及 axes
参数如何定义原始轴到新轴的映射是掌握 transpose()
的关键。
.T
属性提供了便捷的默认转置(二维数组为矩阵转置,高维数组为轴序反转)。transpose(axes=...)
提供了对轴顺序完全控制的能力,是处理复杂高维数组转置的必备方法。transpose()
通常返回原始数组的视图,修改转置后的数组会影响原始数组。- 转置与重塑 (
reshape()
) 不同,转置是轴的排列,而重塑是数据的重新组织。
熟练掌握 transpose()
方法将极大地提高您在 NumPy 中处理和操作多维数据的能力。通过多加练习,尝试不同维度和不同 axes
参数的组合,您将能更好地理解其工作原理并在实际应用中游刃有余。
希望这篇详细教程对您理解和使用 NumPy 的 transpose()
方法有所帮助!