向量化:NumPy 的核心思想

用数组操作替代循环——同样的事,循环写法慢 100 倍:

import numpy as np
import time

n = 1_000_000

# 慢:循环
t = time.time()
result = [i ** 2 for i in range(n)]
print(f"循环: {time.time()-t:.3f}s")     # ~0.5s

# 快:NumPy
a = np.arange(n)
t = time.time()
result = a ** 2
print(f"NumPy: {time.time()-t:.3f}s")    # ~0.005s

NumPy 底层是 C 实现,跳过了 Python 解释器开销。

广播:不同形状的数组怎么运算

a = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])
b = np.array([10, 20, 30])

a + b
# [[11 22 33]
#  [14 25 36]
#  [17 28 39]]

a 是 (3, 3),b 是 (3,)——NumPy 自动把 b "广播"到每一行。

广播规则(简单版)

形状从右往左对齐,对应维度满足下面任一条件就行:

  • 维度大小相等
  • 一方维度是 1
  • 一方没这个维度
np.zeros((3, 4)) + np.ones((4,))         # OK
np.zeros((3, 4)) + np.ones((3, 1))       # OK
np.zeros((3, 4)) + np.ones((3,))         # 报错(最右维不匹配)

实战:归一化(每列减均值除标准差)

data = np.random.randn(100, 5)            # 100 行 × 5 列
mean = data.mean(axis=0)                  # 每列均值,shape (5,)
std = data.std(axis=0)                     # 每列标准差,shape (5,)

normalized = (data - mean) / std           # 广播自动按列处理

不写循环——一行搞定。

np.where:条件赋值

a = np.array([-1, 2, -3, 4, -5])
np.where(a > 0, a, 0)        # [0 2 0 4 0]   正数保留,负数变 0

类似三元运算符,但对整个数组生效。

高级索引

a = np.array([10, 20, 30, 40, 50])

# 用整数数组取多个位置
a[[0, 2, 4]]            # [10 30 50]

# 用布尔数组
a[a > 25]               # [30 40 50]

# 修改满足条件的元素
a[a > 25] = 0
print(a)                # [10 20 0 0 0]

数学库

np.sin(a) / np.cos(a) / np.exp(a) / np.log(a)
np.sum(a) / np.mean(a) / np.std(a) / np.var(a)
np.dot(a, b)            # 矩阵乘法(也可用 @ 运算符)
a @ b                   # 同上,更地道

节省内存:原地操作

a += 1                  # 比 a = a + 1 省内存(不建中间数组)
np.add(a, b, out=a)     # 显式原地

NumPy vs PyTorch Tensor

很多 NumPy 操作在 PyTorch 里几乎完全一样

import numpy as np
import torch

a = np.array([1, 2, 3])
b = torch.tensor([1, 2, 3])

a.shape == b.shape          # 都有 shape
a.sum() == b.sum().item()   # 都有 sum

学完 NumPy = 顺带学了 80% 的 PyTorch Tensor——这就是路径设计的妙处。

下一篇讲 Pandas。