主题
10 NumPy 与科学计算
一句话:ndarray 用「连续内存 + 固定 dtype + C 层循环」换速度,代价是所有元素必须同类型、形状必须对齐;理解 shape、dtype、视图与广播,比记 API 更重要。
适用:NumPy 2.x(1.26 也可),CPython 3.11+。默认整数类型与平台有关,跨平台代码要显式写 dtype。
为什么需要
同样对一百万个整数求和,Python list 要逐个解引用对象、走解释器循环;ndarray 把值紧凑排列,用一个 C 循环算完。数据量一大,差距从「慢一点」变成「跑不完」。但 NumPy 的规则也更严格:所有元素同一 dtype,运算前形状要能广播,切片默认共享内存而不是复制。不知道这三条,常见结果是平台之间 dtype 不一致导致溢出、广播报错、改了「副本」却把原数组改坏。
核心机制
shape 与 dtype
python
import numpy as np
a = np.array([1, 2, 3]) # 一维
b = np.array([[1, 2], [3, 4]]) # 二维
c = np.zeros((2, 3), dtype=np.float32)
e = np.linspace(0, 1, 5) # 0 到 1 均匀 5 个点
print(a.shape, b.shape) # (3,) (2, 2)
print(a.dtype) # 2.x 下常见 int64;1.x 的 Windows 是 int32
print(np.array([1, 2, 3], dtype=np.int64).dtype) # int64,跨平台确定默认整数 dtype 与 NumPy 版本、平台都有关:1.x 的 Windows 默认 int32,2.x 起 Windows 也改为 int64。需要跨平台一致或防止溢出时,显式写 dtype=np.int64;内存敏感的大数组用 float32,但要注意精度。
shape 是每一维的长度;AI 里常见 (32, 3, 224, 224)(图像 CHW)、(16, 512)(文本)、(128, 768)(向量)。
向量化:把循环交给 C
python
data = np.array([1.0, 2.0, 3.0])
print(data + 1) # [2. 3. 4.]
print(data ** 2) # [1. 4. 9.]
print(np.sqrt(data)) # [1. 1.414 1.732]
print(np.clip(data, 1.5, 2.5), np.maximum(data, 0)) # [1.5 2. 2.5] [1. 2. 3.]
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.sum()) # 21
print(arr.sum(axis=0)) # [5 7 9]:沿第 0 维合并,得到每列
print(arr.sum(axis=1)) # [6 15]:沿第 1 维合并,得到每行
print(arr.argmax(axis=1)) # [2 2]:每行最大值的列下标axis=k 表示「在第 k 维上做归约」,该维会消失,除非 keepdims=True。argmax(axis=1) 是分类输出取预测类别的常用操作。
索引、切片与视图
python
arr = np.arange(12).reshape(3, 4)
print(arr[1, 2]) # 6
print(arr[:, 1]) # [1 5 9]
print(arr[arr > 5]) # [ 6 7 8 9 10 11]
print(arr[[0, 2]]) # 第 0、2 行
view = arr[:2, :2]
view[0, 0] = 99
print(arr[0, 0]) # 99:切片是视图,共享内存基础切片返回视图,花式索引和布尔索引返回副本。判断是否共享内存看 view.base,需要独立时显式 .copy();reshape 在内存允许时也返回视图,不要假设它一定复制。
广播:形状从右往左对齐
规则:从最后一维开始比较,两维相等或其中一维为 1 才能广播,否则报错。
python
mat = np.array([[1, 2, 3], [4, 5, 6]]) # (2, 3)
row = np.array([10, 20, 30]) # (3,)
col = np.array([[100], [200]]) # (2, 1)
print(mat + row) # row 广播到每一行
print(mat + col) # col 广播到每一列
data = np.array([[1., 2., 3.], [4., 5., 6.]])
row_sums = data.sum(axis=1, keepdims=True) # (2, 1)
print(data / row_sums) # 每行除以自己的和(3,) 和 (3, 1) 看起来元素数相同,广播行为完全不同:前者沿行扩展,后者沿列扩展。广播失败时先打印两边 shape,再决定 reshape 还是 expand_dims。
转置、拼接与文件
python
m = np.arange(6).reshape(2, 3) # (2, 3)
print(m.T.shape) # (3, 2)
v = np.array([1, 2, 3])
print(v.T.shape) # (3,):一维数组转置没有效果
print(v.reshape(-1, 1).shape) # (3, 1):需要列向量时显式 reshape
x = np.array([[1, 2]])
y = np.array([[3, 4]])
print(np.concatenate([x, y], axis=0).shape) # (2, 2)
print(np.concatenate([x, y], axis=1).shape) # (1, 4)
print(np.stack([x, y]).shape) # (2, 1, 2).T 只对二维及以上有转置意义;一维数组的 .T 返回自身,形状不变。需要「一列」时用 reshape(-1, 1) 或 [:, None]。持久化用 np.save / np.load,跨语言交换优先 Parquet 等格式。
最小上手
保存为 numpy_demo.py 后运行,预期输出 int64 (3,) (3, 2)、广播结果和 True:
python
import numpy as np
a = np.array([1, 2, 3], dtype=np.int64)
print(a.dtype, a.shape)
m = np.arange(6).reshape(2, 3)
print(m.T.shape) # (3, 2)
print(m + np.array([10, 20, 30])) # 每行加 [10 20 30]
view = m[0]
view[0] = 99
print(m[0, 0] == 99) # True:视图共享内存
copy = m[0].copy()
copy[0] = 0
print(m[0, 0] == 99) # True:副本不影响原数组
v = np.array([1, 2, 3])
print(v.T.shape, v.reshape(-1, 1).shape) # (3,) (3, 1)生产动作
- dtype 显式化:跨平台或持久化时写
np.int64/np.float32;注意窄整数溢出,累加前先转更宽类型。 - 随机数与内存:新代码用
rng = np.random.default_rng(42),不要依赖全局np.random.seed;float64每元素 8 字节、float324 字节,大数组优先float32,但不要反复转换。 - 视图检查:修改切片前确认是否需要
.copy();用x.base is not None或x.flags.owndata判断。 - 排障三段:
- 现象:广播报错
operands could not be broadcast together→ 命令:print(a.shape, b.shape)→ 判断:从右往左是否每维相等或为 1。 - 现象:结果数值溢出/精度异常 → 命令:
print(a.dtype, a.max(), a.min())→ 判断:是否用了过窄 dtype 或混用 float32/float64。 - 现象:改切片影响了原数组 → 命令:
print(view.base is arr)→ 判断:是否把视图当成了副本。
- 现象:广播报错
边界
- NumPy 要求规则矩形数组;变长序列用 list、pandas 或 object 数组,不要硬塞。
- NumPy 主要跑 CPU;GPU 计算用 PyTorch/CuPy,接口相似但语义有差异。小数组上 NumPy 的调度开销可能高于 Python list,优化前先测量。
.T不是「转成列向量」的通用手段;一维数组用reshape(-1, 1)。np.matrix已不推荐,统一用 ndarray +@。
讲清楚
NumPy 快在连续内存、固定 dtype 和 C 层循环:Python 只描述「做什么」,循环由编译好的代码执行。核心概念是 shape 和 dtype:shape 决定广播能否成立,dtype 决定精度、内存和溢出风险;默认整数类型跨平台不一致,持久化和跨平台代码要显式指定。切片默认是视图,改视图会改原数组,需要独立副本时 .copy()。广播从右往左对齐,相等或为 1 才能扩展;一维数组的 .T 没有效果,转列向量用 reshape(-1, 1)。
适用 NumPy 2.x / CPython 3.11+ | 初稿 2026-08,2026-10 压缩并订正 dtype 与一维转置