PyTorch学习指南
首页
基础篇
进阶篇
高级篇
实战项目
🚀 编程指南
首页
基础篇
进阶篇
高级篇
实战项目
🚀 编程指南
  • 🚀 基础篇

    • 🚀 基础篇概述
    • ⚡ 快速入门:60分钟上手PyTorch
    • 📦 安装配置
    • 🔢 张量基础
    • ⚡ 自动求导
    • 🧩 torch.nn 快速入门
    • 📂 数据集处理

⚡ 自动求导

自动求导(Autograd) 是PyTorch的核心功能,它能自动计算导数/梯度,这是训练神经网络的基础。

🤔 为什么需要自动求导?

深度学习的训练过程可以简化为:

1. 前向传播:输入数据 → 模型 → 预测结果
2. 计算损失:预测结果 vs 真实标签 → 损失值
3. 反向传播:计算损失对每个参数的梯度(导数)
4. 更新参数:参数 = 参数 - 学习率 × 梯度

其中第3步需要求导,手动计算非常复杂,PyTorch帮我们自动完成!

💡 直观理解

想象你在爬山找最低点(损失最小):

  • 梯度:告诉你当前位置最陡峭的方向
  • 反向传播:计算这个方向
  • 参数更新:向最陡峭的下坡方向走一小步

📝 基本用法

开启梯度追踪

import torch

# 创建需要计算梯度的张量
x = torch.tensor([2.0, 3.0], requires_grad=True)
print(x)  # tensor([2., 3.], requires_grad=True)

# 或者后续开启
y = torch.tensor([1.0, 2.0])
y.requires_grad_(True)  # 原地修改,注意下划线

简单求导示例

import torch

# 例子:y = x² 的导数
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2  # y = x² = 4

# 反向传播,计算梯度
y.backward()

# 查看梯度 dy/dx = 2x = 4
print(x.grad)  # tensor([4.])

让我们用图来理解这个过程:

前向传播:
x = 2.0  ──────→  y = x² = 4.0
                      │
                      ▼
反向传播:
dy/dx = 2x = 4  ←────┘

更复杂的例子

import torch

# 计算 z = (x + y) * y 在 x=2, y=3 处的梯度
x = torch.tensor([2.0], requires_grad=True)
y = torch.tensor([3.0], requires_grad=True)

# 前向传播
z = (x + y) * y  # z = (2+3)*3 = 15

# 反向传播
z.backward()

# z = xy + y²
# dz/dx = y = 3
# dz/dy = x + 2y = 2 + 6 = 8
print(f"dz/dx = {x.grad}")  # tensor([3.])
print(f"dz/dy = {y.grad}")  # tensor([8.])

🔗 计算图

PyTorch会自动构建计算图来追踪所有操作:

import torch

x = torch.tensor([1.0], requires_grad=True)
y = torch.tensor([2.0], requires_grad=True)

# 每个操作都会被记录
a = x + y      # 加法
b = a * 2      # 乘法
c = b.sum()    # 求和

# 计算图结构:
#    x ─┐
#       ├─ + → a ─── × 2 → b ─── sum → c
#    y ─┘

⚠️ 重要概念

  • 计算图是动态的:每次前向传播都会构建新的图
  • backward() 后图会被销毁(除非设置 retain_graph=True)
  • 只有叶子节点(用户创建的张量)才会保存梯度

🚫 停止梯度追踪

有时候我们不需要计算梯度(比如推理阶段),可以这样做:

方法1:torch.no_grad()

import torch

x = torch.tensor([1.0], requires_grad=True)

# 在这个块内,不会追踪梯度
with torch.no_grad():
    y = x * 2
    print(y.requires_grad)  # False

方法2:detach()

import torch

x = torch.tensor([1.0], requires_grad=True)
y = x * 2

# 分离出一个不需要梯度的张量
z = y.detach()
print(z.requires_grad)  # False

方法3:设置requires_grad

import torch

x = torch.tensor([1.0], requires_grad=True)
x.requires_grad_(False)  # 关闭梯度

📊 多次反向传播

默认情况下,backward() 会累加梯度,而不是替换:

import torch

x = torch.tensor([1.0], requires_grad=True)

# 第一次
y = x * 2
y.backward()
print(x.grad)  # tensor([2.])

# 第二次(梯度会累加!)
y = x * 3
y.backward()
print(x.grad)  # tensor([5.])  # 2 + 3 = 5

🔴 常见陷阱

训练循环中一定要清零梯度,否则梯度会累加导致错误!

# 正确做法:每次反向传播前清零梯度
x.grad.zero_()  # 原地清零

# 或者
x.grad = None

🎓 实际训练中的应用

下面是一个简化的训练循环示例:

import torch

# 模拟数据
X = torch.tensor([[1.0], [2.0], [3.0], [4.0]])  # 输入
Y = torch.tensor([[2.0], [4.0], [6.0], [8.0]])  # 目标(y = 2x)

# 模型参数(我们要学习的)
w = torch.tensor([[1.0]], requires_grad=True)  # 权重
b = torch.tensor([[0.0]], requires_grad=True)  # 偏置

learning_rate = 0.01

# 训练循环
for epoch in range(100):
    # 1. 前向传播
    Y_pred = X @ w + b
    
    # 2. 计算损失(均方误差)
    loss = ((Y_pred - Y) ** 2).mean()
    
    # 3. 反向传播
    loss.backward()
    
    # 4. 更新参数(不要追踪这个操作的梯度)
    with torch.no_grad():
        w -= learning_rate * w.grad
        b -= learning_rate * b.grad
    
    # 5. 清零梯度(重要!)
    w.grad.zero_()
    b.grad.zero_()
    
    if epoch % 20 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}, w: {w.item():.4f}, b: {b.item():.4f}")

print(f"\n最终结果: w = {w.item():.4f}, b = {b.item():.4f}")
print("理想结果应该是: w = 2.0, b = 0.0")

输出:

Epoch 0, Loss: 30.0000, w: 1.3000, b: 0.1000
Epoch 20, Loss: 0.0123, w: 1.9621, b: 0.0812
Epoch 40, Loss: 0.0001, w: 1.9966, b: 0.0073
Epoch 60, Loss: 0.0000, w: 1.9997, b: 0.0007
Epoch 80, Loss: 0.0000, w: 2.0000, b: 0.0001

最终结果: w = 2.0000, b = 0.0000
理想结果应该是: w = 2.0, b = 0.0

🔧 常用API总结

功能代码
开启梯度追踪torch.tensor(..., requires_grad=True)
反向传播loss.backward()
查看梯度tensor.grad
清零梯度tensor.grad.zero_()
停止追踪(上下文)with torch.no_grad():
停止追踪(单个张量)tensor.detach()
检查是否追踪tensor.requires_grad

🎯 向量对向量求导

当输出是向量而不是标量时,需要提供gradient参数:

import torch

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x ** 2  # y = [1, 4, 9]

# 不能直接对向量backward
# y.backward()  # 报错!

# 需要提供gradient参数(通常是全1向量)
y.backward(torch.ones_like(y))
print(x.grad)  # tensor([2., 4., 6.])  # dy/dx = 2x

💡 解释

gradient参数表示后续计算图的"种子",对于标量损失,它默认是1。

🌿 叶子节点与非叶子节点

理解叶子节点(Leaf Tensor)是掌握自动求导的关键。

什么是叶子节点?

import torch

# 叶子节点:用户直接创建的张量
a = torch.tensor([1.0, 2.0], requires_grad=True)
b = torch.randn(3, 3, requires_grad=True)

print(f"a是叶子节点: {a.is_leaf}")  # True
print(f"b是叶子节点: {b.is_leaf}")  # True

# 非叶子节点:通过运算产生的张量
c = a + 1
d = a * 2

print(f"c是叶子节点: {c.is_leaf}")  # False
print(f"d是叶子节点: {d.is_leaf}")  # False
计算图示意:

    a (叶子节点) ──┬── +1 ──→ c (非叶子节点)
                   │
                   └── *2 ──→ d (非叶子节点)

为什么区分叶子节点?

只有叶子节点的梯度会被保留,非叶子节点的梯度在反向传播后会被自动清除:

import torch

x = torch.tensor([2.0], requires_grad=True)  # 叶子节点
y = x * 3   # 非叶子节点
z = y ** 2  # 非叶子节点

z.backward()

print(f"x.grad = {x.grad}")  # tensor([36.]) ← 梯度被保留
print(f"y.grad = {y.grad}")  # None ← 梯度未保留!

⚠️ 内存优化

PyTorch默认只保留叶子节点的梯度,这是为了节省内存。在深度网络中,中间层的激活值数量巨大,如果都保存梯度,内存会爆炸。

使用retain_grad()保留非叶子节点梯度

如果确实需要查看中间节点的梯度,使用retain_grad():

import torch

x = torch.tensor([2.0], requires_grad=True)
y = x * 3
y.retain_grad()  # 在backward之前调用!

z = y ** 2
z.backward()

print(f"x.grad = {x.grad}")  # tensor([36.])
print(f"y.grad = {y.grad}")  # tensor([12.]) ← 现在有梯度了!

# 验证:dz/dy = 2y = 2*6 = 12 ✓

实用场景

import torch
import torch.nn as nn

# 场景:查看神经网络中间层的梯度
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 5)
        self.fc2 = nn.Linear(5, 1)
    
    def forward(self, x):
        self.hidden = torch.relu(self.fc1(x))  # 保存中间激活
        self.hidden.retain_grad()  # 保留梯度
        return self.fc2(self.hidden)

model = SimpleNet()
x = torch.randn(1, 10)
y = model(x)
y.backward()

print(f"隐藏层梯度: {model.hidden.grad}")

🔬 深入理解requires_grad

requires_grad的传播规则

import torch

# 规则1:只要有一个输入requires_grad=True,输出就需要梯度
a = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([2.0], requires_grad=False)

c = a + b
print(f"c.requires_grad = {c.requires_grad}")  # True

# 规则2:所有输入都不需要梯度时,输出也不需要
x = torch.tensor([1.0])
y = torch.tensor([2.0])
z = x + y
print(f"z.requires_grad = {z.requires_grad}")  # False

动态控制requires_grad

import torch

x = torch.randn(3, 3)

# 方法1:创建时指定
y = torch.randn(3, 3, requires_grad=True)

# 方法2:原地修改(注意下划线)
x.requires_grad_(True)
print(x.requires_grad)  # True

# 方法3:关闭梯度
x.requires_grad_(False)
print(x.requires_grad)  # False

冻结模型参数

迁移学习中常用的技巧——冻结预训练层:

import torch
import torch.nn as nn

# 假设这是预训练模型
pretrained_model = nn.Sequential(
    nn.Linear(100, 50),
    nn.ReLU(),
    nn.Linear(50, 20)
)

# 冻结所有预训练层
for param in pretrained_model.parameters():
    param.requires_grad = False

# 只有新添加的分类头需要训练
classifier = nn.Linear(20, 10)

# 验证
print(f"预训练层参数是否训练: {pretrained_model[0].weight.requires_grad}")  # False
print(f"分类器参数是否训练: {classifier.weight.requires_grad}")  # True

📊 grad_fn:追踪操作历史

每个张量都有一个grad_fn属性,记录了创建它的操作:

import torch

x = torch.tensor([2.0], requires_grad=True)
print(f"x.grad_fn = {x.grad_fn}")  # None(叶子节点没有grad_fn)

y = x ** 2
print(f"y.grad_fn = {y.grad_fn}")  # <PowBackward0 object>

z = y + 3
print(f"z.grad_fn = {z.grad_fn}")  # <AddBackward0 object>

w = z.mean()
print(f"w.grad_fn = {w.grad_fn}")  # <MeanBackward0 object>

💡 调试技巧

当梯度计算出现问题时,检查grad_fn可以帮助理解计算图的结构:

def debug_computation_graph(tensor, depth=0):
    """打印计算图结构"""
    indent = "  " * depth
    print(f"{indent}{tensor.grad_fn}")
    if tensor.grad_fn is not None:
        for child in tensor.grad_fn.next_functions:
            if child[0] is not None:
                # 创建一个虚拟张量来递归
                pass

🔄 梯度累积与清零

梯度累积的妙用

默认情况下梯度会累加,这其实是个特性,可以用于梯度累积(模拟大batch):

import torch
import torch.nn as nn

model = nn.Linear(10, 1)
criterion = nn.MSELoss()

# 模拟小batch训练,但效果等于大batch
accumulation_steps = 4  # 累积4次
effective_batch_size = 8 * accumulation_steps  # 等效batch_size = 32

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

for i in range(accumulation_steps):
    # 小batch数据
    x = torch.randn(8, 10)
    y = torch.randn(8, 1)
    
    # 前向传播
    output = model(x)
    loss = criterion(output, y) / accumulation_steps  # 记得除以累积次数
    
    # 累积梯度(不调用optimizer.zero_grad())
    loss.backward()

# 累积完成后,一次性更新
optimizer.step()
optimizer.zero_grad()  # 更新后再清零

set_to_none vs zero_grad

import torch
import torch.nn as nn

model = nn.Linear(10, 1)
optimizer = torch.optim.Adam(model.parameters())

# 方法1:传统清零
optimizer.zero_grad()

# 方法2:设置为None(更高效,推荐)
optimizer.zero_grad(set_to_none=True)

# 区别:
# - zero_grad(): 将梯度设为0张量,保留内存
# - zero_grad(set_to_none=True): 将梯度设为None,释放内存

🚀 高级技巧

梯度裁剪(防止梯度爆炸)

import torch
import torch.nn as nn

model = nn.LSTM(10, 20)
optimizer = torch.optim.Adam(model.parameters())

# 训练循环中
loss.backward()

# 裁剪梯度,防止爆炸
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

optimizer.step()

二阶导数

import torch

x = torch.tensor([2.0], requires_grad=True)
y = x ** 3  # y = x³

# 一阶导数
dy_dx = torch.autograd.grad(y, x, create_graph=True)[0]
print(f"dy/dx = {dy_dx}")  # 12.0 (3x² = 12)

# 二阶导数
d2y_dx2 = torch.autograd.grad(dy_dx, x)[0]
print(f"d²y/dx² = {d2y_dx2}")  # 12.0 (6x = 12)

自定义梯度函数

import torch

class MyReLU(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x):
        ctx.save_for_backward(x)  # 保存用于反向传播
        return x.clamp(min=0)
    
    @staticmethod
    def backward(ctx, grad_output):
        x, = ctx.saved_tensors
        grad_input = grad_output.clone()
        grad_input[x < 0] = 0  # x<0时梯度为0
        return grad_input

# 使用
my_relu = MyReLU.apply
x = torch.tensor([-1.0, 0.0, 1.0, 2.0], requires_grad=True)
y = my_relu(x)
y.sum().backward()
print(f"梯度: {x.grad}")  # tensor([0., 0., 1., 1.])

🏋️ 练习

import torch

# 练习1:计算 f(x) = sin(x) 在 x = π/4 处的导数
# 提示:理论值是 cos(π/4) ≈ 0.707
# 你的代码:


# 练习2:对于 f(x, y) = x²y + y³,计算在 (1, 2) 处的偏导数
# 提示:∂f/∂x = 2xy, ∂f/∂y = x² + 3y²
# 你的代码:

点击查看答案
import torch
import math

# 练习1
x = torch.tensor([math.pi / 4], requires_grad=True)
y = torch.sin(x)
y.backward()
print(f"导数: {x.grad.item():.4f}")  # 约0.7071

# 练习2
x = torch.tensor([1.0], requires_grad=True)
y = torch.tensor([2.0], requires_grad=True)
f = x**2 * y + y**3
f.backward()
print(f"∂f/∂x = {x.grad.item()}")  # 4.0 (2*1*2)
print(f"∂f/∂y = {y.grad.item()}")  # 13.0 (1 + 12)

下一步

理解了自动求导后,让我们学习如何处理数据集!

上次更新: 2025/11/25 18:38
Prev
🔢 张量基础
Next
🧩 torch.nn 快速入门