Day 079 — NumPy 基础:图解
1. NumPy 内存布局对比
Python 列表 vs NumPy 数组
Python 列表 (内存不连续):
┌─────────┬─────────┬─────────┬─────────┐
│ 0x1000 │ 0x2000 │ 0x3000 │ 0x4000 │ ← 指针数组
└────┬────┴────┬────┴────┬────┴────┬────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ int │ │ int │ │ int │ │ int │ ← 每个元素是独立对象
│ obj │ │ obj │ │ obj │ │ obj │ 包含类型信息、引用计数等
│ 28B │ │ 28B │ │ 28B │ │ 28B │
└──────┘ └──────┘ └──────┘ └──────┘
总占用: 16B(指针) + 4×28B(对象) = 128B
NumPy 数组 (内存连续):
┌─────────────────────────────────────┐
│ 0x1000 │ ← 数据直接存储
│ ┌────┬────┬────┬────┐ │
│ │ 1 │ 2 │ 3 │ 4 │ │ ← 连续内存块
│ │ 8B │ 8B │ 8B │ 8B │ │ 同一类型
│ └────┴────┴────┴────┘ │
└─────────────────────────────────────┘
总占用: 32B (8B × 4个元素)
为什么连续内存更快?
CPU 缓存行 (通常 64 字节):
┌──────────────────────────────────────────────┐
│ NumPy: [1, 2, 3, 4, 5, 6, 7, 8] ← 一次缓存加载
└──────────────────────────────────────────────┘
→ 一次加载,连续访问,缓存命中率高 ✅
┌──────────────────────────────────────────────┐
│ Python: [ptr1, ptr2, ptr3, ptr4, ...] │
│ ↓ ↓ ↓ ↓ │
│ obj1 obj2 obj3 obj4 ← 4次跳转
└──────────────────────────────────────────────┘
→ 频繁跳转,缓存命中率低 ❌
2. 广播机制图解
情况1: 标量广播
a: [1, 2, 3] shape: (3,)
b: 5 shape: ()
b 被广播为: [5, 5, 5]
结果: [1+5, 2+5, 3+5] = [6, 7, 8]
情况2: 列向量广播
a: [[1, 2, 3], shape: (2, 3)
[4, 5, 6]]
b: [[10], shape: (2, 1)
[20]]
b 被广播为: [[10, 10, 10],
[20, 20, 20]]
结果: [[11, 12, 13],
[24, 25, 26]]
广播规则流程图
两个数组的形状
│
▼
┌─────────────────────┐
│ 从右往左逐维度比较 │
└─────────┬───────────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
维度相等 一个为1 都不满足
直接运算 扩展为另一 ───────┐
│ 个的大小 │
│ │ │
▼ ▼ ▼
正常计算 正常计算 ValueError
3. 布尔索引图解
原始数组: [15, 22, 8, 35, 12, 40, 5]
布尔掩码: [ F, T, F, T, F, T, F] (条件: > 20)
↓ ↓ ↓ ↓ ↓ ↓ ↓
筛选结果: [22, 35, 40] (只保留 True 对应的元素)
多条件组合
条件1 (>10): [ T, T, F, T, T, T, F]
条件2 (<30): [ T, T, T, F, T, F, T]
─────────────────────────────
AND 运算: [ T, T, F, F, T, F, F]
结果: [15, 22, 12 ] → 在 (10, 30) 范围内
4. 轴 (axis) 概念图解
二维数组:
列0 列1 列2
┌────┬────┬────┐
行0 │ 1 │ 2 │ 3 │ ← axis=1 (行方向)
├────┼────┼────┤
行1 │ 4 │ 5 │ 6 │
└────┴────┴────┘
↑
axis=0 (列方向)
axis=0: 沿列方向操作 → 结果是每行的统计值 (压缩行)
axis=1: 沿行方向操作 → 结果是每列的统计值 (压缩列)
arr.mean(axis=0) → [2.5, 3.5, 4.5] 每列的均值
arr.mean(axis=1) → [2.0, 5.0] 每行的均值
5. 视图 vs 副本
切片 (视图):
arr = [1, 2, 3, 4, 5]
view = arr[1:4]
arr ──────┬─────────────┐
│ │
▼ │
[1, 2, 3, 4, 5] │
↑ ↑ │
修改 view 也跟着变 ←─┘ (共享内存)
copy (副本):
arr2 = [10, 20, 30, 40, 50]
copy = arr2[1:4].copy()
arr2 ───────→ [10, 20, 30, 40, 50] (独立内存)
copy ───────→ [20, 30, 40] (独立内存)
↑
修改不影响原数组
6. 常用 NumPy 函数关系图
NumPy 数组
│
┌───────────────┼───────────────┐
│ │ │
创建操作 形状操作 数学运算
│ │ │
┌────┴────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ zeros │ │ reshape │ │ 统计类 │
│ ones │ │ flatten │ │ mean/std │
│ arange │ │ ravel │ │ sum/min │
│ random │ │ T (转置) │ │ max/argmax│
│ eye │ │ vstack │ ├───────────┤
│ full │ │ hstack │ │ 线性代数 │
└─────────┘ │ split │ │ dot/matmul│
└───────────┘ │ inv/det │
│ solve/eig│
└───────────┘