当前位置: 首页 > news >正文

TASK 1 训练一个网络识别手写数字

TASK 1 训练一个网络识别手写数字

1、导入必要的库

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import matplotlib.pyplot as plt
  • torch: PyTorch 的核心库,提供张量计算和自动微分功能。
  • torch.nn: 包含神经网络层(如卷积层、全连接层)和损失函数。
  • torch.optim: 提供优化算法(如 Adam、SGD)。
  • torchvision: 提供数据集(如 MNIST)和图像预处理工具。
  • DataLoader: 用于批量加载数据并支持多线程加速。
  • matplotlib.pyplot: 用于可视化图像和结果

2、数据预处理和加载

transform = transforms.Compose([transforms.ToTensor(),          # 将PIL图像或NumPy数组转为PyTorch张量,并归一化到[0,1]transforms.Normalize((0.1307,), (0.3081,))  # 标准化(均值0.1307,标准差0.3081)
])
  • ToTensor(): 这是一个将图像数据转换为PyTorch张量并进行标准化处理的转换器。

    • 具体操作

      • 数据类型转换
        • 输入:PIL图像 或 NumPy数组(uint8类型,范围[0,255])
        • 输出:PyTorch张量(float32类型,范围[0,1])
      • 数值范围缩放
        • 原始:0 ─────── 255(整数)
        • 转换后:0.0 ─── 1.0(浮点数)
        • 公式:像素值 / 255.0
      • 维度顺序调整
        • 原始图像格式:(H, W, C) ← (高度, 宽度, 通道)
        • 转换后格式:(C, H, W) ← (通道, 高度, 宽度)
        • 对于MNIST(灰度图):(28, 28)(1, 28, 28)
        • 对于彩色图像:(H, W, 3)(3, H, W)
      • 为什么需要这样做?
        • 数值稳定性:将整数转换为浮点数,便于梯度计算
        • 模型期望:PyTorch的卷积层期望输入格式为 (批次大小, 通道数, 高度, 宽度)
        • 标准化基础:为后续的Normalize操作做准备
  • Normalize(): 对每个像素值进行标准化,使数据分布更加稳定。

    • 具体操作

      • 数学公式
        • 对于每个像素:(x - 0.1307) / 0.3081
      • 参数解释
        • (0.1307,):MNIST数据集的平均像素值(均值)
        • (0.3081,):MNIST数据集的标准差
        • MNIST的均值和是通过计算整个MNIST数据集的统计特性得到的
      • 实际计算示例
        • 如果一个像素原始值为 0.5(经过ToTensor后):
        • 标准化后:(0.5 - 0.1307) / 0.3081 ≈ 1.198
      • 为什么需要标准化?
        1. 加速收敛
          • 原始数据分布可能不均匀
          • 标准化后数据均值为0,标准差为1,便于优化器快速收敛
        2. 数值稳定性
          • 防止梯度爆炸或消失
          • 使激活函数工作在敏感区域
        3. 泛化能力
          • 使模型对不同亮度、对比度的图像更具鲁棒性
train_data = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_data = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
  • datasets.MNIST: 下载 MNIST 数据集(训练集和测试集),存储在 ./data 目录。
  • transform: 对数据应用预处理流程。
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=1000, shuffle=False)
  • DataLoader: 将数据集分批次加载(训练集每批64张,测试集每批1000张)。
  • shuffle=True: 打乱训练数据顺序,避免模型学习到顺序偏差。

3、定义CNN模型

class CNN(nn.Module):def __init__(self):super(CNN, self).__init__()self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)self.fc1 = nn.Linear(64 * 7 * 7, 128)self.fc2 = nn.Linear(128, 10)
  • 类定义和初始化

    • class CNN(nn.Module): 定义一个继承自 nn.Module 的神经网络类(PyTorch要求所有自定义网络都必须继承此类)。
    • super(CNN, self).__init__(): 调用父类的构造函数,确保正确初始化。
  • 卷积层定义

    • 第一层卷积
      self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
      
      • in_channels=1: 输入通道数(MNIST是灰度图,所以是1个通道)
      • out_channels=32: 输出通道数(即使用32个不同的卷积核)
      • kernel_size=3: 卷积核大小 3×3
      • stride=1: 滑动步长为1
      • padding=1: 边缘填充1圈(保持输入输出尺寸相同
    • 公式:

      \[输出尺寸 = (输入尺寸 + 2×填充 - 卷积核大小) ÷ 步长 + 1 \]

    • 计算过程
      • 输入尺寸: 28(MNIST图像是28×28)

      • 卷积核大小: 3

      • 填充: 1(四周各补1圈0)

      • 步长: 1(每次移动1个像素)

        \[输出尺寸 = (28 + 2×1 - 3) ÷ 1 + 1= (28 + 2 - 3) ÷ 1 + 1= (27) ÷ 1 + 1= 27 + 1= 28 \]

  • 池化层输出尺寸公式

    \[输出尺寸 = 输入尺寸 ÷ 池化窗口大小 \]

    • 具体计算(第一次池化)
      • 输入尺寸: 28

      • 池化窗口: 2×2

      • 步长: 默认为2(与窗口大小相同)

        \[输出尺寸 = 28 ÷ 2 = 14 \]

  • 全连接层

    • 第一全连接层
      self.fc1 = nn.Linear(64 * 7 * 7, 128)
      
      • 参数详解
        • in_features=64*7*7: 输入特征数(64通道 × 7高度 × 7宽度 = 3136)
        • out_features=128: 输出特征数(128个神经元)
    • 第二全连接层(输出层)
      self.fc2 = nn.Linear(128, 10)
      
      • in_features=128: 输入128维特征
      • out_features=10: 输出10个数字类别(0-9)的概率
  • 前向传播

        def forward(self, x):x = torch.relu(self.conv1(x))          # 卷积1 + ReLU激活x = torch.max_pool2d(x, 2)            # 最大池化(2x2窗口,步长2)x = torch.relu(self.conv2(x))          # 卷积2 + ReLUx = torch.max_pool2d(x, 2)             # 最大池化x = x.view(-1, 64 * 7 * 7)            # 展平为向量x = torch.relu(self.fc1(x))            # 全连接层1 + ReLUx = self.fc2(x)                        # 全连接层2(输出未激活,因CrossEntropyLoss自带Softmax)return x
    
    • torch.relu: ReLU激活函数,增加非线性。
    • max_pool2d: 最大池化,降低特征图尺寸(28x28 → 14x14 → 7x7)。
    • view(-1, 64*7*7): 将4D张量 [batch, channels, height, width] 展平为2D [batch, features]
  • 整个网络的数据变化过程

    一步一步来看

    1. 开始: 输入图像 1×28×28 ← (通道, 高, 宽)
      • 就像一张28×28的灰度照片
    2. 第一层卷积后: 32×28×28
      • 用32个不同的3×3滤镜扫描图片
      • 得到32张新的28×28特征图
    3. 第一次池化后: 32×14×14
      • 每2×2的区域取最大值
      • 尺寸减半,变成14×14
    4. 第二层卷积后: 64×14×14
      • 用64个滤镜再次扫描
      • 得到64张14×14特征图
    5. 第二次池化后: 64×7×7
      • 再次取2×2区域最大值
      • 尺寸再减半,变成7×7
    6. 展平后: 3136个数字
      • 把64×7×7=3136个数字排成一长串
    7. 全连接层1: 3136 → 128
      • 从3136个特征中选出128个最重要的
    8. 全连接层2: 128 → 10
      • 从128个特征判断是哪个数字(0-9)
  • 参数数量计算(简单理解)

    卷积层就像滤镜工厂
    • 第一层: 32个3×3的滤镜 → 32 × 3 × 3 = 288个权重 + 32个偏置 = 320个参数
    • 第二层: 64个3×3×32的滤镜 → 64 × 3 × 3 × 32 = 18,432个权重 + 64个偏置 = 18,496个参数
    全连接层就像投票系统
    • 第一全连接: 3136人每人给128个选项投票 → 3136 × 128 = 401,408个权重 + 128个偏置 = 401,536个参数
    • 第二全连接: 128人每人给10个选项投票 → 128 × 10 = 1,280个权重 + 10个偏置 = 1,290个参数

    总共: 320 + 18,496 + 401,536 + 1,290 = 421,642个可调节的"旋钮"

  • 总结成一句话

    这个CNN就像:

    1. 先用各种滤镜扫描图片(卷积层)
    2. 然后压缩重要信息(池化层)
    3. 最后投票决定是什么数字(全连接层)

    每次卷积保持尺寸,每次池化尺寸减半,最终从28×28变成7×7!

4、定义损失函数和优化器

  • 损失函数

    criterion = nn.CrossEntropyLoss()
    

    这是一个多分类交叉熵损失函数,专门用于多类别分类问题(比如10个数字的分类)。

    它做什么?

    1. 计算预测误差:比较模型的预测结果和真实标签之间的差异
    2. 指导模型学习:告诉模型哪些预测是正确的,哪些是错误的

    工作原理(简单版)

    假设模型对一张"7"的图片预测:

    • 输出:[0.1, 0.2, 0.05, 0.1, 0.1, 0.1, 0.1, 0.6, 0.05, 0.1]
      • 每个数字代表对应类别(0-9)的得分
      • 第7个位置(索引7)的值0.6最大,表示预测为数字7
    • 真实标签:7(不是one-hot编码,就是数字7)

    损失函数会计算这个预测的"错误程度"

    为什么适合数字识别?

    • 直接处理类别标签(0,1,2,...,9)
    • 内部自动包含Softmax,将得分转换为概率
    • 非常适合多分类问题
  • 优化器

    optimizer = optim.Adam(model.parameters(), lr=0.001)
    

    这是一个Adam优化器,负责根据损失函数的反馈来更新模型的参数(权重)。

    参数详解

    • model.parameters():获取模型中所有需要训练的参数
      • 包括:卷积层的权重和偏置、全连接层的权重和偏置
      • 总共约42万个参数(前面计算过)
    • lr=0.001:学习率(Learning Rate)
      • 作用:控制每次参数更新的步长
      • 为什么是0.001?:这是深度学习中常用的默认值
        • 太大(如0.1):可能跳过最优解
        • 太小(如0.00001):学习太慢
        • 0.001:在大多数任务上效果良好

    Adam优化器的特点

    1. 自适应学习率:为每个参数设置不同的学习率
    2. 动量机制:保持之前的更新方向,加速收敛
    3. 偏差校正:防止训练初期的不稳定

    类比理解

    • 模型:一个学生
    • 损失函数:考试分数(告诉学生答错了多少题)
    • 优化器:老师(根据错题指导学生如何改进)
    • 学习率:老师的严格程度
      • 太严格:学生不敢尝试新方法
      • 太宽松:学生进步太慢

5、训练函数

def train(model, device, train_loader, optimizer, epoch):model.train()  # 设置为训练模式(启用Dropout/BatchNorm)for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()  # 清空梯度output = model(data)   # 前向传播loss = criterion(output, target)  # 计算损失loss.backward()        # 反向传播optimizer.step()       # 更新权重
  • 函数定义

    def train(model, device, train_loader, optimizer, epoch):
    
    • model: 要训练的神经网络模型(我们的CNN)
    • device: 计算设备(CPU或GPU)
    • train_loader: 数据加载器,提供批量训练数据
    • optimizer: 优化器(Adam),负责更新模型参数
    • epoch: 当前训练轮次,用于打印进度
  • 设置为训练模式

    model.train()  # 设置为训练模式(启用Dropout/BatchNorm)
    

    为什么需要这个?

    • 训练模式:启用Dropout、BatchNorm等训练特有的层
    • 评估模式:这些层在测试时会表现不同(model.eval()
    • 就像学生:上课时(训练)要积极尝试,考试时(测试)要稳定发挥
  • 遍历数据批次

    for batch_idx, (data, target) in enumerate(train_loader):
    
    • batch_idx: 当前批次的索引(0, 1, 2, ...)
    • data: 一批图像数据,形状为 [64, 1, 28, 28] ← (批次大小, 通道, 高, 宽)
    • target: 对应的真实标签,形状为 [64] ← 64个数字标签
    • enumerate: 同时获取索引和数据
  • 数据转移到设备

    data, target = data.to(device), target.to(device)
    

    作用:将数据从CPU内存转移到GPU显存(如果可用)

    • CPU: data 在内存中
    • GPU: data 在显存中,计算速度更快
    • 自动检测: device 参数自动选择最佳设备
  • 清空梯度

    optimizer.zero_grad()  # 清空梯度
    

    为什么需要清空?

    • PyTorch会累积梯度(默认行为)
    • 如果不清空,每次loss.backward()都会累加梯度
    • 就像记账:每次计算新账目前要清空旧账本

    数学原理:

    • 梯度:损失对每个权重的偏导数

      \[\frac{\partial loss}{\partial w} \]

    • 需要从0开始计算新的梯度

  • 前向传播

    output = model(data)   # 前向传播
    
    发生了什么?
    1. 数据通过卷积层、池化层、全连接层
    2. 最终得到输出:[64, 10] ← (批次大小, 10个类别的得分)
    3. 比如:output[0] = [2.1, -0.5, 1.3, ..., 0.8] 表示第一个样本的10个数字得分
  • 计算损失

    loss = criterion(output, target)  # 计算损失
    
    具体计算
    • output: 模型预测的10个得分 [64, 10]
    • target: 真实标签 [64](如 [7, 2, 1, ..., 9]
    • criterion: CrossEntropyLoss,计算预测与真实的差异
    例子
    • 如果模型正确预测数字7(第7个得分最高),损失较小
    • 如果错误预测,损失较大
  • 反向传播

    loss.backward()        # 反向传播
    

    这是最神奇的一步!

    1. 自动微分:PyTorch自动计算所有参数的梯度
    2. 链式法则:从输出层逐层反向计算到输入层
    3. 结果:每个参数都知道自己应该怎么调整才能减少损失

    具体来说

    • 计算出:

      \[\frac{\partial loss}{\partial w_1}, \frac{\partial loss}{\partial w_2}, ..., \frac{\partial loss}{\partial w_{421642}} \]

    • 总共计算42万多个梯度!

  • 更新权重

    optimizer.step()       # 更新权重
    

    根据梯度调整参数

    • Adam优化器:使用自适应学习率更新每个参数

    • 更新公式(简化):

      \[w_{new} = w_{old} - \eta \cdot \frac{\partial loss}{\partial w} \]

    就像走山路

    • 梯度:告诉你哪个方向是下山(减少损失)
    • 优化器:沿着这个方向走一小步(学习率控制步长)
  • 完整训练过程类比

    学生学习比喻

    1. model.train():学生进入学习状态
    2. data.to(device):准备好学习资料
    3. optimizer.zero_grad():清空旧知识,准备学新的
    4. model(data):学生尝试解题(前向传播)
    5. criterion(output, target)老师批改作业,给出分数(损失)
    6. loss.backward():学生分析错题,知道哪里错了(反向传播)
    7. optimizer.step():学生改正错误,更新知识(参数更新)

    工厂生产比喻

    1. 原料准备data.to(device)
    2. 清空生产线optimizer.zero_grad()
    3. 加工生产model(data)
    4. 质量检测criterion(output, target)
    5. 调整机器loss.backward()
    6. 优化生产optimizer.step()
  • 总结

    这8行代码完成了深度学习的核心训练循环:

    1. 准备:设置模式 + 转移数据 + 清空梯度
    2. 预测:前向传播获得输出
    3. 评估:计算损失衡量好坏
    4. 学习:反向传播计算梯度 + 优化器更新参数

6、测试函数

def test(model, device, test_loader):model.eval()  # 设置为评估模式(关闭Dropout/BatchNorm)with torch.no_grad():  # 禁用梯度计算for data, target in test_loader:output = model(data)pred = output.argmax(dim=1)  # 取概率最大的类别correct += (pred == target).sum().item()print(f'Test Accuracy: {100. * correct / len(test_loader.dataset):.2f}%')
  • 函数定义

    def test(model, device, test_loader):
    
    • model: 已经训练好的神经网络模型
    • device: 计算设备(CPU或GPU)
    • test_loader: 测试数据加载器,提供批量测试数据
  • 设置为评估模式

    model.eval()  # 设置为评估模式(关闭Dropout/BatchNorm)
    

    为什么需要这个?

    训练模式 vs 评估模式

    模式 Dropout BatchNorm 用途
    训练模式 (model.train()) 启用 使用当前批次统计 训练时
    评估模式 (model.eval()) 关闭 使用训练期统计 测试时

    具体影响

    • Dropout:在测试时应该关闭(否则会随机丢弃神经元,导致结果不稳定)
    • BatchNorm:使用在训练期间学到的移动平均和方差,而不是当前批次的统计量

    类比

    • 训练时:学生要做各种练习题(Dropout模拟不同情况)
    • 考试时:学生要稳定发挥(关闭Dropout,用学到的知识)
  • 禁用梯度计算

    with torch.no_grad():  # 禁用梯度计算
    

    这是关键优化!

    为什么需要禁用梯度?

    1. 节省内存:梯度计算需要存储中间结果,占用大量内存
    2. 加速计算:避免不必要的梯度计算,提高速度
    3. 防止意外更新:确保测试时不会误修改模型参数
  • 遍历测试数据

    for data, target in test_loader:
    
    • data: 一批测试图像,形状 [1000, 1, 28, 28]
    • target: 对应的真实标签,形状 [1000]
    • 这里使用较大的批次大小(1000),因为不需要计算梯度
  • 模型预测

    output = model(data)   # 前向传播
    

    输出形状: [1000, 10] ← (批次大小, 10个数字的得分)

    示例输出:

    output[0] = [1.2, -0.5, 0.8, 2.1, -1.0, 0.3, -0.2, 3.5, 0.1, 1.8]

    • 每个数字代表对应类别(0-9)的得分
    • 得分越高,模型认为属于该类别的可能性越大
  • 获取预测结果

    pred = output.argmax(dim=1)  # 取概率最大的类别
    

    具体操作

    • dim=1: 在第二个维度(类别维度)上取最大值索引

    • 输入: output 形状 [1000, 10]

    • 输出: pred 形状 [1000],每个元素是预测的数字(0-9)

      例子

      • 如果 output[0] = [..., 3.5, ...] 且3.5是最大值
      • 那么 pred[0] = 7(因为索引7对应数字7)
  • 计算正确预测数

    correct += (pred == target).sum().item()
    

    逐步解析

    1. pred == target: 逐元素比较预测和真实标签
      • 返回布尔张量,如 [True, False, True, ...]
      • True 表示预测正确,False 表示错误
    2. .sum(): 统计 True 的数量(正确预测的样本数)
    3. .item(): 将单元素张量转换为Python数值
    4. correct += ...: 累加所有批次的正确预测数
  • 计算并打印准确率

    print(f'Test Accuracy: {100. * correct / len(test_loader.dataset):.2f}%')
    

    计算过程

    • 正确数: correct(所有批次累加的结果)
    • 总数: len(test_loader.dataset) = 10,000(MNIST测试集大小)
    • 准确率: \(\frac{\text{正确数}}{10000} \times 100%\)

    格式化输出

    • 100. *: 转换为百分比
    • :.2f%: 保留两位小数,加上百分号
    • 示例输出:Test Accuracy: 98.76%
  • 完整流程

    1. 准备阶段: 设置评估模式 + 禁用梯度
    2. 预测阶段: 遍历所有测试数据,进行前向传播
    3. 评估阶段: 比较预测结果与真实标签
    4. 统计阶段: 计算总体准确率
  • 总结

    这段测试代码的核心思想是:

    1. 确保一致性:使用评估模式,保持测试结果稳定
    2. 提高效率:禁用梯度计算,节省资源和时间
    3. 准确评估:统计所有测试样本的预测准确率

7、主训练循环

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)  # 将模型移动到GPU(如果可用)for epoch in range(1, 6):  # 训练5轮train(model, device, train_loader, optimizer, epoch)test(model, device, test_loader)
  • 设备检测与分配

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    

    这行代码的作用:自动检测并选择最佳的计算设备

  • 模型转移到设备

    model.to(device)  # 将模型移动到GPU(如果可用)
    

    这行代码的作用:将模型的所有参数和缓冲区移动到指定设备

    具体操作
    1. 模型参数:权重矩阵、偏置向量
    2. 缓冲区:BatchNorm的running mean和variance
    3. 所有层:卷积层、全连接层等的参数
    为什么需要这个?
    • 数据与模型必须在同一设备:否则无法计算
    • GPU加速:如果可用,将模型移到GPU显存中
    • 内存管理:CPU内存和GPU显存是分开的
  • 训练循环

    for epoch in range(1, 6):  # 训练5轮
    

    epoch的概念:一个epoch表示模型完整遍历一次训练数据集

    参数详解
    • range(1, 6):生成序列 [1, 2, 3, 4, 5]

    • 为什么从1开始:更人性化的显示(Epoch 1而不是Epoch 0)

    • 5个epoch:对于MNIST这样的简单数据集,通常足够收敛

      epoch vs batch

      概念 含义 示例
      Epoch 完整遍历一次训练集 60,000张图片都训练一次
      Batch 一次处理的样本数 每次处理64张图片
      Iteration 完成一个batch的训练 60,000/64 ≈ 938次迭代/epoch
  • 训练过程

    train(model, device, train_loader, optimizer, epoch)
    

    调用训练函数,执行以下操作:

    1. 设置训练模式model.train()
    2. 遍历所有batch:使用 train_loader
    3. 数据转移data.to(device)
    4. 前向传播:计算输出和损失
    5. 反向传播:计算梯度
    6. 参数更新optimizer.step()
    设备传递的重要性

    device 参数传递给 train 函数,确保:

    • 数据转移到正确设备
    • 模型和数据在同一设备上计算
  • 测试评估

    test(model, device, test_loader)
    

    在每个epoch后评估模型性能

    为什么要在每个epoch后测试?
    1. 监控训练进度:查看准确率是否提升
    2. 检测过拟合:如果训练准确率上升但测试准确率下降,说明过拟合
    3. 选择最佳模型:保存测试准确率最高的模型参数
    测试过程
    1. 设置评估模式model.eval()
    2. 禁用梯度with torch.no_grad()
    3. 计算准确率:在10,000张测试图片上评估

8、可视化预测结果

data, target = next(iter(test_loader))  # 取一批测试数据
with torch.no_grad():output = model(data)pred = output.argmax(dim=1)plt.figure(figsize=(8, 4))
for i in range(6):plt.subplot(2, 3, i+1)plt.imshow(data[i].cpu().squeeze(), cmap='gray')  # 显示图像plt.title(f'Pred: {pred[i].item()}, True: {target[i].item()}')
plt.show()
  • 获取测试数据

    data, target = next(iter(test_loader))  # 取一批测试数据
    

    这行代码做了三件事

    a) iter(test_loader)
    • 作用:将DataLoader转换为迭代器
    • 原理test_loader本身是一个可迭代对象,iter()使其变成可逐个获取数据的迭代器
    b) next()
    • 作用:获取迭代器的下一个元素(第一个批次)
    • 返回:一个批次的测试数据
    • 数据形状
      • data: [1000, 1, 28, 28] ← (批次大小, 通道, 高, 宽)
      • target: [1000] ← 对应的真实标签
    c) 赋值
    • data: 测试图像数据
    • target: 对应的真实数字标签
  • 模型预测(无梯度模式)

    with torch.no_grad():output = model(data)pred = output.argmax(dim=1)
    
    a) with torch.no_grad():
    • 作用:在这个代码块内禁用梯度计算
    • 为什么需要
      • 节省内存:不存储计算图的中间结果
      • 加速计算:避免不必要的梯度计算
      • 测试阶段不需要更新参数,所以不需要梯度
    b) output = model(data)
    • 作用:模型对这批数据进行预测
    • 输出形状[1000, 10] ← (批次大小, 10个数字的得分)
    • 示例output[0] = [1.2, -0.5, 0.8, ..., 3.2]
    c) pred = output.argmax(dim=1)
    • 作用:获取预测结果(取得分最高的类别)
    • dim=1:在第二个维度(类别维度)上找最大值
    • 输出形状[1000] ← 预测的数字(0-9)
    • 数学运算:对于每个样本,找到10个得分中最大的那个的索引
  • 创建画布

    plt.figure(figsize=(8, 4))
    
    • plt.figure():创建一个新的图形窗口
    • figsize=(8, 4):设置图形大小为8英寸宽、4英寸高
    • 为什么这个尺寸:适合显示2行3列共6个子图
  • 绘制子布

    for i in range(6):plt.subplot(2, 3, i+1)
    
    a) range(6)
    • 循环6次,显示6张图片
    • i 的值:0, 1, 2, 3, 4, 5
    b) plt.subplot(2, 3, i+1)
    • 作用:创建子图网格
    • 参数(行数, 列数, 当前位置)
    • 布局:位置编号: 1 2 3
      4 5 6
    • i+1:因为子图位置从1开始编号
  • 显示图像

    plt.imshow(data[i].cpu().squeeze(), cmap='gray')
    
    a) data[i]
    • 获取第i个样本的图像数据
    • 形状:[1, 28, 28] ← (通道, 高, 宽)
    b) .cpu()
    • 作用:如果数据在GPU上,将其移回CPU
    • 为什么需要matplotlib只能在CPU上显示图像
    • 保险做法:确保数据在正确的设备上
    c) .squeeze()
    • 作用:去除维度为1的维度
    • 输入[1, 28, 28]
    • 输出[28, 28]
    • 为什么需要imshow期望2D或3D数组,不需要通道维度
    d) cmap='gray'
    • 作用:使用灰度色彩映射
    • 为什么:MNIST是灰度图像,不是彩色
  • 设置标题

    plt.title(f'Pred: {pred[i].item()}, True: {target[i].item()}')
    
    a) pred[i].item()
    • pred[i]:第i个样本的预测结果(张量)
    • .item():将单元素张量转换为Python数值
    • 示例:如果预测为数字7,返回 7
    b) target[i].item()
    • 第i个样本的真实标签
    • 同样转换为Python数值
    c) f-string格式化
    • 显示格式:Pred: 7, True: 7(预测正确)
    • 或:Pred: 1, True: 7(预测错误)
  • 显示图形

    plt.show()
    
    • 作用:渲染并显示所有子图
    • 效果:弹出图形窗口显示6张图片及其预测结果
  • 完整的数据流

    原始数据 → 数据加载器 → 一个批次 → 模型预测 → 获取预测结果

    选择6个样本 → 转换为显示格式 → 绘制子图 → 显示结果

  • 总结

    这段代码完成了从模型预测到结果可视化的完整流程:

    1. 数据获取:从测试集中取一个批次
    2. 模型预测:禁用梯度,获取预测结果
    3. 结果解析:转换为人类可读的格式
    4. 可视化:创建子图,显示图像和预测对比
    5. 结果展示:渲染并显示最终结果
http://www.wxhsa.cn/company.asp?id=625

相关文章:

  • 复杂背景验证码的识别思路与图像处理方法
  • Symfony学习笔记 - The Symfony Framework Best Practices
  • 大学军训
  • Vue Day3【综合案例2】vue小兔鲜儿
  • Java 基础知识解析
  • 力扣第3题 无重复字符的最长子串
  • UniApp 自定义导航栏
  • P3177 [HAOI2015] 树上染色
  • UniApp 自定义tabBar
  • NOIP2024复盘
  • Avalonia 学习笔记04. Page Navigation(页面导航) (转载)
  • 判断左手坐标系和右手坐标系的方法
  • 题解:P11894 「LAOI-9」Update
  • 题解:P2012 拯救世界2
  • 今日随笔
  • 一键安装小雅Alist
  • 题解:AT_abc394_c [ABC394C] Debug
  • Lumion Pro 12.0 下载安装教程包含安装包下载、安装、激活超详细图文步骤
  • 题解:CF348C Subset Sums
  • 题解:CF351B Jeff and Furik
  • 题解:CF2118D1 Red Light, Green Light (Easy version)
  • Project Euler题解思路导航(私人)
  • 27届春招备战一轮复习--第五期
  • 阅读方式
  • Audition 2025(AU2025)超详细直装版下载安装教程保姆级
  • pg 解析select语句的返回值
  • 长乐一中 CSP-S 2025 提高级模拟赛 Day2
  • 费用流
  • [豪の学习笔记] 软考中级备考 基础复习#6
  • RAG