深度学习体系学习系列【8】之设计卷积神经网络架构(Pytorch版本) ...

打印 上一主题 下一主题

主题 1826|帖子 1826|积分 5478

网络层间分列规律



  • 卷积神经网络中最常见的是卷积层后接Pooling 层,目的是淘汰下一次卷积输入图像大小,然后重复该过程,提出图像中的高维特征,末了通过全连接层得到输出。因此最常见的卷积神经网络布局:
                                                  I                               N                               P                               U                               T                               →                               [                               C                               O                               N                               V                               ]                               →                               [                               P                               O                               O                               L                               ]                               →                               [                               F                               C                               ]                               →                               O                               U                               T                               P                               U                               T                                         C                               O                               N                               V                               为卷积层,                               P                               O                               O                               L                               为                               P                               o                               o                               l                               i                               n                               g                               层,                               F                               C                               为全连接层,                               [                               x                               ]                               为重复                               x                               层多次                                      INPUT \rightarrow [CONV] \rightarrow [POOL] \rightarrow [FC] \rightarrow OUTPUT \\ CONV为卷积层,POOL为Pooling层,FC为全连接层,[x]为重复x层多次                        INPUT→[CONV]→[POOL]→[FC]→OUTPUTCONV为卷积层,POOL为Pooling层,FC为全连接层,[x]为重复x层多次
  •                                         I                            N                            P                            U                            T                            →                            F                            C                                  INPUT→FC                     INPUT→FC:实现一个简单的线性分类器。
  •                                         I                            N                            P                            U                            T                            →                            C                            O                            N                            V                                  INPUT→CONV                     INPUT→CONV:实现一个滤波操作,如高斯模糊、中值滤波等。
  •                                         I                            N                            P                            U                            T                            →                            [                            C                            O                            N                            V                            →                            P                            O                            L                            L                            ]                            ×                            2                            →                            F                            C                            →                            O                            U                            T                            P                            U                            T                                  INPUT→[CONV→POLL]×2→FC→OUTPUT                     INPUT→[CONV→POLL]×2→FC→OUTPUT:经过两次卷积层接 Pooling 层,实现小规模的卷积神经分类网络。
  •                                         I                            N                            P                            U                            T                            →                            [                            C                            O                            N                            V                            →                            C                            O                            N                            V                            →                            P                            O                            O                            L                            ]                            ×                            3                            →                            [                            F                            C                            ]                            ×                            2                            →                            O                            U                            T                            P                            U                            T                                  INPUT →[CONV → CONV → POOL]×3→ [FC]×2→ OUTPUT                     INPUT→[CONV→CONV→POOL]×3→[FC]×2→OUTPUT:多次一连两个卷积层后接一个Pooling层,该思绪适用于深层次网络(如VGGNet、ResNet 等)。因为在执行 Pooling 操作前,多次小卷积核卷积可以有效淘汰网络权重参数,从输入数据中学习到更高维特征。
卷积神经网络(CNN)参数设计规范

输入层设计



  • 尺寸要求:输入矩阵边长应可被2多次整除,常用尺寸为32、64、96、224、384或512。

    • 理由:便于卷积层和池化层计算(如每次池化输出特征图尺寸减半),淘汰数据丢失。
    • 示例:AlexNet经典输入尺寸为224×224。

卷积层设计


  • 卷积核尺寸

    • 优先使用小卷积核(如1×1、3×3、5×5),深层网络应减小卷积核尺寸。
    • 理由

      • 避免因感知区域过大导致特征提取困难。
      • 小卷积核参数更少,计算服从更高。


  • 步长(Stride)

    • 步长建议设为1,空间下采样由池化层完成。
    • 理由:小步长能保留更多特征信息。

  • 添补(Padding)

    • 使用Same Padding(零添补)保持输入/输出尺寸划一。
    • 添补规则

      • 卷积核尺寸K=3 → padding=1
      • K=5 → padding=2
      • 通用公式:padding=(K-1)/2


池化层设计



  • 推荐参数:2×2窗口,步长为2的Max Pooling。
  • 注意事项: 避免窗口尺寸>3,否则易导致信息丢失。 下采样过于激进会明显降低模型性能。
全连接层设计



  • 层数限制:不超过3层(通常为2个全连接层+1个输出层)。
  • 理由:过多全连接层易引发过拟合和梯度消失。



  • 焦点原则:通过小卷积核、适度添补和保守下采样平衡特征提取与计算服从,避免信息丢失。
可视化手写字体特征网络

MNIST手写字体数据库



  • MNIST 数据集是一个手写体数据集。,数据会合每一个样本都是                                        0                                                         9                                  0~9                     0 9的手写数字,该数据集由 4 部分构成:训练图片集、 训练标签集、测试图片集和测试标签集。训练会合有60000个样本,测试会合有 10000 个样本,每张照片均由 28×28 大小的灰度图像构成。
  • 为了便于存储和下载,官方对MNIST数据集的图片举行会合处理,将每一张图片拉伸成为                                        (                            1                            ,                            784                            )                                  (1,784)                     (1,784)的向量表现。

  1. # ---------------------------
  2.     # 数据预处理和加载
  3.     # ---------------------------
  4.     transform = transforms.Compose([
  5.         transforms.ToTensor(),
  6.         transforms.Normalize((0.1307,), (0.3081,))  # MNIST 均值和标准差
  7.     ])
  8.     train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
  9.     val_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
  10.     train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
  11.     val_loader = DataLoader(dataset=val_dataset, batch_size=64, shuffle=False)
复制代码
LeNet-5 模型介绍



  • LeNet-5 是由 Yann LeCun 等人于 1998 年提出的经典卷积神经网络(CNN),主要用于手写数字识别(如 MNIST 数据集)。它是早期 CNN 的代表,奠定了当代卷积神经网络的基础架构。
  • LeNet-5 由 7 层构成(2 个卷积层 + 2 个池化层 + 3 个全连接层),布局如下:
层范例参数输出尺寸 (W×H×C)输入层32×32 灰度图像32×32×1卷积层 C16 个 5×5 卷积核,步长 16×28×28池化层 S22×2 最大池化,步长 26×14×14卷积层 C316 个 5×5 卷积核,步长 116×10×10池化层 S42×2最大池化,步长 216×5×5全连接层 C5120 个神经元1×120全连接层 F684 个神经元1×84输出层10 个神经元(对应 0-9 数字)1×10

关键特点


  • 小卷积核(5×5)

    • 受限于当时的计算能力,接纳较小的卷积核提取局部特征。
    • 当代 CNN(如 VGG、ResNet)更多使用 3×3 卷积核

  • 平均池化(而非 Max Pooling)

    • LeNet-5 使用 2×2 平均池化(计算窗口内像素均值)。
    • 当代 CNN 普遍接纳 Max Pooling(保留最明显特征)。

  • 激活函数:Tanh / Sigmoid

    • 原始 LeNet-5 使用 TanhSigmoid 激活函数。
    • 当代 CNN 通常使用 ReLU(计算更快,缓解梯度消失)。

  • 全连接层占比大

    • 后 3 层均为全连接层(C5、F6、输出层),参数目较大。
    • 当代 CNN 倾向于淘汰全连接层(如用全局平均池化替代)。

代码实现(Pytorch版)



  • 在 PyTorch 中,不像 Keras 那样内置 model.summary() 方法来查看模型布局和参数信息。可以使用第三方库 torchinfo来实现类似功能。
  1. pip install torchinfo
  2. summary(model, input_size=(1, 1, 28, 28))
复制代码
项目布局和文件




  • data:保存MNIST数据集
  • leNet5_checkpoints:保存训练模型
  • test_images:保存用于测试的图片
  • LetNet5_stu:模型界说、训练和保存
  • predict_single_digits:简单使用图片测试训练结果。
源码地点



  • 本次项目案例地点:letNet5_stu
模型界说和概览

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as functional
  4. from torchinfo import summary
  5. class LeNet5(nn.Module):
  6.     def __init__(self, num_classes=10):
  7.         super(LeNet5, self).__init__()
  8.         # 卷积层
  9.         """
  10.         第一个层卷积:conv1
  11.         in_channels=1:输入图像的通道数。例如,MNIST手写数字图像是灰度图,所以通道数为1。
  12.         out_channels=6:输出通道数(即卷积核数量),表示这一层会提取6个特征图。
  13.         kernel_size=5:卷积核大小为 5x5。
  14.         padding=2:在输入图像四周填充 2 层像素,使得输出特征图与输入图像的空间尺寸一致(即保持尺寸不变)。
  15.         """
  16.         self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)  # 修改 padding 以保持尺寸
  17.         """
  18.         第二个层卷积:conv2
  19.         in_channels=6:上一层输出了 6 个特征图,因此当前层的输入通道数为 6。
  20.         out_channels=16:本层使用 16 个卷积核,输出 16 个特征图。
  21.         kernel_size=5:卷积核大小为 5x5。
  22.         没有指定 padding,默认为 0,因此输出尺寸会比输入小(如输入 14x14 → 输出 10x10)。
  23.         """
  24.         self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
  25.         # 池化层和激活函数显式定义
  26.         """
  27.         定义一个 ReLU(Rectified Linear Unit)激活函数层
  28.         作用:
  29.             1. 在卷积层之后使用 ReLU 可以增强模型的非线性表达能力
  30.         将 ReLU 实例化为类成员变量,因此可以在 forward 函数中通过 self.relu() 调用
  31.             2. 同时也方便torchinfo打印输出信息
  32.         """
  33.         self.relu = nn.ReLU()
  34.         """
  35.         定义一个二维最大池化(Max Pooling)层
  36.         最大池化是一种下采样操作,它从每个池化窗口中取最大值作为输出。
  37.         作用:
  38.             1. 减少特征图的空间尺寸(高度、宽度),从而减少计算量和参数数量。
  39.             2. 提高模型对小范围平移的鲁棒性。
  40.         如:输入尺寸为 28x28 的特征图经过 kernel_size=2, stride=2 的 MaxPool 后,输出尺寸变为 14x14   
  41.         """
  42.         self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
  43.         # 全连接层
  44.         """
  45.         第一个全连接层(Fully Connected Layer)
  46.         作用:将卷积提取到的高维特征映射为一个长度为 120 的向量
  47.         16 * 5 * 5:输入特征的数量。由前面卷积和池化操作后输出的特征图展平后的维度。
  48.             16 是上一层输出的通道数(即特征图数量)
  49.             5 * 5 是每个特征图的空间尺寸(高度 × 宽度),来源于经过多次池化后的结果
  50.         120:该层输出的神经元数量,是 LeNet-5 的设计结构中规定的
  51.         """
  52.         self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 根据图像尺寸调整输入大小
  53.         """
  54.         定义第二个全连接层
  55.         作用:进一步压缩或抽象特征信息,提升模型表达能力
  56.         120:输入来自上一层 (fc1) 的输出
  57.         84:输出神经元数量,也是 LeNet-5 结构中预设的中间层节点数
  58.         """
  59.         self.fc2 = nn.Linear(120, 84)
  60.         """
  61.         定义最后一个全连接层,用于分类输出
  62.         作用:输出每个类别的预测得分,通常配合 Softmax 激活函数得到概率分布
  63.         84:输入来自上一层 (fc2) 的输出。
  64.         mum_classes:类别总数,默认为 10,适用于如 MNIST 手写数字识别任务。
  65.         """
  66.         self.fc3 = nn.Linear(84, num_classes)
  67.         # 可选:加载预训练权重的方法可以自行实现或使用torch.load
  68.     def forward(self, x):
  69.         # 第一层卷积 + 池化
  70.         x = self.relu(self.conv1(x))
  71.         x = self.pool(x)
  72.         # 第二层卷积 + 池化
  73.         x = self.relu(self.conv2(x))
  74.         x = self.pool(x)
  75.         # 展平
  76.         """
  77.         将输入张量 x 从卷积层输出的形状(如 [batch_size, channels, height, width])展平为二维张量,以便输入到全连接层。
  78.         -1:自动推导 batch size 大小。
  79.         16 * 5 * 5:这是经过前面卷积和池化操作后每个样本的特征总数。
  80.             16 是通道数(由 conv2 输出)。
  81.             5 * 5 是每个通道的空间尺寸(高度 × 宽度),来源于经过多次池化后的结果。
  82.         将四维张量 [batch_size, 16, 5, 5] 转换为二维张量 [batch_size, 400],其中 400 = 16×5×5。
  83.         """
  84.         x = x.view(-1, 16 * 5 * 5)
  85.         # 全连接层
  86.         """
  87.         功能:第一个全连接层 + ReLU 激活函数。
  88.         self.fc1 是定义在 __init__ 中的线性层:nn.Linear(16*5*5, 120)。
  89.         输入维度:400(即 16*5*5)。
  90.         输出维度:120。
  91.         作用:将展平后的特征向量映射到更高层次的表示空间。
  92.         """
  93.         x = functional.relu(self.fc1(x))
  94.         """
  95.         功能:第二个全连接层 + ReLU 激活函数。
  96.         self.fc2:nn.Linear(120, 84)。
  97.         输入维度:120。
  98.         输出维度:84。
  99.         作用:进一步压缩和抽象特征信息。
  100.         """
  101.         x = functional.relu(self.fc2(x))
  102.         """
  103.         最后一个全连接层,用于最终分类输出。
  104.         self.fc3:nn.Linear(84, num_classes),默认 num_classes=10。
  105.         输入维度:84。
  106.         输出维度:类别数量(如 MNIST 数据集为 10 类)。
  107.         作用:输出每个类别的预测得分。通常配合 Softmax 函数得到概率分布。
  108.         """
  109.         x = self.fc3(x)
  110.         return x
  111. # 示例输入 (batch_size=1, channel=1, height=28, width=28)
  112. # x = torch.randn(1, 1, 28, 28).to(device)
  113. # y = model(x)
  114. # print(y)
  115. if  __name__ == '__main__':
  116.     device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  117.     # 创建模型实例
  118.     model = LeNet5().to(device)
  119.     # 打印模型结构
  120.     print(model)
  121.     summary(model, input_size=(1, 1, 28, 28))
复制代码
  1. LeNet5(
  2.   (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
  3.   (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  4.   (relu): ReLU()
  5.   (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  6.   (fc1): Linear(in_features=400, out_features=120, bias=True)
  7.   (fc2): Linear(in_features=120, out_features=84, bias=True)
  8.   (fc3): Linear(in_features=84, out_features=10, bias=True)
  9. )
  10. ==========================================================================================
  11. Layer (type:depth-idx)                   Output Shape              Param #
  12. ==========================================================================================
  13. LeNet5                                   [1, 10]                   --
  14. ├─Conv2d: 1-1                            [1, 6, 28, 28]            156
  15. ├─ReLU: 1-2                              [1, 6, 28, 28]            --
  16. ├─MaxPool2d: 1-3                         [1, 6, 14, 14]            --
  17. ├─Conv2d: 1-4                            [1, 16, 10, 10]           2,416
  18. ├─ReLU: 1-5                              [1, 16, 10, 10]           --
  19. ├─MaxPool2d: 1-6                         [1, 16, 5, 5]             --
  20. ├─Linear: 1-7                            [1, 120]                  48,120
  21. ├─Linear: 1-8                            [1, 84]                   10,164
  22. ├─Linear: 1-9                            [1, 10]                   850
  23. ==========================================================================================
  24. Total params: 61,706
  25. Trainable params: 61,706
  26. Non-trainable params: 0
  27. Total mult-adds (M): 0.42
  28. ==========================================================================================
  29. Input size (MB): 0.00
  30. Forward/backward pass size (MB): 0.05
  31. Params size (MB): 0.25
  32. Estimated Total Size (MB): 0.30
  33. ==========================================================================================
复制代码
LeNet5网络训练和模型保存



  • LeNet5_stu.py
  1. import osimport torchimport torch.nn as nnimport torch.nn.functional as functionalfrom torch import optimfrom torchinfo import summaryfrom torchvision import datasets, transformsfrom torch.utils.data import DataLoaderclass LeNet5(nn.Module):    def __init__(self, num_classes=10):        super(LeNet5, self).__init__()        # 卷积层        """        第一个层卷积:conv1        in_channels=1:输入图像的通道数。例如,MNIST手写数字图像是灰度图,以是通道数为1。        out_channels=6:输出通道数(即卷积核数目),表现这一层会提取6个特征图。        kernel_size=5:卷积核大小为 5x5。        padding=2:在输入图像四周添补 2 层像素,使得输出特征图与输入图像的空间尺寸划一(即保持尺寸不变)。        """        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)  # 修改 padding 以保持尺寸        """        第二个层卷积:conv2        in_channels=6:上一层输出了 6 个特征图,因此当前层的输入通道数为 6。        out_channels=16:本层使用 16 个卷积核,输出 16 个特征图。        kernel_size=5:卷积核大小为 5x5。        没有指定 padding,默认为 0,因此输出尺寸会比输入小(如输入 14x14 → 输出 10x10)。        """        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)        # 池化层和激活函数显式界说        """        界说一个 ReLU(Rectified Linear Unit)激活函数层        作用:            1. 在卷积层之后使用 ReLU 可以增强模型的非线性表达能力        将 ReLU 实例化为类成员变量,因此可以在 forward 函数中通过 self.relu() 调用             2. 同时也方便torchinfo打印输出信息        """        self.relu = nn.ReLU()        """        界说一个二维最大池化(Max Pooling)层        最大池化是一种下采样操作,它从每个池化窗口中取最大值作为输出。        作用:            1. 淘汰特征图的空间尺寸(高度、宽度),从而淘汰计算量和参数数目。            2. 进步模型对小范围平移的鲁棒性。        如:输入尺寸为 28x28 的特征图经过 kernel_size=2, stride=2 的 MaxPool 后,输出尺寸变为 14x14            """        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)        # 全连接层        """        第一个全连接层(Fully Connected Layer)        作用:将卷积提取到的高维特征映射为一个长度为 120 的向量        16 * 5 * 5:输入特征的数目。由前面卷积和池化操作后输出的特征图展平后的维度。            16 是上一层输出的通道数(即特征图数目)            5 * 5 是每个特征图的空间尺寸(高度 × 宽度),泉源于经过多次池化后的结果        120:该层输出的神经元数目,是 LeNet-5 的设计布局中规定的        """        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 根据图像尺寸调解输入大小        """        界说第二个全连接层        作用:进一步压缩或抽象特征信息,提升模型表达能力        120:输入来自上一层 (fc1) 的输出        84:输入迷经元数目,也是 LeNet-5 布局中预设的中间层节点数        """        self.fc2 = nn.Linear(120, 84)        """        界说末了一个全连接层,用于分类输出        作用:输出每个类别的预测得分,通常配合 Softmax 激活函数得到概率分布        84:输入来自上一层 (fc2) 的输出。        mum_classes:类别总数,默认为 10,适用于如 MNIST 手写数字识别任务。        """        self.fc3 = nn.Linear(84, num_classes)        # 可选:加载预训练权重的方法可以自行实现或使用torch.load    def forward(self, x):        # 第一层卷积 + 池化        x = self.relu(self.conv1(x))        x = self.pool(x)        # 第二层卷积 + 池化        x = self.relu(self.conv2(x))        x = self.pool(x)        # 展平        """        将输入张量 x 从卷积层输出的形状(如 [batch_size, channels, height, width])展平为二维张量,以便输入到全连接层。        -1:主动推导 batch size 大小。        16 * 5 * 5:这是经过前面卷积和池化操作后每个样本的特征总数。            16 是通道数(由 conv2 输出)。            5 * 5 是每个通道的空间尺寸(高度 × 宽度),泉源于经过多次池化后的结果。        将四维张量 [batch_size, 16, 5, 5] 转换为二维张量 [batch_size, 400],其中 400 = 16×5×5。        """        x = x.view(-1, 16 * 5 * 5)        # 全连接层        """        功能:第一个全连接层 + ReLU 激活函数。        self.fc1 是界说在 __init__ 中的线性层:nn.Linear(16*5*5, 120)。        输入维度:400(即 16*5*5)。        输出维度:120。        作用:将展平后的特征向量映射到更高层次的表现空间。        """        x = functional.relu(self.fc1(x))        """        功能:第二个全连接层 + ReLU 激活函数。        self.fc2:nn.Linear(120, 84)。        输入维度:120。        输出维度:84。        作用:进一步压缩和抽象特征信息。        """        x = functional.relu(self.fc2(x))        """        末了一个全连接层,用于最终分类输出。        self.fc3:nn.Linear(84, num_classes),默认 num_classes=10。        输入维度:84。        输出维度:类别数目(如 MNIST 数据集为 10 类)。        作用:输出每个类别的预测得分。通常配合 Softmax 函数得到概率分布。        """        x = self.fc3(x)        return x# 自界说模型保存函数def save_checkpoint(model, val_acc, filepath='./leNet5_checkpoints'):    if not os.path.exists(filepath):        os.makedirs(filepath)    filename = f"{filepath}/model_epoch_{epoch:02d}_valacc_{val_acc:.3f}.pth"    torch.save(model.state_dict(), filename)    print(f"Saved model to {filename}")# 示例输入 (batch_size=1, channel=1, height=28, width=28)# x = torch.randn(1, 1, 28, 28).to(device)# y = model(x)# print(y)if __name__ == '__main__':    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")    print(f"Using device: {device}")    # 创建模型实例    model = LeNet5().to(device)    print(model)    summary(model, input_size=(1, 1, 28, 28))    # ---------------------------
  2.     # 数据预处理和加载
  3.     # ---------------------------
  4.     transform = transforms.Compose([
  5.         transforms.ToTensor(),
  6.         transforms.Normalize((0.1307,), (0.3081,))  # MNIST 均值和标准差
  7.     ])
  8.     train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
  9.     val_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
  10.     train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
  11.     val_loader = DataLoader(dataset=val_dataset, batch_size=64, shuffle=False)
  12.     # ---------------------------    # 损失函数和优化器    # ---------------------------    criterion = nn.CrossEntropyLoss()    optimizer = optim.Adadelta(model.parameters())    # ---------------------------    # 模型保存路径    # ---------------------------    checkpoint_dir = "leNet5_checkpoints"    if not os.path.exists(checkpoint_dir):        os.makedirs(checkpoint_dir)    best_val_acc = 0.0    # ---------------------------    # 训练和验证循环    # ---------------------------    epochs = 10    for epoch in range(epochs):        # 训练阶段        model.train()        running_loss = 0.0        for inputs, labels in train_loader:            inputs, labels = inputs.to(device), labels.to(device)            optimizer.zero_grad()            outputs = model(inputs)            loss = criterion(outputs, labels)            loss.backward()            optimizer.step()            running_loss += loss.item()        # 验证阶段        model.eval()        correct = total = 0        with torch.no_grad():            for inputs, labels in val_loader:                inputs, labels = inputs.to(device), labels.to(device)                outputs = model(inputs)                _, preds = torch.max(outputs, 1)                total += labels.size(0)                correct += (preds == labels).sum().item()        val_acc = correct / total        avg_loss = running_loss / len(train_loader)        print(f"Epoch [{epoch + 1}/{epochs}], Loss: {avg_loss:.4f}, Val Accuracy: {val_acc:.4f}")        # 保存最佳模型        if val_acc > best_val_acc:            best_val_acc = val_acc            save_path = os.path.join(checkpoint_dir, f"model_epoch_{epoch + 1:02d}_valacc_{val_acc:.3f}.pth")            torch.save(model.state_dict(), save_path)            print(f"Saved best model to {save_path}")
复制代码


  • 训练结果:
  1. Using device: cudaLeNet5(
  2.   (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
  3.   (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  4.   (relu): ReLU()
  5.   (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  6.   (fc1): Linear(in_features=400, out_features=120, bias=True)
  7.   (fc2): Linear(in_features=120, out_features=84, bias=True)
  8.   (fc3): Linear(in_features=84, out_features=10, bias=True)
  9. )
  10. ==========================================================================================
  11. Layer (type:depth-idx)                   Output Shape              Param #
  12. ==========================================================================================
  13. LeNet5                                   [1, 10]                   --
  14. ├─Conv2d: 1-1                            [1, 6, 28, 28]            156
  15. ├─ReLU: 1-2                              [1, 6, 28, 28]            --
  16. ├─MaxPool2d: 1-3                         [1, 6, 14, 14]            --
  17. ├─Conv2d: 1-4                            [1, 16, 10, 10]           2,416
  18. ├─ReLU: 1-5                              [1, 16, 10, 10]           --
  19. ├─MaxPool2d: 1-6                         [1, 16, 5, 5]             --
  20. ├─Linear: 1-7                            [1, 120]                  48,120
  21. ├─Linear: 1-8                            [1, 84]                   10,164
  22. ├─Linear: 1-9                            [1, 10]                   850
  23. ==========================================================================================
  24. Total params: 61,706
  25. Trainable params: 61,706
  26. Non-trainable params: 0
  27. Total mult-adds (M): 0.42
  28. ==========================================================================================
  29. Input size (MB): 0.00
  30. Forward/backward pass size (MB): 0.05
  31. Params size (MB): 0.25
  32. Estimated Total Size (MB): 0.30
  33. ==========================================================================================
  34. 100.0%100.0%100.0%100.0%Epoch [1/10], Loss: 0.1728, Val Accuracy: 0.9834Saved best model to ./leNet5_checkpoints\model_epoch_01_valacc_0.983.pthEpoch [2/10], Loss: 0.0485, Val Accuracy: 0.9876Saved best model to ./leNet5_checkpoints\model_epoch_02_valacc_0.988.pthEpoch [3/10], Loss: 0.0363, Val Accuracy: 0.9890Saved best model to ./leNet5_checkpoints\model_epoch_03_valacc_0.989.pthEpoch [4/10], Loss: 0.0275, Val Accuracy: 0.9892Saved best model to ./leNet5_checkpoints\model_epoch_04_valacc_0.989.pthEpoch [5/10], Loss: 0.0229, Val Accuracy: 0.9852Epoch [6/10], Loss: 0.0185, Val Accuracy: 0.9886Epoch [7/10], Loss: 0.0133, Val Accuracy: 0.9896Saved best model to ./leNet5_checkpoints\model_epoch_07_valacc_0.990.pthEpoch [8/10], Loss: 0.0117, Val Accuracy: 0.9908Saved best model to ./leNet5_checkpoints\model_epoch_08_valacc_0.991.pthEpoch [9/10], Loss: 0.0084, Val Accuracy: 0.9900Epoch [10/10], Loss: 0.0085, Val Accuracy: 0.9905
复制代码
单图单数字测试



  • 安装cv2图像预处理:转灰度、二值化、去噪
  1. pip install opencv-python
复制代码


  • leNet5_stu/test_images/single_digit.png

  • predict_single_digits.py
  1. import os
  2. import torch
  3. import cv2
  4. import torchvision.transforms as transforms
  5. from deep_learning_skill.leNet5_stu.LeNet5_stu import LeNet5  # 导入模型定义
  6. # ---------------------------
  7. # 配置参数
  8. # ---------------------------
  9. MODEL_PATH = 'leNet5_checkpoints/model_epoch_08_valacc_0.991.pth'
  10. IMAGE_PATH = 'test_images/single_digit.png'  # 替换为自己的图片路径
  11. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  12. print(f"Using device: {device}")
  13. # ---------------------------
  14. # 图像预处理函数
  15. # ---------------------------
  16. def preprocess_image(image_path):
  17.     # 加载图像并转为灰度图
  18.     image = cv2.imread(image_path, 0)
  19.     if image is None:
  20.         raise FileNotFoundError(f"找不到图像文件:{image_path}")
  21.     # 调整尺寸为 28x28
  22.     image_resized = cv2.resize(image, (28, 28))
  23.     # 图像增强对比度(可选)
  24.     _, image_binary = cv2.threshold(image_resized, 128, 255, cv2.THRESH_BINARY_INV)
  25.     # 转换为 Tensor 并标准化(使用 MNIST 的均值和标准差)
  26.     transform = transforms.Compose([
  27.         transforms.ToTensor(),
  28.         transforms.Normalize((0.1307,), (0.3081,))
  29.     ])
  30.     # 增加 batch 维度 [1, 1, 28, 28]
  31.     image_tensor = transform(image_binary).unsqueeze(0).to(device)
  32.     return image_tensor
  33. # ---------------------------
  34. # 推理函数
  35. # ---------------------------
  36. def predict_digit(model, image_tensor):
  37.     model.eval()
  38.     with torch.no_grad():
  39.         output = model(image_tensor)
  40.         _, predicted = torch.max(output, 1)
  41.     return predicted.item()
  42. # ---------------------------
  43. # 主程序入口
  44. # ---------------------------
  45. if __name__ == '__main__':
  46.     # 加载模型结构
  47.     model = LeNet5().to(device)
  48.     # 加载训练好的权重
  49.     if not os.path.exists(MODEL_PATH):
  50.         raise FileNotFoundError(f"找不到模型文件:{MODEL_PATH}")
  51.     model.load_state_dict(torch.load(MODEL_PATH, map_location=device))
  52.     print(f"模型已加载:{MODEL_PATH}")
  53.     # 加载并预处理图像
  54.     if not os.path.exists(IMAGE_PATH):
  55.         raise FileNotFoundError(f"找不到测试图片:{IMAGE_PATH}")
  56.     image_tensor = preprocess_image(IMAGE_PATH)
  57.     # 进行预测
  58.     predicted_label = predict_digit(model, image_tensor)
  59.     print(f"识别结果:这张图片中的数字是 -> {predicted_label}")
复制代码
  1. Using device: cuda
  2. 模型已加载:leNet5_checkpoints/model_epoch_08_valacc_0.991.pth
  3. 识别结果:这张图片中的数字是 -> 3
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

瑞星

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表