Day 101 — NumPy 核心:图解
1. ndarray 内存布局
┌─────────────────────────────────────────────────────────┐
│ Python 对象层 │
│ ndarray 对象: shape, dtype, strides, data pointer │
└───────────────────────┬─────────────────────────────────┘
│ data pointer
↓
┌─────────────────────────────────────────────────────────┐
│ C 层连续内存块 │
│ [int64][int64][int64][int64][int64][int64] │
│ 1 2 3 4 5 6 │
│ offset=0 │
└─────────────────────────────────────────────────────────┘
对比 Python 列表的内存布局:
┌─────────────────────────────────────────────────────────┐
│ [ptr0] [ptr1] [ptr2] [ptr3] [ptr4] [ptr5] │
│ │ │ │ │ │ │ │
│ ↓ ↓ ↓ ↓ ↓ ↓ │
│ [PyInt] [PyInt] [PyInt] [PyInt] [PyInt] [PyInt] │
│ (每个都是独立的 Python 对象,开销大) │
└─────────────────────────────────────────────────────────┘
2. 广播机制可视化
场景:(3, 4) 数组 + (4,) 数组
数组 A shape: (3, 4)
┌──────────────────────┐
│ 1 2 3 4 │
│ 5 6 7 8 │
│ 9 10 11 12 │
└──────────────────────┘
数组 B shape: (4,)
┌──────────────────────┐
│ 10 20 30 40 │
└──────────────────────┘
↓ B 向左扩展为 (1, 4)
┌──────────────────────┐
│ 10 20 30 40 │
│ 10 20 30 40 │ ← 复制 3 次
│ 10 20 30 40 │
└──────────────────────┘
↓ 逐元素相加
┌──────────────────────┐
│ 11 22 33 44 │
│ 15 26 37 48 │
│ 19 30 41 52 │
└──────────────────────┘
3. 轴(Axis)概念图
3D 数组 shape = (2, 3, 4)
axis=0 (深度层) axis=1 (行) axis=2 (列)
│ │ │
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ layer0 │ │ row 0 │ │ col 0 1 2 3│
│ 3 x 4 │ │ 1 x 4 │ └──────────┘
├──────────┤ ├──────────┤
│ layer1 │ │ row 1 │
│ 3 x 4 │ │ 1 x 4 │
└──────────┘ ├──────────┤
│ row 2 │
│ 1 x 4 │
└──────────┘
arr.sum(axis=0) → 沿深度方向求和,结果 shape: (3, 4)
arr.sum(axis=1) → 沿行方向求和,结果 shape: (2, 4)
arr.sum(axis=2) → 沿列方向求和,结果 shape: (2, 3)
4. 视图 vs 副本
切片(基础索引)→ 视图(共享内存)
┌────────────────────┐
│ 原始数组 │ ← data: 1,2,3,4,5,6
│ [1,2,3,4,5,6] │
└────────┬───────────┘
│ arr[0:3]
↓
┌────────────────────┐
│ 视图 │ ← data: 同一块内存
│ [1,2,3] │ 修改会反映到原数组!
└────────────────────┘
布尔索引 / 花式索引 → 副本(独立内存)
┌────────────────────┐
│ 原始数组 │ ← data: 1,2,3,4,5,6
│ [1,2,3,4,5,6] │
└────────┬───────────┘
│ arr[[0,2,4]] 或 arr[arr>2]
↓
┌────────────────────┐
│ 副本 │ ← data: 新分配的内存
│ [1,3,5] │ 修改不影响原数组
└────────────────────┘
5. 距离计算的三种方法
方法 1: 双重循环 O(n² × d)
────────────────────────
for i in range(n):
for j in range(n):
dist[i,j] = ||P[i] - P[j]||
方法 2: 向量化循环 O(n × d + n × n)
────────────────────────
for i in range(n):
dist[i] = ||P[i] - P[:]|| # 广播
方法 3: 全向量化 O(n² + n × d) ← 最快
────────────────────────
利用恒等式: ||a-b||² = ||a||² + ||b||² - 2a·b
步骤:
1. sq = sum(P², axis=1) # (n,) 各点模的平方
2. dist² = sq[:,None] + sq[None,:] - 2 * P @ P.T
3. dist = sqrt(dist²)
┌───────────┐
│ P @ P.T │ 矩阵乘法,利用 BLAS 加速
│ (n × n) │
└───────────┘