跳到正文

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 字节、float32 4 字节,大数组优先 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 与一维转置

把一门技术讲透到既懂也会用