Python 编程

从 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()

主要差异就是 axisdim,其余记忆可以直接迁移。


放到 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

发生了什么

  1. requires_grad=True 让 PyTorch 记录这个张量参与的所有运算,构建一张计算图
  2. backward() 沿着这张图反向应用链式法则
  3. 结果累加到 .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() 忘了会怎样?

登录 后可看答案