将下面代码保存到 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%