← 返回 Day 101 正文

Day 101 · 图解

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)   │
       └───────────┘