从 NumPy 到 PyTorch 张量
约 14 分钟
第一、二章学了 NumPy 数组。PyTorch 的张量(Tensor)几乎就是 NumPy 数组加两个能力——这一节把这一步走完。
张量多的两个能力
| 能力 | 意义 |
|---|---|
| 能放到 GPU 上 | 矩阵运算快几十倍 |
| 能自动求导 | 反向传播不用手推公式 |
除此之外,用法和 NumPy 高度相似。
创建与互转
import torch
import numpy as np
x = torch.tensor([[1., 2.], [3., 4.]])
z = torch.zeros(3, 4); o = torch.ones(2, 2); r = torch.randn(2, 3)
# 和 NumPy 互转
a = np.array([1., 2., 3.])
t = torch.from_numpy(a) # NumPy → Tensor
b = t.numpy() # Tensor → NumPy
⚠️ 注意:from_numpy 和 .numpy() 共享同一块内存,改一个另一个也变。要独立副本用 torch.tensor(a)。
常用操作:几乎和 NumPy 一样
x.shape # 形状(NumPy 里也叫 shape)
x.reshape(4, 1) # 改形状
x.T # 转置
x @ y # 矩阵乘法
x * y # 逐元素乘
x.sum(dim=0) # 按维度求和(NumPy 里参数叫 axis,这里叫 dim)
x.mean()
主要差异就是 axis → dim,其余记忆可以直接迁移。
放到 GPU 上
device = "cuda" if torch.cuda.is_available() else "cpu"
x = x.to(device)
model = model.to(device)
写成这个形式的好处:同一份代码在有无 GPU 的机器上都能跑,这是 PyTorch 项目的标准写法。
最常见的报错:
RuntimeError: Expected all tensors to be on the same device
含义:有的张量在 CPU、有的在 GPU。参与同一个运算的张量必须在同一个设备上——数据和模型都要 .to(device)。
自动求导:反向传播的引擎
x = torch.tensor(3.0, requires_grad=True)
y = x ** 2 + 2 * x
y.backward() # 自动算梯度
print(x.grad) # tensor(8.) —— 因为 dy/dx = 2x + 2 = 8
发生了什么:
requires_grad=True让 PyTorch 记录这个张量参与的所有运算,构建一张计算图backward()沿着这张图反向应用链式法则- 结果累加到
.grad里
这就是第二章 ai-02 讲的反向传播——你不用手推导数,框架替你做了。
训练循环的骨架
所有 PyTorch 训练代码都是这五步:
for epoch in range(epochs):
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X) # ① 前向
loss = loss_fn(pred, y) # ② 算损失
optimizer.zero_grad() # ③ 清空上一轮的梯度
loss.backward() # ④ 反向传播
optimizer.step() # ⑤ 更新参数
第 ③ 步最容易忘,而且忘了不报错。
为什么必须清空:PyTorch 的梯度是累加的(这个设计是为了支持梯度累积等技巧)。不清空的话,这一轮的梯度会加在上一轮之上,参数更新方向完全错乱,但程序照常运行——loss 不下降,你却找不到原因。
记住这五步的顺序,你就能读懂绝大多数深度学习代码。
两个调试常识
print(x.shape) # 形状不匹配是最高频的错误
with torch.no_grad(): # 推理时关掉梯度记录,省显存也更快
pred = model(X)
练习:用 PyTorch 实现 在 处的导数,用 backward() 算出来,再手工验算一遍是否等于 17。
小纸条
PyTorch 张量比 NumPy 数组多了哪两个能力?训练循环里 optimizer.zero_grad() 忘了会怎样?
登录 后可看答案