手动跑一次训练迭代

🎉摘要:本文通过PyTorch代码演示手动跑一次训练迭代,使用MNIST CNN模型、交叉熵损失和Adam优化器,展示训练前后Loss变化及不同预测质量下的Loss行为,帮助理解损失函数与优化器的工作原理。

将下面代码保存到 chapter06_loss_optimizer.py 文件,运行代码手动跑一次训练迭代,观察损失变化。

代码如下:

"""
第 6 章:损失函数与优化器 —— 手动跑一次训练迭代

运行方式:python chapter06_loss_optimizer.py
"""
import torch
import torch.nn as nn
import torch.optim as optim

# 复制第5章的网络定义(实际项目中你会从独立文件导入) ──
class MNIST_CNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 第一层卷积:输入单通道灰度图,输出32个特征图,3*3卷积、padding保持尺寸不变
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
        # 最大池化:窗口2×2,特征图宽高缩小一半
        self.pool1 = nn.MaxPool2d(kernel_size=2)

        # 第二层卷积:接收32路特征,输出64路特征
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        # 第二层池化,再次缩小特征图尺寸
        self.pool2 = nn.MaxPool2d(kernel_size=2)

        # 全连接层1:展平后特征数量64*7*7,映射到128个神经元
        self.fc1 = nn.Linear(64 * 7 * 7, 128)

        # 输出层:映射为10个类别(数字0~9)
        self.fc2 = nn.Linear(128, 10)

    # 前向传播,模型(data)时会自动调用
    def forward(self, x):
        x = torch.relu(self.conv1(x))  # 卷积+激活函数,提取基础图像特征
        x = self.pool1(x)              # 池化,降低特征图尺寸,减少计算量
        x = torch.relu(self.conv2(x))  # 第二层卷积,提取深层特征
        x = self.pool2(x)              # 第二次池化
        x = x.view(x.size(0), -1)      # 展平:保留batch维度,其余维度合并成一维
        x = torch.relu(self.fc1(x))    # 全连接+激活
        x = self.fc2(x)                # 最终输出各类别得分
        return x


# 1. 准备"假数据"
batch_size = 4
images = torch.randn(batch_size, 1, 28, 28)
labels = torch.tensor([3, 7, 0, 5])  # 假装正确答案是 3, 7, 0, 5

# 2. 创建模型、损失函数、优化器
model = MNIST_CNN()
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 3. 演示:训练前看一次 Loss
print("=" * 50)
print("  单次训练迭代演示")
print("=" * 50)

with torch.no_grad():  # 这个模式下不记录计算图(只做前向,不算梯度)
    before = model(images)
    loss_before = loss_fn(before, labels)

print(f"\n训练前:")
print(f"  模型输出(logits): {before.argmax(dim=1).tolist()}")
print(f"  正确答案:           {labels.tolist()}")
print(f"  Loss: {loss_before.item():.4f}")

# 4. 训练一次(核心五步)
optimizer.zero_grad()             # ③ 清零梯度
outputs = model(images)           # ① 前向传播
loss = loss_fn(outputs, labels)   # ② 计算损失
loss.backward()                   # ④ 反向传播
optimizer.step()                  # ⑤ 更新参数

# 5. 训练后再看一次 Loss
with torch.no_grad():
    after = model(images)
    loss_after = loss_fn(after, labels)

print(f"\n训练后(1次迭代):")
print(f"  模型输出: {after.argmax(dim=1).tolist()}")
print(f"  Loss: {loss_after.item():.4f}")
print(f"  Loss 变化: {loss_before.item():.4f} → {loss_after.item():.4f}")

# 6. 演示:不同预测质量对应不同 Loss
print("\n" + "=" * 50)
print("  损失函数行为演示")
print("=" * 50)

# 模拟三种情况
good_logits = torch.tensor([[0.1, 0.1, 0.1, 9.0, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]])
ok_logits   = torch.tensor([[0.5, 0.5, 1.0, 3.0, 0.5, 1.5, 0.5, 0.5, 0.5, 0.5]])
bad_logits  = torch.tensor([[1.0, 1.5, 1.0, 0.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]])
true_label  = torch.tensor([3])  # 正确答案都是 3

for name, logits in [("预测很好", good_logits), ("预测一般", ok_logits), ("预测很差", bad_logits)]:
    l = loss_fn(logits, true_label).item()
    probs = torch.softmax(logits, dim=1)[0]
    print(f"  {name}: Loss={l:.4f}, 数字3的预测概率={probs[3]:.2%}")

运行代码,输出如下:

==================================================
  单次训练迭代演示
==================================================

训练前:
  模型输出(logits): [6, 6, 6, 6]
  正确答案:           [3, 7, 0, 5]
  Loss: 2.3690

训练后(1次迭代):
  模型输出: [3, 7, 0, 0]
  Loss: 1.7556
  Loss 变化: 2.3690 → 1.7556

==================================================
  损失函数行为演示
==================================================
  预测很好: Loss=0.0012, 数字3的预测概率=99.88%
  预测一般: Loss=0.6591, 数字3的预测概率=51.73%
  预测很差: Loss=3.1123, 数字3的预测概率=4.45%

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号