欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。
目录
- 引言
- [1 Tensor](#1 Tensor)
-
- [1.1 🛠️Tensor 的核心用途(5大场景)](#1.1 🛠️Tensor 的核心用途(5大场景))
- [1.2 表示现实世界数据|输入数据](#1.2 表示现实世界数据|输入数据)
- [1.3 表示标签(Labels / Targets)](#1.3 表示标签(Labels / Targets))
- [1.4 存储模型参数(Weights & Biases)](#1.4 存储模型参数(Weights & Biases))
- [1.5 中间计算结果(Forward Pass)](#1.5 中间计算结果(Forward Pass))
- [1.6 梯度计算(Backward Pass)](#1.6 梯度计算(Backward Pass))
- [1.7 📚 常用 API 速查表](#1.7 📚 常用 API 速查表)
- [1.8 💡 Tensor 演示](#1.8 💡 Tensor 演示)
- [2 Module](#2 Module)
-
- [2.1 简介](#2.1 简介)
- [2.2 创建Module](#2.2 创建Module)
- [2.3 Module 的核心操作](#2.3 Module 的核心操作)
- [3 Autograd](#3 Autograd)
-
- [3.1 简介](#3.1 简介)
- [3.2 梯度追踪](#3.2 梯度追踪)
- [3.3 反向传播](#3.3 反向传播)
- [3.4 禁用梯度计算](#3.4 禁用梯度计算)
- [4 📌 三件套关系图解](#4 📌 三件套关系图解)
- [5 🌟 三件套协同工作流程(完整训练示例)](#5 🌟 三件套协同工作流程(完整训练示例))
-
- [5.1 Demo:三件套简单串联](#5.1 Demo:三件套简单串联)
- [5.2 构建一个简单线性回归模型 y=wx+b](#5.2 构建一个简单线性回归模型 y=wx+b)
- [5.3 Demo:搭建一个简单的多层感知机](#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采样) |
python
# 图像转 Tensor(使用 torchvision)
from torchvision import transforms
transform = transforms.ToTensor()
image_tensor = transform(pil_image) # PIL图像 → Tensor
1.3 表示标签(Labels / Targets)
可以告诉模型"正确答案"。
python
# 分类任务:类别标签
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 里。
python
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 。
python
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 记录梯度 。
python
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 演示
python
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
是一个有序的容器,它将传入的模块按顺序组合起来。对于不需要复杂逻辑的"直筒型"网络,这是最简单的方法。pythonimport 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
。pythonimport torch.nn as nn class 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
子类的实例注册为模型的参数。我们可以使用以下方法访问它们。python# 获取模型的所有参数 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)
方法将整个模型(包括其所有参数和子模块)迁移到指定设备。pythondevice = 'cuda' if torch.cuda.is_available() else 'cpu' model = model.to(device) # 一键迁移到GPU print(f"模型设备: {next(model.parameters()).device}")
-
状态保存与加载 :
我们可以使用
state_dict()
来获取模型参数的字典,这非常适合保存和加载训练好的模型。python# 保存模型 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()
来切换模式。pythonmodel.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
需要追踪对这个张量的所有操作。pythonimport 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}")
-
构建计算图 :
一旦开启了梯度追踪,后续的运算都会被记录下来。
pythonz = 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()
。pythonloss = 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
参数(形状与该张量相同),作为外部梯度。pythonv = 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()
上下文管理器来临时禁用梯度计算,这可以节省内存并加快计算速度。
python
print(f"Before no_grad, x.requires_grad: {x.requires_grad}")
with torch.no_grad():
a = x * 2
print(f"Inside no_grad, a.requires_grad: {a.requires_grad}") # False
print(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:三件套简单串联
python
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
属性,看到梯度的产生。
python
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_grad
model.linear.bias -= learning_rate * b_grad
print("\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中,三大件协作如下:
txt
(数据)
↓
┌─────────────┐
│ 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。
python
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增加一个隐藏层?