← 返回 Day 79 正文

Day 79 · 图解

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