1.10 全连接神经网络的完整训练流程(第二章收尾)

小节开头思维引导

前面学的 “前向传播、损失函数、反向传播、优化器” 都是 “零散的零件”,这一节要把它们组装成 “完整的训练机器”。你会学到从 “加载数据” 到 “训练多轮”,再到 “评估模型效果” 的全流程,还会用 MNIST 手写数字识别实战 —— 这是你第一次完整训练一个深度学习模型,也是后续学 CNN、Transformer 等复杂模型的 “模板流程”。

小节详细内容

  1. 先搞懂 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)。
  1. 完整训练流程: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()  # 设为评估模式,即可用于预测
  1. 全连接网络的局限性:为后续 CNN 铺垫

    虽然能训练出 95% 准确率的模型,但全连接网络处理 “图片” 有个致命问题 ——参数爆炸

  • 例子:如果处理 32×32 的彩色图片(3 个通道),输入层神经元数 = 32×32×3=3072;若隐藏层设 2048 个神经元,仅输入层→隐藏层的权重参数就有 3072×2048≈630 万,训练慢且容易过拟合。
  • 原因:全连接网络 “不考虑图片的空间信息”(比如图片中 “相邻像素相关性高”),把图片当成 “扁平的向量”,浪费了图片的结构特征 —— 这就是下一章要学 “卷积神经网络(CNN)” 的原因,CNN 能高效提取图片的空间特征,解决参数爆炸问题。

小节结尾思维引导

到这里,第二章 “全连接神经网络” 就彻底学完了 —— 你不仅掌握了 “神经元→层→网络→训练流程” 的完整理论,还实战了 MNIST 任务,能独立训练、评估、保存模型。但全连接网络处理图片的 “参数爆炸” 问题,需要用 CNN 来解决。下一章会从 “卷积操作” 入手,带你理解 CNN 是怎么 “聪明地提取图片特征” 的,彻底摆脱全连接的局限。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter_Monster

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值