引言
上一篇文章用了 KNN 做分类,KNN 并不需要梯度。但从神经网络开始,自动求导(Autograd) 就成了最核心的基础设施。
PyTorch 之所以成为深度学习领域的第一框架(没有之一),两个原因:
- 动态计算图 — 边运行边构建,调试方便,灵活度高
- 自动求导 — 你只需要定义前向传播,反向传播自动算好
本文从零开始,带你掌握 PyTorch 的两大核心:张量操作 和 自动微分。
前置知识
会用 NumPy 就够了,PyTorch 的 Tensor API 和 NumPy 几乎一模一样。
一、安装与配置
1
| pip install torch torchvision torchaudio
|
验证安装:
1 2 3 4 5
| import torch print(f"PyTorch 版本: {torch.__version__}") print(f"CUDA 可用: {torch.cuda.is_available()}") if torch.cuda.is_available(): print(f"GPU 型号: {torch.cuda.get_device_name(0)}")
|
二、张量(Tensor)—— 深度学习的”数字容器”
张量是多维数组的泛化概念:
| 维度 |
名称 |
示例 |
| 0D |
标量 |
3.14 |
| 1D |
向量 |
[1, 2, 3] |
| 2D |
矩阵 |
[[1,2],[3,4]] |
| 3D |
3阶张量 |
一张彩色图片 (H×W×3) |
| 4D |
4阶张量 |
一批图片 (N×H×W×3) |
2.1 创建张量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import torch import numpy as np
a = torch.tensor([[1, 2], [3, 4]]) print(f"从列表创建:\n{a}")
b = torch.from_numpy(np.array([[5, 6], [7, 8]])) print(f"从 NumPy 创建:\n{b}")
zeros = torch.zeros(2, 3) ones = torch.ones(2, 3) eye = torch.eye(3) rand = torch.randn(2, 4) print(f"全零:\n{zeros}") print(f"随机:\n{rand}")
x = torch.tensor([1, 2, 3], dtype=torch.float32, device='cuda' if torch.cuda.is_available() else 'cpu') print(f"指定设备和类型: {x}")
|
2.2 张量属性
1 2 3 4 5 6
| x = torch.randn(3, 4, 5) print(f"形状: {x.shape}") print(f"维度数: {x.ndim}") print(f"数据类型: {x.dtype}") print(f"元素总数: {x.numel()}") print(f"设备: {x.device}")
|
输出:
1 2 3 4 5
| 形状: torch.Size([3, 4, 5]) 维度数: 3 数据类型: torch.float32 元素总数: 60 设备: cpu
|
2.3 索引与切片(和 NumPy 一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| x = torch.randn(4, 4) print(f"原始:\n{x}")
print(f"第一行: {x[0]}") print(f"第二列: {x[:, 1]}") print(f"子矩阵: {x[1:3, 1:3]}")
indices = torch.tensor([0, 2, 3]) print(f"花式索引: {x[indices]}")
print(f"大于0的值: {x[x > 0]}")
|
2.4 张量运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| a = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32) b = torch.tensor([[5, 6], [7, 8]], dtype=torch.float32)
print(f"加法: \n{a + b}") print(f"乘法(逐元素): \n{a * b}")
print(f"矩阵乘法: \n{torch.mm(a, b)}") print(f"矩阵乘法(@运算符): \n{a @ b}")
print(f"求和: {a.sum()}") print(f"均值: {a.mean()}") print(f"标准差: {a.std()}") print(f"最大值: {a.max()}") print(f"转置: \n{a.T}")
c = torch.randn(2, 3, 4) print(f"原始形状: {c.shape}") print(f"展平: {c.view(-1).shape}") print(f"重塑: {c.view(6, 4).shape}")
|
2.5 广播机制
和 NumPy 一样,PyTorch 会自动扩展维度不一致的运算:
1 2 3 4 5
| a = torch.tensor([[1, 2, 3], [4, 5, 6]]) b = torch.tensor([10, 20, 30])
print(a + b)
|
2.6 CPU 与 GPU 切换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f"使用设备: {device}")
x = torch.randn(1000, 1000) y = torch.randn(1000, 1000)
%timeit x @ y
x_gpu = x.to(device) y_gpu = y.to(device) %timeit x_gpu @ y_gpu
|
在 GPU 上,矩阵乘法通常比 CPU 快 10-50 倍(取决于显卡)。
三、自动微分(Autograd)—— 深度学习的引擎
自动微分是 PyTorch 最核心的机制。你只需要定义前向计算,PyTorch 会自动记录所有操作,并在你调用 backward() 时自动计算梯度。
3.1 基本原理
1 2 3 4 5 6 7 8 9 10 11
| x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1
y.backward()
print(f"梯度 dy/dx: {x.grad}")
|
关键理解:PyTorch 会自动构建计算图,backward() 沿图反向传播梯度。
3.2 计算图可视化
1 2 3 4 5 6 7
| x(2.0) ──┬──► x²(4.0) ──┐ │ ├──► (+) ──► y(11.0) └──► 3x(6.0) ──┘ + 1 ──────┘
backward() 反向传播: dy/dx = 2x + 3 = 7
|
3.3 复杂例子:线性回归
让我们用自动微分手写一个线性回归:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import torch import matplotlib.pyplot as plt
torch.manual_seed(42) X = torch.linspace(-1, 1, 100).reshape(-1, 1) true_w, true_b = 2.0, 1.0 y = true_w * X + true_b + torch.randn_like(X) * 0.1
w = torch.randn(1, requires_grad=True) b = torch.zeros(1, requires_grad=True)
learning_rate = 0.1 losses = []
for epoch in range(100): y_pred = X @ w + b
loss = ((y_pred - y) ** 2).mean()
loss.backward()
with torch.no_grad(): w -= learning_rate * w.grad b -= learning_rate * b.grad
w.grad.zero_() b.grad.zero_()
losses.append(loss.item())
print(f"真实参数: w={true_w}, b={true_b}") print(f"学习结果: w={w.item():.4f}, b={b.item():.4f}")
plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(losses) plt.xlabel('Epoch') plt.ylabel('Loss') plt.title('训练损失')
plt.subplot(1, 2, 2) plt.scatter(X.numpy(), y.numpy(), alpha=0.5) plt.plot(X.numpy(), (X @ w + b).detach().numpy(), 'r-', linewidth=2) plt.xlabel('X') plt.ylabel('y') plt.title('拟合结果') plt.show()
|
输出示例:
1 2
| 真实参数: w=2.0, b=1.0 学习结果: w=1.9892, b=1.0037
|
3.4 梯度清零:为什么每次都要做?
1 2 3 4 5 6 7 8 9 10
| loss.backward() w -= lr * w.grad
loss.backward() with torch.no_grad(): w -= lr * w.grad w.grad.zero_()
|
PyTorch 默认会累加梯度,这是为了 RNN 等需要累积梯度的场景设计的。普通训练每次都要手动清零。
3.5 禁用梯度追踪
某些操作不需要梯度(如模型推理、参数更新),用 torch.no_grad() 避免构建计算图:
1 2 3 4 5 6
| with torch.no_grad(): y_pred = model(X_test)
torch.set_grad_enabled(False)
|
四、实用技巧
4.1 随机种子
1 2 3 4
| torch.manual_seed(42) torch.cuda.manual_seed(42) np.random.seed(42)
|
4.2 Tensor 与 NumPy 互转
1 2 3 4 5 6 7 8 9 10
| tensor = torch.ones(3, 4) numpy_arr = tensor.numpy()
numpy_arr = np.ones((3, 4)) tensor = torch.from_numpy(numpy_arr)
tensor_clone = torch.tensor(numpy_arr)
|
注意:CPU 上的 Tensor 和 NumPy 数组共享内存,修改一个会影响另一个。
4.3 设备无关的代码
1 2 3 4 5
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = MyModel().to(device) data = data.to(device) labels = labels.to(device)
|
五、总结
本文你掌握了:
| 技能 |
掌握程度 |
| Tensor 创建与属性 |
✅ 基础 |
| 索引、切片、运算 |
✅ 基础 |
| 广播机制 |
✅ 理解 |
| GPU 加速切换 |
✅ 掌握 |
| 自动求导 Autograd |
✅ 核心 |
| 手写线性回归 |
✅ 实战 |
下一步推荐: