numpy.reshape() 改变的是数组的维度结构,而不是元素总数。它可以把一维数据整理成矩阵、增加批次或通道维度,也可以把高维数组重新展平。真正容易出错的地方,是维度乘积、-1 自动推断、元素排列顺序,以及新数组是否与原数组共享内存。
形状可以改变,元素数量不能改变
一个数组能否重塑,取决于新旧形状包含的元素总数是否一致。例如,包含 12 个元素的一维数组,可以变成 (3, 4)、(2, 2, 3) 或 (12, 1),但不能变成 (5, 3)。
下面的示例可以直接运行。运行前安装 NumPy:
python -m pip install numpy
import numpy as np
values = np.arange(12)
matrix = values.reshape(3, 4)
cube = values.reshape(2, 2, 3)
column = values.reshape(12, 1)
print("原始形状:", values.shape)
print("矩阵形状:", matrix.shape)
print(matrix)
print("三维形状:", cube.shape)
print("列向量形状:", column.shape)
如果执行 values.reshape(5, 3),NumPy 会抛出 ValueError,因为 12 个元素无法填满 15 个位置。处理外部数据时,不要只依赖异常信息,可以先验证数量:
import numpy as np
values = np.arange(12)
target_shape = (3, 4)
if np.prod(target_shape) != values.size:
raise ValueError("目标形状与元素数量不匹配")
result = values.reshape(target_shape)
print(result)
用 -1 推断一个维度
当某个维度可以由元素总数计算时,可以把它写成 -1。这在样本数量动态变化时尤其有用:
import numpy as np
records = np.arange(24)
# 每条记录包含 6 个值,让 NumPy 推断记录数量。
table = records.reshape(-1, 6)
print(table.shape) # (4, 6)
# 保留第一维,把后面的维度展平。
batch = np.arange(24).reshape(2, 3, 4)
features = batch.reshape(batch.shape[0], -1)
print(features.shape) # (2, 12)
一次重塑中只能出现一个 -1。像 reshape(-1, -1) 这样的写法没有足够信息确定结果,因此会失败。
增加和移除维度
reshape() 不仅能重新分组元素,也能添加长度为 1 的轴。这些轴常用于批处理、广播和机器学习模型输入。
import numpy as np
image = np.arange(12).reshape(3, 4)
# 增加批次维度:(height, width) -> (batch, height, width)
batched = image.reshape(1, 3, 4)
# 增加尾部通道维度:(height, width) -> (height, width, channels)
grayscale = image.reshape(3, 4, 1)
# 再恢复为二维数组。
restored = grayscale.reshape(3, 4)
print(batched.shape) # (1, 3, 4)
print(grayscale.shape) # (3, 4, 1)
print(restored.shape) # (3, 4)
如果意图只是删除所有长度为 1 的轴,np.squeeze() 往往表达得更清楚;如果只是增加轴,np.expand_dims() 或 None 索引也更明确。reshape() 更适合已知完整目标形状的场景。
order 决定元素如何被读取和写入
默认的 order="C" 按最后一个轴变化最快的顺序处理元素。order="F" 则按第一个轴变化最快的顺序处理。两者使用相同的数据,却可能得到不同的二维排列:
import numpy as np
values = np.arange(1, 7)
by_rows = values.reshape(2, 3, order="C")
by_columns = values.reshape(2, 3, order="F")
print("C 顺序:")
print(by_rows)
print("F 顺序:")
print(by_columns)
输出分别为:
C 顺序:
[[1 2 3]
[4 5 6]]
F 顺序:
[[1 3 5]
[2 4 6]]
这里的 order 描述索引遍历和重排规则,不等同于强制结果采用某种底层连续内存布局。读取 Fortran、MATLAB 或某些科学计算程序生成的数据时,应先确认对方使用的维度约定和存储顺序。
共享内存不是可以默认依赖的契约
reshape() 会在条件允许时返回原数组的视图,否则可能创建副本。调用方不应仅凭一次实验就假定两者总是共享数据。需要判断时,可以使用 np.shares_memory():
import numpy as np
source = np.arange(12)
reshaped = source.reshape(3, 4)
print(np.shares_memory(source, reshaped))
reshaped[0, 0] = 99
print(source[0])
# 如果后续代码必须拥有独立数据,应显式复制。
independent = source.reshape(3, 4).copy()
independent[0, 0] = -1
print(source[0])
对切片、转置或非连续数组再次执行 reshape() 时,是否复制更值得关注。性能敏感代码应测量实际内存占用;需要隔离修改时,应明确调用 .copy()。
使用前的检查清单
使用 reshape() 时,可以快速检查以下事项:
- 新形状的维度乘积是否等于
array.size。 - 是否只使用了一个
-1。 - 长度为 1 的轴代表批次、通道还是单纯的占位维度。
- 数据来源是否要求
C或F顺序。 - 后续代码是否会原地修改结果,以及是否需要显式复制。
reshape() 本身并不复杂,复杂的是形状背后的数据语义。把每个轴的含义写清楚,并在接口边界检查 shape,通常比反复修正维度错误更可靠。