向量化: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。