网络层间分列规律
- 卷积神经网络中最常见的是卷积层后接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)的向量表现。
- # ---------------------------
- # 数据预处理和加载
- # ---------------------------
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.1307,), (0.3081,)) # MNIST 均值和标准差
- ])
- train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
- val_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
- train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
- 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 使用 Tanh 或 Sigmoid 激活函数。
- 当代 CNN 通常使用 ReLU(计算更快,缓解梯度消失)。
- 全连接层占比大
- 后 3 层均为全连接层(C5、F6、输出层),参数目较大。
- 当代 CNN 倾向于淘汰全连接层(如用全局平均池化替代)。
代码实现(Pytorch版)
- 在 PyTorch 中,不像 Keras 那样内置 model.summary() 方法来查看模型布局和参数信息。可以使用第三方库 torchinfo来实现类似功能。
- pip install torchinfo
- summary(model, input_size=(1, 1, 28, 28))
复制代码 项目布局和文件
- data:保存MNIST数据集
- leNet5_checkpoints:保存训练模型
- test_images:保存用于测试的图片
- LetNet5_stu:模型界说、训练和保存
- predict_single_digits:简单使用图片测试训练结果。
源码地点
模型界说和概览
- import torch
- import torch.nn as nn
- import torch.nn.functional as functional
- from torchinfo import summary
- class 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
- # 示例输入 (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")
- # 创建模型实例
- model = LeNet5().to(device)
- # 打印模型结构
- print(model)
- summary(model, input_size=(1, 1, 28, 28))
复制代码- LeNet5(
- (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
- (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
- (relu): ReLU()
- (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
- (fc1): Linear(in_features=400, out_features=120, bias=True)
- (fc2): Linear(in_features=120, out_features=84, bias=True)
- (fc3): Linear(in_features=84, out_features=10, bias=True)
- )
- ==========================================================================================
- Layer (type:depth-idx) Output Shape Param #
- ==========================================================================================
- LeNet5 [1, 10] --
- ├─Conv2d: 1-1 [1, 6, 28, 28] 156
- ├─ReLU: 1-2 [1, 6, 28, 28] --
- ├─MaxPool2d: 1-3 [1, 6, 14, 14] --
- ├─Conv2d: 1-4 [1, 16, 10, 10] 2,416
- ├─ReLU: 1-5 [1, 16, 10, 10] --
- ├─MaxPool2d: 1-6 [1, 16, 5, 5] --
- ├─Linear: 1-7 [1, 120] 48,120
- ├─Linear: 1-8 [1, 84] 10,164
- ├─Linear: 1-9 [1, 10] 850
- ==========================================================================================
- Total params: 61,706
- Trainable params: 61,706
- Non-trainable params: 0
- Total mult-adds (M): 0.42
- ==========================================================================================
- Input size (MB): 0.00
- Forward/backward pass size (MB): 0.05
- Params size (MB): 0.25
- Estimated Total Size (MB): 0.30
- ==========================================================================================
复制代码 LeNet5网络训练和模型保存
- 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)) # ---------------------------
- # 数据预处理和加载
- # ---------------------------
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.1307,), (0.3081,)) # MNIST 均值和标准差
- ])
- train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
- val_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
- train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
- val_loader = DataLoader(dataset=val_dataset, batch_size=64, shuffle=False)
- # --------------------------- # 损失函数和优化器 # --------------------------- 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}")
复制代码
- Using device: cudaLeNet5(
- (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
- (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
- (relu): ReLU()
- (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
- (fc1): Linear(in_features=400, out_features=120, bias=True)
- (fc2): Linear(in_features=120, out_features=84, bias=True)
- (fc3): Linear(in_features=84, out_features=10, bias=True)
- )
- ==========================================================================================
- Layer (type:depth-idx) Output Shape Param #
- ==========================================================================================
- LeNet5 [1, 10] --
- ├─Conv2d: 1-1 [1, 6, 28, 28] 156
- ├─ReLU: 1-2 [1, 6, 28, 28] --
- ├─MaxPool2d: 1-3 [1, 6, 14, 14] --
- ├─Conv2d: 1-4 [1, 16, 10, 10] 2,416
- ├─ReLU: 1-5 [1, 16, 10, 10] --
- ├─MaxPool2d: 1-6 [1, 16, 5, 5] --
- ├─Linear: 1-7 [1, 120] 48,120
- ├─Linear: 1-8 [1, 84] 10,164
- ├─Linear: 1-9 [1, 10] 850
- ==========================================================================================
- Total params: 61,706
- Trainable params: 61,706
- Non-trainable params: 0
- Total mult-adds (M): 0.42
- ==========================================================================================
- Input size (MB): 0.00
- Forward/backward pass size (MB): 0.05
- Params size (MB): 0.25
- Estimated Total Size (MB): 0.30
- ==========================================================================================
- 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
复制代码 单图单数字测试
- pip install opencv-python
复制代码
- leNet5_stu/test_images/single_digit.png
- predict_single_digits.py
- import os
- import torch
- import cv2
- import torchvision.transforms as transforms
- from deep_learning_skill.leNet5_stu.LeNet5_stu import LeNet5 # 导入模型定义
- # ---------------------------
- # 配置参数
- # ---------------------------
- MODEL_PATH = 'leNet5_checkpoints/model_epoch_08_valacc_0.991.pth'
- IMAGE_PATH = 'test_images/single_digit.png' # 替换为自己的图片路径
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- print(f"Using device: {device}")
- # ---------------------------
- # 图像预处理函数
- # ---------------------------
- def preprocess_image(image_path):
- # 加载图像并转为灰度图
- image = cv2.imread(image_path, 0)
- if image is None:
- raise FileNotFoundError(f"找不到图像文件:{image_path}")
- # 调整尺寸为 28x28
- image_resized = cv2.resize(image, (28, 28))
- # 图像增强对比度(可选)
- _, image_binary = cv2.threshold(image_resized, 128, 255, cv2.THRESH_BINARY_INV)
- # 转换为 Tensor 并标准化(使用 MNIST 的均值和标准差)
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.1307,), (0.3081,))
- ])
- # 增加 batch 维度 [1, 1, 28, 28]
- image_tensor = transform(image_binary).unsqueeze(0).to(device)
- return image_tensor
- # ---------------------------
- # 推理函数
- # ---------------------------
- def predict_digit(model, image_tensor):
- model.eval()
- with torch.no_grad():
- output = model(image_tensor)
- _, predicted = torch.max(output, 1)
- return predicted.item()
- # ---------------------------
- # 主程序入口
- # ---------------------------
- if __name__ == '__main__':
- # 加载模型结构
- model = LeNet5().to(device)
- # 加载训练好的权重
- if not os.path.exists(MODEL_PATH):
- raise FileNotFoundError(f"找不到模型文件:{MODEL_PATH}")
- model.load_state_dict(torch.load(MODEL_PATH, map_location=device))
- print(f"模型已加载:{MODEL_PATH}")
- # 加载并预处理图像
- if not os.path.exists(IMAGE_PATH):
- raise FileNotFoundError(f"找不到测试图片:{IMAGE_PATH}")
- image_tensor = preprocess_image(IMAGE_PATH)
- # 进行预测
- predicted_label = predict_digit(model, image_tensor)
- print(f"识别结果:这张图片中的数字是 -> {predicted_label}")
复制代码- Using device: cuda
- 模型已加载:leNet5_checkpoints/model_epoch_08_valacc_0.991.pth
- 识别结果:这张图片中的数字是 -> 3
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |