小节开头思维引导
前面学的 “前向传播、损失函数、反向传播、优化器” 都是 “零散的零件”,这一节要把它们组装成 “完整的训练机器”。你会学到从 “加载数据” 到 “训练多轮”,再到 “评估模型效果” 的全流程,还会用 MNIST 手写数字识别实战 —— 这是你第一次完整训练一个深度学习模型,也是后续学 CNN、Transformer 等复杂模型的 “模板流程”。
小节详细内容
-
先搞懂 3 个核心术语:避免代码里的 confusion
训练流程里经常提到 “Epoch、Batch、Iteration”,这三个词是 “训练进度的单位”,必须先分清:
- Epoch(轮次):把 “所有训练数据完整过一遍” 叫 1 个 Epoch(比如训练集有 60000 个样本,每次用 64 个样本训练,就要跑 938 次小循环才能完成 1 个 Epoch)。
- Batch Size(批次大小):每次训练时 “一次性喂给网络的样本数”(比如 Batch Size=64,就是每次用 64 张图片算损失、求梯度、调参)。
- Iteration(迭代次数):1 个 Epoch 包含的 “小循环次数”(计算公式:Iteration 数 = 训练集总样本数 ÷ Batch Size)。
-
完整训练流程:5 步落地(MNIST 实战代码)
以 “MNIST 手写数字识别”(训练集 60000 张 28×28 手写数字图,测试集 10000 张,10 个类别)为例,用 Pytorch 实现从 0 到 1 的训练,代码可直接复制运行:
步骤 1:数据准备(加载 + 预处理)
Pytorch 提供现成的 MNIST 数据集,不用自己下载;预处理要做 “格式转换” 和 “归一化”,让数据适合网络训练:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 1. 数据预处理:把图片转成张量+归一化(关键!让训练更稳定)
transform = transforms.Compose([
transforms.ToTensor(), # 把PIL图片转成张量,维度:[1,28,28](通道数×高×宽)
transforms.Normalize((0.1307,), (0.3081,)) # MNIST官方推荐的均值和标准差,把像素值从0-255缩到0-1附近
])
# 2. 加载训练集和测试集(download=True:本地没有就自动下载)
train_dataset = datasets.MNIST(
root="./data", # 数据保存路径(可自定义)
train=True, # True=训练集,False=测试集
transform=transform, # 应用上面定义的预处理
download=True
)
test_dataset = datasets.MNIST(
root="./data",
train=False,
transform=transform,
download=True
)
# 3. 批量加载数据(用DataLoader自动分批次,训练时打乱顺序)
train_loader = DataLoader(
dataset=train_dataset,
batch_size=64, # 每次用64个样本(新手可选32/64/128,太大可能内存不够)
shuffle=True # 训练集打乱顺序,避免网络“死记”样本顺序
)
test_loader = DataLoader(
dataset=test_dataset,
batch_size=64,
shuffle=False # 测试集不用打乱,按顺序评估即可
)
步骤 2:初始化 “网络 + 损失函数 + 优化器”
复用之前定义的全连接网络,搭配适合分类任务的损失函数和优化器:
# 1. 定义全连接网络(输入层784=28×28,隐藏层128,输出层10)
class FCNet(nn.Module):
def __init__(self):
super(FCNet, self).__init__()
self.fc1 = nn.Linear(28*28, 128) # 输入层→隐藏层
self.fc2 = nn.Linear(128, 10) # 隐藏层→输出层(10个类别)
self.relu = nn.ReLU() # 隐藏层用ReLU激活函数
def forward(self, x):
# 把图片张量([64,1,28,28])拉平成[64,784](-1表示自动计算批次大小)
x = x.view(-1, 28*28)
x = self.fc1(x) # 第一步:全连接层计算
x = self.relu(x) # 第二步:激活函数处理
x = self.fc2(x) # 第三步:输出层计算(输出logits,无Softmax)
return x
# 2. 初始化核心模块
net = FCNet() # 网络实例
criterion = nn.CrossEntropyLoss() # 分类任务用交叉熵损失(内部含Softmax)
optimizer = optim.Adam(net.parameters(), lr=0.001) # Adam优化器,lr=0.001
步骤 3:训练循环(多轮迭代,核心中的核心)
重复 “取批次数据→前向传播→算损失→反向传播→调参” 的循环,直到训练完指定轮次(这里设 5 个 Epoch,新手不用太多轮):
num_epochs = 5 # 训练5轮(可根据效果调整,轮次太多可能过拟合)
# 外层循环:按轮次迭代
for epoch in range(num_epochs):
net.train() # 把网络设为“训练模式”(后续CNN的Dropout等层需要这个模式)
running_loss = 0.0 # 记录当前轮次的总损失,用于打印进度
running_correct = 0 # 记录当前轮次的正确预测数,计算训练集准确率
# 内层循环:按批次迭代(1个批次=1次Iteration)
for batch_idx, (data, targets) in enumerate(train_loader):
# 1. 前向传播:算预测结果
outputs = net(data) # data是[64,1,28,28],outputs是[64,10](10个类别的logits)
# 2. 计算损失和训练集准确率
loss = criterion(outputs, targets) # 算损失
_, predicted = torch.max(outputs.data, dim=1) # 取概率最大的类别索引(预测结果)
running_correct += (predicted == targets).sum().item() # 累加正确数
running_loss += loss.item() # 累加损失
# 3. 反向传播+调参
optimizer.zero_grad() # 梯度清零(必做)
loss.backward() # 反向传播求梯度
optimizer.step() # 优化器更新参数
# 每100个批次打印一次进度(避免输出太多)
if batch_idx % 100 == 99:
avg_loss = running_loss / 100 # 100个批次的平均损失
avg_acc = 100 * running_correct / (100 * train_loader.batch_size) # 100个批次的平均准确率
print(f"Epoch [{epoch+1}/{num_epochs}], Batch [{batch_idx+1}/{len(train_loader)}]")
print(f"Avg Loss: {avg_loss:.4f}, Avg Train Accuracy: {avg_acc:.2f}%")
# 重置统计变量
running_loss = 0.0
running_correct = 0
步骤 4:模型评估(用测试集验证泛化能力)
训练完后,必须用 “没见过的测试集” 评估模型 —— 避免模型 “死记” 训练数据(过拟合),真正看它 “会不会预测新数据”:
net.eval() # 把网络设为“评估模式”(禁用训练时的特殊层,保证预测稳定)
test_correct = 0 # 测试集正确预测数
test_total = 0 # 测试集总样本数
# 评估时不用计算梯度(节省内存,加快速度)
with torch.no_grad():
for data, targets in test_loader:
outputs = net(data)
_, predicted = torch.max(outputs.data, dim=1)
test_total += targets.size(0) # 累加总样本数
test_correct += (predicted == targets).sum().item() # 累加正确数
# 计算测试集准确率(核心评估指标)
test_acc = 100 * test_correct / test_total
print(f"\n=== Training Finished ===")
print(f"Test Accuracy on 10000 MNIST Images: {test_acc:.2f}%") # 正常效果:95%以上
步骤 5:模型保存(可选,下次直接用,不用重训)
训练好的模型可以保存参数,后续需要时直接加载,节省时间:
# 保存模型参数(推荐!体积小,只存参数,不存网络结构)
torch.save(net.state_dict(), "fc_net_mnist.pth")
print("Model saved as 'fc_net_mnist.pth'")
# 后续加载模型的代码(示例)
# net = FCNet() # 先重建网络结构
# net.load_state_dict(torch.load("fc_net_mnist.pth")) # 加载参数
# net.eval() # 设为评估模式,即可用于预测
-
全连接网络的局限性:为后续 CNN 铺垫
虽然能训练出 95% 准确率的模型,但全连接网络处理 “图片” 有个致命问题 ——参数爆炸:
- 例子:如果处理 32×32 的彩色图片(3 个通道),输入层神经元数 = 32×32×3=3072;若隐藏层设 2048 个神经元,仅输入层→隐藏层的权重参数就有 3072×2048≈630 万,训练慢且容易过拟合。
- 原因:全连接网络 “不考虑图片的空间信息”(比如图片中 “相邻像素相关性高”),把图片当成 “扁平的向量”,浪费了图片的结构特征 —— 这就是下一章要学 “卷积神经网络(CNN)” 的原因,CNN 能高效提取图片的空间特征,解决参数爆炸问题。
小节结尾思维引导
到这里,第二章 “全连接神经网络” 就彻底学完了 —— 你不仅掌握了 “神经元→层→网络→训练流程” 的完整理论,还实战了 MNIST 任务,能独立训练、评估、保存模型。但全连接网络处理图片的 “参数爆炸” 问题,需要用 CNN 来解决。下一章会从 “卷积操作” 入手,带你理解 CNN 是怎么 “聪明地提取图片特征” 的,彻底摆脱全连接的局限。
7921

被折叠的 条评论
为什么被折叠?



