PyTorch 核心三件套:Tensor、Module、Autograd
欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。
目录
- 引言
- 1 Tensor
- 1.1 🛠️Tensor 的核心用途(5大场景)
- 1.2 表示现实世界数据|输入数据
- 1.3 表示标签(Labels / Targets)
- 1.4 存储模型参数(Weights & Biases)
- 1.5 中间计算结果(Forward Pass)
- 1.6 梯度计算(Backward Pass)
- 1.7 📚 常用 API 速查表
- 1.8 💡 Tensor 演示
- 2 Module
- 2.1 简介
- 2.2 创建Module
- 2.3 Module 的核心操作
- 3 Autograd
- 3.1 简介
- 3.2 梯度追踪
- 3.3 反向传播
- 3.4 禁用梯度计算
- 4 📌 三件套关系图解
- 5 🌟 三件套协同工作流程(完整训练示例)
- 5.1 Demo:三件套简单串联
- 5.2 构建一个简单线性回归模型 y=wx+b
- 5.3 Demo:搭建一个简单的多层感知机
- 刻意练习
引言
本篇将讲述PyTorch的核心“三件套”:Tensor, Module, Autograd。
尝试以后端岗位工程应用角度来理解、串联起这个三个概念。
AI使用声明:本文内容由作者基于对深度学习和PyTorch框架的学习与理解撰写。在内容整理、结构优化和语言表达的过程中,我使用了人工智能(AI)工具作为辅助。
资料:
《深入浅出PyTorch 第二章节》
《动手学深度学习第四章》
1 Tensor
让我们思考一个问题,我们要怎么把现实世界的数据变成计算机能处理的数字形式呢?
通过Tensor,张量,它是现代机器学习的基础。
n维数组,也称为_张量_(tensor)。
几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是二阶张量。
张量维度 | 代表含义 | 示例 |
---|---|---|
0D | 标量(Scalar) | torch.tensor(3.14) |
1D | 向量(Vector) | [1, 2, 3] |
2D | 矩阵(Matrix) | 图像展平后的特征 |
3D | 序列/单图 | (C, H, W) 彩色图像 |
4D | 批量图像 | (B, C, H, W) |
在PyTorch中, torch.Tensor
是存储和变换数据的主要工具,比起NumPy更适合深度学习。
1.1 🛠️Tensor 的核心用途(5大场景)
1.2 表示现实世界数据|输入数据
现实世界常见数据的Tensor示例如下:
数据类型 | Tensor形状示例 | 说明 |
---|---|---|
图像 | (3, 224, 224) | RGB 三通道图像 |
批量图像 | (64, 3, 224, 224) | 一次处理64张图 |
文本 | (1, 512) | 512个词的编码序列 |
音频 | (1, 16000) | 1秒音频(16kHz采样) |
# 图像转 Tensor(使用 torchvision)
from torchvision import transforms
transform = transforms.ToTensor()
image_tensor = transform(pil_image) # PIL图像 → Tensor
1.3 表示标签(Labels / Targets)
可以告诉模型“正确答案”。
# 分类任务:类别标签
labels = torch.tensor([3, 1, 7, 0]) # 4个样本的真实类别# 回归任务:连续值
targets = torch.tensor([1.5, 2.3, 0.8])# 语义分割:每个像素的类别
mask = torch.randint(0, 20, (1, 256, 256)) # (C,H,W)
1.4 存储模型参数(Weights & Biases)
神经网络的“记忆”就存在 Tensor 里。
linear = torch.nn.Linear(10, 5)
print(linear.weight.shape) # torch.Size([5, 10]) ← 这是个 Tensor!
print(linear.bias.shape) # torch.Size([5]) ← 这也是 Tensor!
这些参数 Tensor 会:
- 在训练中不断更新(梯度下降)
- 决定模型的预测能力
- 可以保存和加载(
.pth
文件)
1.5 中间计算结果(Forward Pass)
网络每一层的输出都是 Tensor 。
x = torch.randn(1, 784) # 输入
w = torch.randn(784, 256) # 权重
b = torch.zeros(256) # 偏置# 每一步都是 Tensor 运算
z = x @ w + b # 线性变换
a = torch.relu(z) # 激活函数
# a 仍然是 Tensor,传给下一层
1.6 梯度计算(Backward Pass)
Autograd 用 Tensor 记录梯度 。
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2
y.backward() # 计算 dy/dx
print(x.grad) # tensor(4.) ← 梯度也存在 Tensor 中!
1.7 📚 常用 API 速查表
类别 | API | 说明 | 示例 |
---|---|---|---|
创建 | torch.tensor() | 通用创建 | x = torch.tensor([1,2,3]) |
torch.randn() | 随机正态分布 | x = torch.randn(2,3) | |
torch.zeros() | 全零张量 | x = torch.zeros(4,4) | |
转换 | .numpy() | → NumPy 数组 | arr = x.numpy() |
torch.from_numpy() | ← NumPy 数组 | x = torch.from_numpy(arr) | |
.to('cuda') | → GPU | x_gpu = x.to('cuda') | |
运算 | + ,- ,* ,/ | 基础运算 | z = x + y |
torch.matmul() | 矩阵乘法 | z = torch.matmul(x,y) | |
x.view() | 形状变换 | x_2d = x.view(2,3) | |
自动求导 | .requires_grad_() | 开启梯度追踪 | x.requires_grad_() |
.detach() | 剥离梯度计算 | x_no_grad = x.detach() |
1.8 💡 Tensor 演示
import torch # 1. 创建张量(开启梯度追踪)
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(f"初始张量: {x} | 是否追踪梯度: {x.requires_grad}") # 2. 张量运算(自动构建计算图)
y = x * 2 + 3
z = y.mean()
print(f"中间结果 y: {y} | 最终结果 z: {z}") # 3. 自动求导(反向传播)
z.backward() # 计算 dz/dxprint(f"梯度 dz/dx: {x.grad}") # 输出: [0.6667, 0.6667, 0.6667] # 4. GPU 加速演示
if torch.cuda.is_available(): x_gpu = x.to('cuda') # 一键迁移到GPU print(f"GPU张量: {x_gpu} | 位于设备: {x_gpu.device}")
2 Module
让我们思考一个问题,我们要怎么定义一个神经网络的“结构”和“行为”呢?
通过 nn.Module
,它是PyTorch中所有神经网络模块的基类。你可以把它想象成一个可以“思考”的容器,它不仅存储网络的参数(如权重和偏置),还定义了数据应该如何流动(前向传播)。
本质:所有神经网络层的基类,管理参数 + 定义计算流程
核心:forward()
方法定义数据流动逻辑
2.1 简介
在PyTorch中,torch.nn.Module
是构建神经网络的核心抽象。无论是简单的线性层 nn.Linear
,还是复杂的ResNet模型,它们都是 nn.Module
的子类。通过继承 nn.Module
,我们可以轻松地定义自己的神经网络。
经过本节的学习,你将收获:
nn.Module
的简介- 如何使用
nn.Sequential
快速构建模型 - 如何自定义
nn.Module
子类来创建复杂模型 nn.Module
的核心操作(参数管理、设备迁移)
2.2 创建Module
在接下来的内容中,我们将介绍几种常见的创建 Module
的方法。
-
使用
nn.Sequential
构建模型:
nn.Sequential
是一个有序的容器,它将传入的模块按顺序组合起来。对于不需要复杂逻辑的“直筒型”网络,这是最简单的方法。import torch.nn as nn# 定义一个简单的多层感知机 (MLP) model = nn.Sequential(nn.Flatten(), # 将图像展平成一维向量nn.Linear(28*28, 128), # 全连接层 (输入784 -> 输出128)nn.ReLU(), # 激活函数nn.Linear(128, 10), # 输出层 (输入128 -> 输出10类)nn.Softmax(dim=1) # 输出概率分布 ) print(model)
Sequential((0): Flatten(start_dim=1, end_dim=-1)(1): Linear(in_features=784, out_features=128, bias=True)(2): ReLU()(3): Linear(in_features=128, out_features=10, bias=True)(4): Softmax(dim=1) )
-
自定义
nn.Module
子类:
对于更复杂的网络结构(如包含分支、残差连接等),我们需要创建自己的类,继承nn.Module
。import torch.nn as nnclass SimpleMLP(nn.Module):def __init__(self):super().__init__()# 在 __init__ 中定义网络层self.flatten = nn.Flatten()self.fc1 = nn.Linear(28*28, 128)self.relu = nn.ReLU()self.fc2 = nn.Linear(128, 10)def forward(self, x):# 在 forward 中定义数据流动的逻辑x = self.flatten(x)x = self.fc1(x)x = self.relu(x)x = self.fc2(x)return x# 实例化模型 model = SimpleMLP() print(model)
SimpleMLP((flatten): Flatten(start_dim=1, end_dim=-1)(fc1): Linear(in_features=784, out_features=128, bias=True)(relu): ReLU()(fc2): Linear(in_features=128, out_features=10, bias=True) )
一个标准的 nn.Module
子类有两个必须理解的方法。
__init__
方法:
初始化网络层,并将它们作为类的属性,在这里定义参数。forward
方法:
接收输入x
(一个Tensor),然后执行一系列计算并返回输出(也是一个Tensor)。
不需要手动调用forward
,执行model实例是,PyTorch 会自动调用它的forward方法。
2.3 Module 的核心操作
在接下来的内容中,我们将介绍 Module
的几个关键操作。
-
参数管理:
nn.Module
会自动将nn.Parameter
或任何nn.Module
子类的实例注册为模型的参数。我们可以使用以下方法访问它们。# 获取模型的所有参数 for param in model.parameters():print(param.shape)# 获取模型的命名参数 for name, param in model.named_parameters():print(f"{name}: {param.shape}")
flatten._parameters: {} # Flatten层无参数 fc1.weight: torch.Size([128, 784]) fc1.bias: torch.Size([128]) fc2.weight: torch.Size([10, 128]) fc2.bias: torch.Size([10])
-
设备迁移:
为了利用GPU加速,我们可以使用.to(device)
方法将整个模型(包括其所有参数和子模块)迁移到指定设备。device = 'cuda' if torch.cuda.is_available() else 'cpu' model = model.to(device) # 一键迁移到GPU print(f"模型设备: {next(model.parameters()).device}")
-
状态保存与加载:
我们可以使用state_dict()
来获取模型参数的字典,这非常适合保存和加载训练好的模型。# 保存模型 torch.save(model.state_dict(), 'simple_mlp.pth')# 加载模型 (需要先创建相同结构的模型) new_model = SimpleMLP() new_model.load_state_dict(torch.load('simple_mlp.pth')) new_model.eval() # 切换到评估模式
-
训练/评估模式切换:
一些层(如Dropout
,BatchNorm
)在训练和评估时的行为不同。我们可以使用model.train()
和model.eval()
来切换模式。model.train() # 启用Dropout等训练专用操作 # ... 训练代码 ...model.eval() # 禁用Dropout,使用BatchNorm的统计值 # ... 推理代码 ... with torch.no_grad(): # 通常与 no_grad() 一起使用output = model(input_tensor)
3 Autograd
让我们思考一个问题,我们要怎么让模型“学会”调整自己的参数呢?
通过 autograd
,它是PyTorch的自动微分引擎。它会默默地记录我们对张量进行的所有操作,构建一个“计算图”,然后在需要时自动计算梯度,使得反向传播变得极其简单。
3.1 简介
在深度学习中,我们通过反向传播算法来更新模型参数。手动计算梯度不仅繁琐而且容易出错。PyTorch的 autograd
包可以自动完成这一过程。
autograd
的核心是 torch.Tensor
和 torch.Function
。
当 Tensor
的 requires_grad
属性为 True
时,autograd
会追踪所有作用于该张量的操作
。一旦计算完成,调用 .backward()
方法,autograd
就会自动计算所有梯度,并将它们累积到 .grad
属性中。
经过本节的学习,你将收获:
autograd
的工作原理- 如何使用
requires_grad
控制梯度追踪 - 如何执行反向传播并查看梯度
- 如何使用
torch.no_grad()
上下文管理器
3.2 梯度追踪
-
开启梯度追踪:
通过设置requires_grad=True
,我们可以告诉autograd
需要追踪对这个张量的所有操作。import torch# 方法1: 创建时指定 x = torch.tensor([1., 2., 3.], requires_grad=True) print(f"x.requires_grad: {x.requires_grad}")# 方法2: 对已有张量启用 y = torch.tensor([4., 5., 6.]) y.requires_grad_(True) # 注意是方法,带下划线 print(f"y.requires_grad: {y.requires_grad}")
-
构建计算图:
一旦开启了梯度追踪,后续的运算都会被记录下来。z = x * y + 2 print(f"z.grad_fn: {z.grad_fn}") # <AddBackward0 object> print(f"z's creator: {z.grad_fn}")
3.3 反向传播
-
执行反向传播:
调用.backward()
方法来触发反向传播。如果loss
是一个标量(0维张量),可以直接调用loss.backward()
。loss = z.sum() # loss 是一个标量 print(f"loss: {loss}") # tensor(27., grad_fn=<SumBackward0>)loss.backward() # 计算梯度 print(f"dx/dloss: {x.grad}") # tensor([4., 5., 6.]) print(f"dy/dloss: {y.grad}") # tensor([1., 2., 3.])
注意:
.backward()
会累积梯度。在下一次迭代前,务必使用optimizer.zero_grad()
或手动清零x.grad.zero_()
。 -
非标量输出:
如果要对非标量张量调用.backward()
,需要传入一个gradient
参数(形状与该张量相同),作为外部梯度。v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float) z.backward(gradient=v) # 这里会累加梯度 # x.grad 会增加 v * y 的值
3.4 禁用梯度计算
在模型推理(inference)或某些不需要梯度的计算中,我们可以使用 torch.no_grad()
上下文管理器来临时禁用梯度计算,这可以节省内存并加快计算速度。
print(f"Before no_grad, x.requires_grad: {x.requires_grad}")with torch.no_grad():a = x * 2print(f"Inside no_grad, a.requires_grad: {a.requires_grad}") # Falseprint(f"After no_grad, x.requires_grad: {x.requires_grad}") # 仍然是 True# 另一种方式:使用 .detach()
b = x.detach() * 3
print(f"b.requires_grad: {b.requires_grad}") # False
4 📌 三件套关系图解
┌─────────────┐ ┌───────────┐ ┌─────────────┐│ │ │ │ │ ││ Tensor │─────▶│ Module │─────▶│ Autograd ││ (数据载体) │◀─────│ (计算逻辑)│◀─────│ (梯度引擎) │└─────────────┘ └───────────┘ └─────────────┘▲ │ ││ ▼ ▼┌─────┴──────┐ ┌─────────────┐ ┌─────────────┐│ 数据加载 │ │ 模型定义 │ │ 反向传播 ││ DataLoader │ │ forward() │ │ loss.backward() └────────────┘ └─────────────┘ └─────────────┘
✅ 关键口诀:
“Tensor 存数据,Module 定流程,Autograd 算梯度,三件套合训练成”
5 🌟 三件套协同工作流程(完整训练示例)
资料:https://zh-v2.d2l.ai/chapter_multilayer-perceptrons/mlp-scratch.html
5.1 Demo:三件套简单串联
import torch
import torch.nn as nn
import torch.optim as optim# 1. Tensor:准备数据
x = torch.randn(64, 1, 28, 28) # 64张28x28灰度图
y_true = torch.randint(0, 10, (64,)) # 10分类标签# 2. Module:创建模型
model = nn.Sequential(nn.Flatten(),nn.Linear(28*28, 128),nn.ReLU(),nn.Linear(128, 10)
)# 3. Autograd:训练循环
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)# 前向传播 → 计算损失 → 反向传播 → 更新参数
optimizer.zero_grad() # 清零梯度(Autograd)
logits = model(x) # 前向传播(Module)
loss = criterion(logits, y_true) # 计算损失(Tensor)
loss.backward() # 反向传播(Autograd)
optimizer.step() # 更新参数(Tensor)print(f"训练完成!损失值: {loss.item():.4f}")
5.2 构建一个简单线性回归模型 y=wx+b
我们将构建一个简单的线性回归模型 y = wx + b
,并模拟一个真实的训练过程。
- 创建一个简单的线性模型(
nn.Module
)。 - 使用真实数据和模型预测来手动计算损失(均方误差)。
- 调用
loss.backward()
触发自动微分。 - 观察模型参数(权重
w
和偏置b
)的.grad
属性,看到梯度的产生。
import torch
import torch.nn as nn# ----------------------------------------
# 第一步:准备虚拟数据 (y = 3x + 2)
# ----------------------------------------
# 我们假设真实的世界关系是 y = 3x + 2
# 我们生成一些带点“噪声”的数据来模拟真实情况# 创建输入 x (形状: [5, 1])
x = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]]) # 5个样本
print("输入 x:")
print(x)# 创建真实标签 y_true (形状: [5, 1])
true_w = 3.0
true_b = 2.0
y_true = true_w * x + true_b + torch.randn_like(x) * 0.1 # 加点小噪声
print("\n真实标签 y_true (y ≈ 3x + 2):")
print(y_true)# ----------------------------------------
# 第二步:定义模型 (nn.Module)
# ----------------------------------------
# 我们创建一个非常简单的模型,它就是一个线性层class SimpleLinear(nn.Module):def __init__(self):super().__init__()# nn.Linear(in_features, out_features) -> y = Wx + b# 因为我们只有一个输入和一个输出,所以是 Linear(1, 1)self.linear = nn.Linear(1, 1)def forward(self, x):return self.linear(x)# 实例化模型
model = SimpleLinear()
print("\n模型结构:")
print(model)# 打印初始参数 (模型刚开始是“瞎猜”的)
print("\n训练前的模型参数:")
print(f"权重 w: {model.linear.weight.item():.3f}") # .item() 取出单个数值
print(f"偏置 b: {model.linear.bias.item():.3f}")
# 你会发现初始的 w 和 b 是随机的,和 3.0, 2.0 差很远# ----------------------------------------
# 第三步:前向传播 (Forward Pass)
# ----------------------------------------
# 让模型对输入 x 进行预测model.train() # 确保模型在训练模式
y_pred = model(x) # 调用 forward 方法print("\n模型预测 y_pred (训练前):")
print(y_pred)# ----------------------------------------
# 第四步:手动计算损失 (Loss)
# ----------------------------------------
# 我们使用最简单的均方误差 (MSE) 损失
# loss = (1/N) * Σ (y_true - y_pred)²# 手动计算损失
loss = torch.mean((y_true - y_pred) ** 2)print(f"\n训练前的损失 (MSE): {loss.item():.4f}")# ----------------------------------------
# 第五步:反向传播 (Backward Pass) 和 观察梯度
# ----------------------------------------
# 这是最关键的一步!
# 在反向传播之前,必须先将梯度清零,否则梯度会累积
model.zero_grad() # 等价于 optimizer.zero_grad()# 执行反向传播
loss.backward()# 🔥 现在,我们来“亲眼”看看梯度产生了!
print("\n" + "="*50)
print("调用 loss.backward() 后,模型参数的梯度:")
print("="*50)# 查看权重 w 的梯度
w_grad = model.linear.weight.grad
print(f"权重 w 的梯度 (.grad): {w_grad.item():.4f}")
# 这个梯度告诉我们:为了减小损失,w 应该增加还是减少?# 查看偏置 b 的梯度
b_grad = model.linear.bias.grad
print(f"偏置 b 的梯度 (.grad): {b_grad.item():.4f}")
# 同理,这个梯度指导 b 如何更新# ----------------------------------------
# 第六步:(可选)更新参数
# ----------------------------------------
# 虽然练习目标不包括更新,但我们可以手动模拟一下
# 这就是优化器(如SGD)做的事情:参数 = 参数 - 学习率 * 梯度learning_rate = 0.01# 手动更新权重
with torch.no_grad(): # 更新参数时不需要记录梯度model.linear.weight -= learning_rate * w_gradmodel.linear.bias -= learning_rate * b_gradprint("\n" + "="*50)
print("一次梯度下降更新后的模型参数:")
print("="*50)
print(f"更新后的权重 w: {model.linear.weight.item():.3f}")
print(f"更新后的偏置 b: {model.linear.bias.item():.3f}")
print("现在参数更接近真实的 3.0 和 2.0 了吗?")
在这个Demo中,三大件协作如下:
(数据)↓┌─────────────┐│ Tensor │ ← 存放 x, y, w, b 这些数字└─────────────┘↓ (输入)┌─────────────┐│ Module │ ← 定义 y = w*x + b 这个计算公式└─────────────┘↓ (输出预测 y_pred)┌─────────────┐│ 计算损失 │ ← 比较 y_pred 和 真实 y 的差距└─────────────┘↓ (标量 loss)┌─────────────┐│ Autograd │ ← 调用 loss.backward(),自动算出 w 和 b 的梯度└─────────────┘↓ (梯度 dw, db)┌─────────────┐│ 优化器 │ ← 用梯度更新 w 和 b,让它们更接近真实值└─────────────┘
5.3 Demo:搭建一个简单的多层感知机
Transformer模型中的“前馈网络(Feed-Forward Network)”部分,其本质就是一个多层感知机(MLP)。
让我们仿照教程,搭建搭建一个简单的多层感知机:手写数字分类MLP。
import torch
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import numpy as np # =============================================================================
# 1. Tensor:数据的容器
# =============================================================================
"""
Tensor 是 PyTorch 中存储和操作数据的核心结构。
它不仅是多维数组,还能追踪梯度,是深度学习的“通用货币”。
""" # 演示:Tensor 的基本用途
print("=== 1. Tensor 演示 ===") # 创建一个需要梯度追踪的张量
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
print(f"初始张量: {x} | 追踪梯度: {x.requires_grad}") # 执行运算(构建计算图)
y = x * 2 + 3
z = y.mean()
print(f"中间结果 y: {y} | 最终标量 z: {z}") # 反向传播
z.backward()
print(f"梯度 dz/dx: {x.grad}") # 应为 [2/3, 2/3, 2/3]
# GPU 加速(如果可用)
if torch.cuda.is_available(): x_gpu = x.to('cuda') print(f"GPU张量: {x_gpu} | 设备: {x_gpu.device}")
else: print("GPU不可用,使用CPU") # =============================================================================
# 2. Module:计算逻辑的蓝图(手动实现)
# =============================================================================
"""
我们不使用 nn.Module 和 nn.Linear,
而是手动创建权重和偏置,并定义前向传播函数。
""" print("\n=== 2. 手动实现 MLP(无 nn.Module / nn.Linear)===") # 超参数
input_size = 784 # 28x28 图像展平
hidden_size = 128 # 隐藏层大小
output_size = 10 # MNIST 10 类
learning_rate = 0.01 # 学习率
num_epochs = 3 # 训练轮数# 手动初始化参数(替代 nn.Linear)
torch.manual_seed(42)
W1 = torch.randn(input_size, hidden_size) * 0.01
b1 = torch.zeros(hidden_size)
W2 = torch.randn(hidden_size, output_size) * 0.01
b2 = torch.zeros(output_size) # 开启梯度追踪
W1.requires_grad_(True)
b1.requires_grad_(True)
W2.requires_grad_(True)
b2.requires_grad_(True) print(f"W1: {W1.shape}, b1: {b1.shape}")
print(f"W2: {W2.shape}, b2: {b2.shape}") # 定义前向传播函数(替代 forward)
def forward(x): """ 手动实现前向传播 :param x: 输入张量 (B, 784) :return: logits (B, 10) """ z1 = x @ W1 + b1 # 第一层线性变换 a1 = F.relu(z1) # 激活函数 z2 = a1 @ W2 + b2 # 第二层线性变换 return z2 # =============================================================================
# 3. Autograd:自动微分引擎
# =============================================================================
"""
Autograd 会自动追踪所有操作,调用 loss.backward() 即可计算梯度。
我们手动实现训练循环,替代 optimizer.step() 和 optimizer.zero_grad()""" print("\n=== 3. 数据加载与训练 ===") # 数据预处理
transform = transforms.ToTensor() # 加载 MNIST 数据集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False) print(f"训练集大小: {len(train_dataset)}")
print(f"测试集大小: {len(test_dataset)}") # 训练循环
train_losses = [] for epoch in range(num_epochs): epoch_loss = 0.0 count = 0 for x_batch, y_batch in train_loader: # 展平图像: (B, 1, 28, 28) -> (B, 784) x_batch = x_batch.view(x_batch.size(0), -1) # 前向传播 logits = forward(x_batch) loss = F.cross_entropy(logits, y_batch) # 使用函数式 API # 反向传播 loss.backward() # 手动更新参数(替代 optimizer.step()) with torch.no_grad(): W1 -= learning_rate * W1.grad b1 -= learning_rate * b1.grad W2 -= learning_rate * W2.grad b2 -= learning_rate * b2.grad # 清零梯度(替代 optimizer.zero_grad()) W1.grad.zero_() b1.grad.zero_() W2.grad.zero_() b2.grad.zero_() epoch_loss += loss.item() count += 1 avg_loss = epoch_loss / count train_losses.append(avg_loss) print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}") # =============================================================================
# 4. 模型评估
# =============================================================================
print("\n=== 4. 模型评估 ===") correct = 0
total = 0
with torch.no_grad(): # 推理时关闭梯度 for x_batch, y_batch in test_loader: x_batch = x_batch.view(x_batch.size(0), -1) logits = forward(x_batch) _, predicted = torch.max(logits, 1) total += y_batch.size(0) correct += (predicted == y_batch).sum().item() accuracy = 100 * correct / total
print(f"测试准确率: {accuracy:.2f}%") # =============================================================================
# 5. Demo:线性回归(观察梯度生成过程)
# =============================================================================
print("\n=== 5. 线性回归 Demo:观察梯度如何生成 ===") # 生成虚拟数据:y = 3x + 2 + noise
x_reg = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])
true_w, true_b = 3.0, 2.0
y_true = true_w * x_reg + true_b + torch.randn_like(x_reg) * 0.1 # 初始化参数
w = torch.randn(1, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True) print(f"真实参数: w={true_w}, b={true_b}")
print(f"初始参数: w={w.item():.3f}, b={b.item():.3f}") # 前向传播
y_pred = x_reg @ w + b
loss = F.mse_loss(y_pred, y_true) # 反向传播
loss.backward() print(f"\n调用 loss.backward() 后:")
print(f"权重 w 的梯度: {w.grad.item():.4f}")
print(f"偏置 b 的梯度: {b.grad.item():.4f}") print("\n梯度方向正确:w 的梯度为正,说明当前 w 偏小,应增大") # =============================================================================
# 6. 总结口诀
# =============================================================================
print("\n" + "="*60)
print("🎯 PyTorch 三件套核心口诀")
print("="*60)
print("Tensor 存数据,Module 定流程,Autograd 算梯度")
print("三件套合训练成,手动实现才真懂!")
print("="*60)
概念补充:
- 超参数:一般指一些模型的配置参数,想当于模型的学习规则。
超参数 | 说明 | 为什么重要 |
---|---|---|
学习率 (Learning Rate, lr ) | 每次更新参数时的“步长” | 太大:学得快但可能错过最优解 太小:学得慢,可能卡住 |
批次大小 (Batch Size) | 一次训练使用的样本数量 | 太大:内存压力大,泛化可能差 太小:训练不稳定 |
训练轮数 (Epochs) | 整个数据集被完整遍历的次数 | 太少:欠拟合 太多:过拟合(死记硬背) |
隐藏层大小 (Hidden Size) | 神经网络中隐藏层的神经元数量 | 决定了模型的“容量” 太大:容易过拟合 太小:无法学习复杂模式 |
网络层数 (Number of Layers) | 有多少个隐藏层 | 层数多 → “深度网络”,能学更复杂特征,但也更难训练 |
优化器类型 | 使用 SGD、Adam 还是 RMSprop | 不同优化器收敛速度和稳定性不同 |
正则化参数 | 如 Dropout 概率、L2 权重衰减 | 用于防止过拟合 |
刻意练习
Q:如何给MLP增加一个隐藏层?