【实验11】卷积神经网络(2)-基于LeNet实现手写体数字识别

👉🏼目录👈🏼 

🍒1. 数据

1.1 准备数据

 1.2 数据预处理

🍒2. 模型构建

2.1 模型测试 

2.2 测试网络运算速度

2.3 输出模型参数量

2.4 输出模型计算量

🍒3. 模型训练

🍒4.模型评价

🍒5.模型预测

🍒6 使用完整数据集,不改变输入图像尺寸,使其为1*28*28的图像输入

6.1在第一层卷积时对图像进行padding = 2 填充 

6.2 在第一层卷积时不对图像进行padding = 2 填充 

 6.3 完整代码

运行结果及调参 

🍒参考链接


1. 数据

1.1 准备数据

本实验用到的数据集为MNIST数据集,在实验开始之前先认识一下吧~

        MNIST数据集是CV领域的经典入门数据集,包含了手写数字的图像,每个图像都是一个28x28像素的灰度图像,并标注了图像所表示的数字(0-9)。MNIST 数据集被广泛用于图像分类任务,尤其是在深度学习领域。如图(源paddle):

  • 训练集:包含60,000个手写数字图像和它们对应的标签(数字0到9)。
  • 测试集:包含10,000个手写数字图像和它们对应的标签。

可见,数据集还是很大的!!为了节省时间,实验选取MNIST数据集的一个子集,数据集划分为:

  • 训练集train_set:1,000条样本
  • 验证集dev_set:200条样本
  • 测试集test_set:200条样本
# 加载数据集
train_set, dev_set, test_set = json.load(gzip.open(r'D:\i don`t want to open\深度学习-实验\实验11-卷积神经网络(2)-LeNet-Mnisit\mnist.json.gz', 'rb'))

# 获取前3000个训练样本,200个验证样本和200个测试样本
train_images, train_labels = train_set[0][:3000], train_set[1][:3000]
dev_images, dev_labels = dev_set[0][:200], dev_set[1][:200]
test_images, test_labels = test_set[0][:200], test_set[1][:200]
train_set, dev_set, test_set = [train_images, train_labels], [dev_images, dev_labels], [test_images, test_labels]
 # 打印数据集长度
print('Length of train/dev/test set: {}/{}/{}'.format(len(train_images), len(dev_images), len(test_images)))

运行结果:

Length of train/dev/test set: 3000/200/200

可视化观察其中的一张样本以及对应的标签:

image, label = train_set[0][0], train_set[1][0]
image, label = np.array(image).astype('float32'), int(label)
# 原始图像数据为长度784的行向量,需要调整为[28,28]大小的图像
image = np.reshape(image, [28, 28])
image = Image.fromarray((image*255).astype('uint8'), mode='L')

print("The number in the picture is {}".format(label))
plt.figure(figsize=(5, 5))
plt.imshow(image)
plt.show()

运行结果: 

The number in the picture is 5

 【这里是下载的老师在群里发的数据集文件已归一化的彩色图像,不是官网处理好的灰度图像】

 1.2 数据预处理

  • 调整图片大小:LeNet网络对输入图片大小的要求为 32×32 ,而MNIST数据集中的原始图片大小却是 28×28 ,为了符合网络的结构设计,将其调整为32×32;
  • 规范化: 把输入图像的分布改变成均值为0,标准差为1的标准正态分布,使得最优解的寻优过程明显会变得平缓,训练过程更容易收敛。
# 数据预处理
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
import numpy as np
from PIL import Image

# 数据预处理 将图像的尺寸修改为32*32,转换为tensor形式。并且将输入图像分布改为均值为0,标准差为1的正态分布
transforms = transforms.Compose(
    [transforms.Resize(32), transforms.ToTensor(), transforms.Normalize(mean=[0.], std=[1.0])])

# 数据集类的定义 定义MNIST_dataset类,继承dataset类
class MNIST_dataset(Dataset):
    # 初始化数据集,接收一个数据集dataset,转换操作transform  测试操作mode='train'
    def __init__(self, dataset, transforms, mode='train'):
        self.mode = mode
        self.transforms = transforms
        self.dataset = dataset

    # 根据索引idx从数据集中获取样本。
    def __getitem__(self, idx):
        # 获取图像和标签
        image, label = self.dataset[0][idx], self.dataset[1][idx]
        # 将图像转换为float32类型
        image, label = np.array(image).astype('float32'), int(label)
        image = np.reshape(image, [28, 28])  # 重塑形状

        # 将重塑后的图像转换为Image对象,应用转换操作
        image = Image.fromarray(image.astype('uint8'), mode='L')
        image = self.transforms(image)

        return image, label

    # 返回数据集中的样本数量
    def __len__(self):
        return len(self.dataset[0])

# 加载 mnist 数据集 这些数据集在MNIST_dataset类中被初始化,并用于训练、测试和开发模型
train_dataset = MNIST_dataset(dataset=train_set, transforms=transforms, mode='train')
test_dataset = MNIST_dataset(dataset=test_set, transforms=transforms, mode='test')
dev_dataset = MNIST_dataset(dataset=dev_set, transforms=transforms, mode='dev')

2. 模型构建

        网络共有7层,包含3个卷积层、2个汇聚层以及2个全连接层的简单卷积神经网络接,受输入图像大小为32×32=1 024,输出对应10个类别的得分。 

class LeNet(nn.Module):
    def __init__(self, in_channels, num_classes=10):
        super(LeNet, self).__init__()
        # 卷积层:输出通道数为6,卷积核大小为5×5
        self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5)
        # 汇聚层:汇聚窗口为2×2,步长为2
        self.pool2 = nn.MaxPool2d(2, stride=2)
        # 卷积层:输入通道数为6,输出通道数为16,卷积核大小为5×5,步长为1
        self.conv3 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1)
        # 汇聚层:汇聚窗口为2×2,步长为2
        self.pool4 = nn.AvgPool2d(2, stride=2)
        # 卷积层:输入通道数为16,输出通道数为120,卷积核大小为5×5
        self.conv5 = nn.Conv2d(in_channels=16, out_channels=120, kernel_size=5, stride=1)
        # 全连接层:输入神经元为120,输出神经元为84
        self.linear6 = nn.Linear(120, 84)
        # 全连接层:输入神经元为84,输出神经元为类别数
        self.linear7 = nn.Linear(84, num_classes)

    def forward(self, x):
        # C1:卷积层+激活函数
        output = F.relu(self.conv1(x))
        # S2:汇聚层
        output = self.pool2(output)
        # C3:卷积层+激活函数
        output = F.relu(self.conv3(output))
        # S4:汇聚层
        output = self.pool4(output)
        # C5:卷积层+激活函数
        output = F.relu(self.conv5(output))
        # 输入层将数据拉平[B,C,H,W] -> [B,CxHxW]
        output = torch.squeeze(output, dim=3)
        output = torch.squeeze(output, dim=2)
        # F6:全连接层
        output = F.relu(self.linear6(output))
        # F7:全连接层
        output = self.linear7(output)
        return output

2.1 模型测试 

         测试一下上面的LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入网络,观察每一层特征图的形状变化。

# 这里用np.random创建一个随机数组作为输入数据
inputs = np.random.randn(*[1, 1, 32, 32])
inputs = inputs.astype('float32')
model = LeNet(in_channels=1, num_classes=10)
c = []
for a, b in model.named_children():
    c.append(a)
print(c)
x = torch.tensor(inputs)
for a, item in model.named_children():
    try:
        x = item(x)
    except:
        x = torch.reshape(x, [x.shape[0], -1])
        x = item(x)
    print(a, x.shape, sep=' ', end=' ')
    for name, value in item.named_parameters():
        print(value.shape, end=' ')
    print()

运行结果:

['conv1', 'pool2', 'conv3', 'pool4', 'conv5', 'linear6', 'linear7']
conv1 torch.Size([1, 6, 28, 28]) torch.Size([6, 1, 5, 5]) torch.Size([6]) 
pool2 torch.Size([1, 6, 14, 14]) 
conv3 torch.Size([1, 16, 10, 10]) torch.Size([16, 6, 5, 5]) torch.Size([16]) 
pool4 torch.Size([1, 16, 5, 5]) 
conv5 torch.Size([1, 120, 1, 1]) torch.Size([120, 16, 5, 5]) torch.Size([120]) 
linear6 torch.Size([1, 84]) torch.Size([84, 120]) torch.Size([84]) 
linear7 torch.Size([1, 10]) torch.Size([10, 84]) torch.Size([10]) 

从输出结果看,

  • 对于大小为32×32的单通道图像,先用6个大小为5×5的卷积核对其进行卷积运算,输出为6个28×28大小的特征图;
  • 6个28×28大小的特征图经过大小为2×2,步长为2的汇聚层后,输出特征图的大小变为14×14;
  • 6个14×14大小的特征图再经过16个大小为5×5的卷积核对其进行卷积运算,得到16个10×10大小的输出特征图;
  • 16个10×10大小的特征图经过大小2×2,步长为2的汇聚层后,输出特征图的大小变为5×5;
  • 16个5×5大小的特征图再经过120个大小为5×5的卷积核对其进行卷积运算,得到120个1×1大小的输出特征图;
  • 此时,将特征图展平成1维,则有120个像素点,经过输入神经元个数为120,输出神经元个数为84的全连接层后,输出的长度变为84。
  • 再经过一个全连接层的计算,最终得到了长度为类别数的输出结果。

2.2 测试网络运算速度

import time
x = torch.tensor(inputs)
# 创建LeNet类的实例,指定模型名称和分类的类别数目
model = LeNet(in_channels=1, num_classes=10)
# 计算LeNet类的运算速度
model_time = 0
for i in range(60):
    strat_time = time.time()
    out = model(x)
    end_time = time.time()
    # 预热10次运算,不计入最终速度统计
    if i < 10:
        continue
    model_time += (end_time - strat_time)
avg_model_time = model_time / 50
print('LeNet speed:', avg_model_time, 's')
LeNet speed: 0.0003125572204589844 s

        我直接调用  了pytroch的API,没有用自定义的算子,由输出结果可以看到模型运算效率很快。

2.3 输出模型参数量

        对于一个卷积层,假设输入通道数为 cin,卷积核大小为 k×k,输出通道数为 cout​,则卷积层的参数量为:( k*k*cin + 1 )*cout【其中 +1是偏置项】

        对于一个全连接层,假设输入神经元数为n,输出神经元数为 m,则全连接层的参数量为:n*m+m+m 是偏置项

  • 第一个卷积层的参数量为:6×1×5×5+6=156
  • 第二个卷积层的参数量为:16×6×5×5+16=2416
  • 第三个卷积层的参数量为:120×16×5×5+120=48120
  • 第一个全连接层的参数量为:120×84+84=10164
  • 第二个全连接层的参数量为:84×10+10=850

所以,LeNet-5总的参数量为61706。

# 计算参数量
from torchsummary import summary
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # PyTorch v0.4.0
Torch_model= LeNet(in_channels=1, num_classes=10).to(device)
summary(Torch_model, (1,32, 32))

输出结果:

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1            [-1, 6, 28, 28]             156
         MaxPool2d-2            [-1, 6, 14, 14]               0
            Conv2d-3           [-1, 16, 10, 10]           2,416
         AvgPool2d-4             [-1, 16, 5, 5]               0
            Conv2d-5            [-1, 120, 1, 1]          48,120
            Linear-6                   [-1, 84]          10,164
            Linear-7                   [-1, 10]             850
================================================================
Total params: 61,706
Trainable params: 61,706
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.06
Params size (MB): 0.24
Estimated Total Size (MB): 0.30
----------------------------------------------------------------
[Train] epoch: 0/6, step: 0/4692, loss: 2.29571

 可以看到,结果与公式推导一致。

2.4 输出模型计算量

计算量(即浮点运算次数)通常计算操作中的乘法和加法

  • 第一个卷积层的计算量为:28×28×5×5×6×1+28×28×6=122304
  • 第二个卷积层的计算量为:10×10×5×5×16×6+10×10×16=241600
  • 第三个卷积层的计算量为:1×1×5×5×120×16+1×1×120=48120
  • 平均汇聚层的计算量为:16×5×5=400
  • 第一个全连接层的计算量为:120×84=10080
  • 第二个全连接层的计算量为:84×10=840

所以,LeNet-5总的计算量为423344。

from thop import profile
# 创建一个假输入并将其移动到相同的设备(GPU)
dummy_input = torch.randn(1, 1, 32, 32).to(device)
# 计算模型的 FLOPS 和参数量
flops, params = profile(model, (dummy_input,))

运行结果:

[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[INFO] Register zero_ops() for <class 'torch.nn.modules.pooling.MaxPool2d'>.
[INFO] Register count_avgpool() for <class 'torch.nn.modules.pooling.AvgPool2d'>.
[INFO] Register count_linear() for <class 'torch.nn.modules.linear.Linear'>.
模型的计算量(FLOPS):416920.0
模型的参数量(params):61706.0

 【不知道为什么和计算的不一样o(╥﹏╥)o】

3. 模型训练

和之前实验差不多,用到了RunnerV3类,需要导入

# 进行训练
import torch.optim as opti
from torch.utils.data import DataLoader

# 学习率大小
lr = 0.1
# 批次大小
batch_size = 64

# 创建三个数据加载器,分别用于训练、开发和测试数据集 shuffle=True表示在每个epoch开始时对数据进行随机打乱,防止过拟合
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = DataLoader(dev_dataset, batch_size=batch_size)
test_loader = DataLoader(test_dataset, batch_size=batch_size)
# 定义LeNet网络
model = LeNet(in_channels=1, num_classes=10)
# 定义优化器 优化器的学习率设置为0.2
optimizer = opti.SGD(model.parameters(), 0.2)
# 定义损失函数 使用交叉熵损失函数
loss_fn = F.cross_entropy
# 定义评价指标-这里使用的是准确率
metric = Accuracy()
# 实例化 RunnerV3 类,并传入模型、优化器、损失函数和评价指标
runner = RunnerV3(model, optimizer, loss_fn, metric)
# 启动训练,设置每15步记录一次日志  每15步评估一次模型性能
log_steps = 15
eval_steps = 15
# 训练模型6个epoch,并保存最好的模型参数
runner.train(train_loader, dev_loader, num_epochs=6, log_steps=log_steps,
             eval_steps=eval_steps, save_path="best_model.pdparams")

runner.load_model('best_model.pdparams')

运行结果:

[Train] epoch: 0/6, step: 780/4692, loss: 0.13901
[Evaluate]  dev score: 0.97590, dev loss: 0.08247
[Evaluate] best accuracy performence has been updated: 0.97450 --> 0.97590
.....
[Train] epoch: 1/6, step: 1560/4692, loss: 0.02407
[Evaluate]  dev score: 0.98080, dev loss: 0.06440
.....
[Evaluate] best accuracy performence has been updated: 0.98470 --> 0.98700
[Train] epoch: 2/6, step: 2340/4692, loss: 0.05586
[Evaluate]  dev score: 0.98170, dev loss: 0.06324
.....
[Train] epoch: 3/6, step: 3120/4692, loss: 0.04725
[Evaluate]  dev score: 0.98240, dev loss: 0.05973
.....
[Train] epoch: 4/6, step: 3900/4692, loss: 0.06728
[Evaluate]  dev score: 0.98120, dev loss: 0.07054
.....
[Evaluate]  dev score: 0.97870, dev loss: 0.07452
[Train] Training done!

损失函数收敛,训练集上精度达到0.98 

4.模型评价

使用测试集对在训练过程中保存的最佳模型进行评价

# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))
[Test] accuracy/loss: 0.9861/0.0413

  可见模型结果还是很不错的。

5.模型预测

使用保存好的模型,对测试集中的某一个数据进行模型预测

# 获取测试集中第一条数据
X, label = next(test_loader())
logits = runner.predict(X)
# 多分类,使用softmax计算预测概率
pred = F.softmax(logits)
# 获取概率最大的类别
pred_class = paddle.argmax(pred[1]).numpy()
label = label[1][0].numpy()
# 输出真实类别与预测类别
print("The true category is {} and the predicted category is {}".format(label[0], pred_class[0]))
# 可视化图片
plt.figure(figsize=(2, 2))
image, label = test_set[0][1], test_set[1][1]
image= np.array(image).astype('float32')
image = np.reshape(image, [28,28])
image = Image.fromarray(image.astype('uint8'), mode='L')
plt.imshow(image)
plt.savefig('cnn-number2.pdf')

The true category is 2 and the predicted category is 2

6 使用完整数据集,不改变输入图像尺寸,使其为1*28*28的图像输入🌻🌻🌻

6.1在第一层卷积时对图像进行padding = 2 填充 



class LetNet(nn.Module):
    def __init__(self):
        super(LetNet, self).__init__()
        # 第一层卷积:输入 (1, 28, 28) -> 输出 (6, 28, 28)
        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=1,  # 输入通道数:1(灰度图)
                out_channels=6,  # 输出通道数:6
                kernel_size=5,  # 卷积核大小:5x5
                stride=1,  # 步长:1
                padding=2,  # 填充:2
            ),  # 输出特征图 (6, 28, 28)
            nn.BatchNorm2d(6),  # 批标准化
            nn.ReLU(),  # ReLU 激活函数
            nn.MaxPool2d(kernel_size=2),  # 池化操作(2x2区域)-> 输出 (6, 14, 14)
        )
        # 第二层卷积:输入 (6, 14, 14) -> 输出 (16, 10, 10)
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5, 1, padding=0),  # 卷积核大小:5x5,步长:1 -> 输出 (16, 10, 10)
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.AvgPool2d(2),  # 池化操作(2x2区域)-> 输出 (16, 5, 5)
        )
    # 第二层卷积:输入 (16, 5, 5) -> 输出 (120, 1, 1)
        self.conv3 = nn.Sequential(
            nn.Conv2d(16, 120, 5, 1, padding=0),  # 卷积核大小:5x5,步长:1 -> 输出 (120, 1, 1)
            nn.BatchNorm2d(120),
            nn.ReLU(),
        )

        # 全连接层
        self.fc = nn.Sequential(
            nn.Linear(120 , 84),  # 120x1x1 展平后输入到全连接层 -> 84个输出
            nn.Linear(84, 10)  # 最后一层输出10个类别
        )

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = torch.flatten(x, 1)  # 展平操作,保持batch_size,展平特征图
        output = self.fc(x)  # 通过全连接层进行分类
        return output

6.2 在第一层卷积时不对图像进行padding = 2 填充 

class LetNet(nn.Module):
    def __init__(self):
        super(LetNet, self).__init__()
        # 第一层卷积:输入 (1, 28, 28) -> 输出 (6, 12, 12)
        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=1,  # 输入通道数:1(灰度图)
                out_channels=6,  # 输出通道数:6
                kernel_size=5,  # 卷积核大小:5x5
                stride=1,  # 步长:1
            ),  # 输出特征图 (6, 24, 24)
            nn.BatchNorm2d(6),  # 批标准化
            nn.ReLU(),  # ReLU 激活函数
            nn.MaxPool2d(kernel_size=2),  # 池化操作(2x2区域)-> 输出 (6, 12, 12)
        )
        # 第二层卷积:输入 (6, 12, 12) -> 输出 (16, 4, 4)
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5, 1),  # 卷积核大小:5x5,步长:1 -> 输出 (16, 8, 8)
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.AvgPool2d(2),  # 池化操作(2x2区域)-> 输出 (16, 4, 4)
        )
        # 全连接层
        self.fc = nn.Sequential(
            nn.Linear(16*4*4,120),
            nn.Linear(120 , 84),  # 120x1x1 展平后输入到全连接层 -> 84个输出
            nn.Linear(84, 10)  # 最后一层输出10个类别
        )

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = torch.flatten(x, 1)  # 展平操作,保持batch_size,展平特征图
        output = self.fc(x)  # 通过全连接层进行分类
        return output

        这里我本来想接着用最后一层的卷积,但是发现卷积核为5*5,而此时图像尺寸为4*4了,应该使用4*4的卷积核,但是这里我把最后一个卷积写到全连接层了(效果是一样的)

 6.3 完整代码

'''
@function: 基于LeNet识别MNIST数据集
@Author: lxy
@date: 2024/11/14
'''
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np

# 定义超参数
input_size = 28  # 图像的总尺寸28*28
num_classes = 10  # 标签的种类数
num_epochs = 3  # 训练的总循环周期
batch_size = 64  # 一个撮(批次)的大小,64张图片

# 训练集
train_dataset = datasets.MNIST(root='./data',
                               train=True,
                               transform=transforms.ToTensor(),
                               download=True)

# 测试集
test_dataset = datasets.MNIST(root='./data',
                              train=False,
                              transform=transforms.ToTensor())

# 构建batch数据
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=False)


class LetNet(nn.Module):
    def __init__(self):
        super(LetNet, self).__init__()
        # 第一层卷积:输入 (1, 28, 28) -> 输出 (6, 12, 12)
        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=1,  # 输入通道数:1(灰度图)
                out_channels=6,  # 输出通道数:6
                kernel_size=5,  # 卷积核大小:5x5
                stride=1,  # 步长:1
            ),  # 输出特征图 (6, 24, 24)
            nn.BatchNorm2d(6),  # 批标准化
            nn.ReLU(),  # ReLU 激活函数
            nn.MaxPool2d(kernel_size=2),  # 池化操作(2x2区域)-> 输出 (6, 12, 12)
        )
        # 第二层卷积:输入 (6, 12, 12) -> 输出 (16, 4, 4)
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5, 1),  # 卷积核大小:5x5,步长:1 -> 输出 (16, 8, 8)
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.AvgPool2d(2),  # 池化操作(2x2区域)-> 输出 (16, 4, 4)
        )
        # 全连接层
        self.fc = nn.Sequential(
            nn.Linear(16*4*4,120),
            nn.Linear(120 , 84),  # 120x1x1 展平后输入到全连接层 -> 84个输出
            nn.Linear(84, 10)  # 最后一层输出10个类别
        )

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = torch.flatten(x, 1)  # 展平操作,保持batch_size,展平特征图
        output = self.fc(x)  # 通过全连接层进行分类
        return output


def accuracy(predictions, labels):
    pred = torch.max(predictions.data, 1)[1]
    rights = pred.eq(labels.data.view_as(pred)).sum()
    return rights, len(labels)


def train_one_epoch(model, criterion, optimizer, train_loader):
    model.train()
    train_rights = []
    total_loss = 0
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        right = accuracy(output, target)
        train_rights.append(right)
        total_loss += loss.item()
    # 计算训练准确率
    train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
    avg_loss = total_loss / len(train_loader)
    train_acc = 100. * train_r[0] / train_r[1]
    return avg_loss, train_acc


def evaluate(model, criterion, test_loader):
    model.eval()
    val_rights = []
    total_loss = 0
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data)
            loss = criterion(output, target)
            total_loss += loss.item()

            right = accuracy(output, target)
            val_rights.append(right)

    # 计算测试准确率
    val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))
    avg_loss = total_loss / len(test_loader)
    val_acc = 100. * val_r[0] / val_r[1]

    return avg_loss, val_acc


# 实例化模型
net = LetNet()
# 损失函数
criterion = nn.CrossEntropyLoss()
# 优化器
optimizer = optim.Adam(net.parameters(), lr=0.001)

# 训练和测试过程
train_losses = []
train_accuracies = []
test_losses = []
test_accuracies = []

for epoch in range(num_epochs):
    print(f"Epoch {epoch + 1}/{num_epochs}")
    # 训练阶段
    train_loss, train_acc = train_one_epoch(net, criterion, optimizer, train_loader)
    train_losses.append(train_loss)
    train_accuracies.append(train_acc)

    # 测试阶段
    test_loss, test_acc = evaluate(net, criterion, test_loader)
    test_losses.append(test_loss)
    test_accuracies.append(test_acc)

    print(f"训练集损失: {train_loss:.4f}, 训练集准确率: {train_acc:.2f}%")
    print(f"测试集损失: {test_loss:.4f}, 测试集准确率: {test_acc:.2f}%")

# 可视化训练过程中的损失和准确率
epochs = np.arange(1, num_epochs + 1)

# 绘制损失图
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(epochs, train_losses, label='loss_train')
plt.plot(epochs, test_losses, label='loss_test')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('loos')

# 绘制准确率图
plt.subplot(1, 2, 2)
plt.plot(epochs, train_accuracies, label='Accuracy_train')
plt.plot(epochs, test_accuracies, label='Accuracy_test')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.title('Accuracy')

plt.tight_layout()
plt.show()

运行结果及调参 

 

Epoch 1/3
训练集损失: 0.1695, 训练集准确率: 95.00%
测试集损失: 0.0622, 测试集准确率: 98.02%
Epoch 2/3
训练集损失: 0.0605, 训练集准确率: 98.13%
测试集损失: 0.0486, 测试集准确率: 98.31%
Epoch 3/3
训练集损失: 0.0475, 训练集准确率: 98.52%
测试集损失: 0.0376, 测试集准确率: 98.82%

这里使用全部数据,我只设置了3个epoch,准确率就已经很不错了~

调整为epoch =  10,时输出结果为:

可以看到有点要过拟合了,训练集准确率一直上升,但是测试集在epoch = 4左右开始下降,但是!!!我发现epoch=10之后又有上升的趋势,于是改为epoch = 14,输出:

Epoch 14/14
训练集损失: 0.0211, 训练集准确率: 99.34%
测试集损失: 0.0283, 测试集准确率: 99.16%

参考链接

详解MNIST数据集下载、解析及显示的Python实现-CSDN博客   对mnist的介绍,真的很详细(尤其是下载的方式)

6.6. 卷积神经网络(LeNet) — 动手学深度学习 2.0.0 documentation  

这本书中将LeNet最后一个卷积视为全连接

Lenet5经典论文解读 - 知乎  文章里详细解释了LeNet-5的七个层次,并给出了详细的网络图

细品经典:LeNet-1, LeNet-4, LeNet-5, Boosted LeNet-4-CSDN博客 博客回顾了LeNet系列神经网络模型,包括LeNet-1、LeNet-4、LeNet-5

NNDL 实验六 卷积神经网络(3)LeNet实现MNIST_mnist.json.gz-CSDN博客  一位学长的博客哈哈哈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/917372.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Springboot3.3.5 启动流程之 tomcat启动流程介绍

在文章 Springboot3.3.5 启动流程&#xff08;源码分析&#xff09; 中讲到 应用上下文&#xff08;applicationContext&#xff09;刷新(refresh)时使用模板方法 onRefresh 创建了 Web Server. 本文将详细介绍 ServletWebServer — Embedded tomcat 的启动流程。 首先&…

学习日志011--模块,迭代器与生成器,正则表达式

一、python模块 在之前学习c语言时&#xff0c;我们学了分文件编辑&#xff0c;那么在python中是否存在类似的编写方式&#xff1f;答案是肯定的。python中同样可以实现分文件编辑。甚至还有更多的好处&#xff1a; ‌提高代码的可维护性‌&#xff1a;当代码被分成多个文件时…

【提高篇】3.3 GPIO(三,工作模式详解 上)

目录 一,工作模式介绍 二,输入浮空 2.1 输入浮空简介 2.2 输入浮空特点 2.3 按键检测示例 2.4 高阻态 三,输入上拉 3.1 输入上拉简介 3.2 输入上拉的特点 3.3 按键检测示例 四,输入下拉 4.1 输入下拉简介 4.2 输入下拉特点 4.3 按键检测示例 一,工作模式介绍…

Excel单元格中自适应填充多图

实例需求&#xff1a;在Excel插入图片时&#xff0c;由于图片尺寸各不相同&#xff0c;如果希望多个图片填充指定单元格&#xff0c;依靠用户手工调整&#xff0c;不仅费时费力&#xff0c;而且很难实现完全填充。如下图中的产品图册&#xff0c;有三个图片&#xff0c;如下图所…

@Autowired 和 @Resource思考(注入redisTemplate时发现一些奇怪的现象)

1. 前置知识 Configuration public class RedisConfig {Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template new RedisTemplate<>();template.setConnectionFactory(facto…

从零开始学习 sg200x 多核开发之 milkv-duo256 编译运行 sophpi

sophpi 是 算能官方针对 sg200x 系列的 SDK 仓库 https://github.com/sophgo/sophpi &#xff0c;支持 cv180x、cv81x、sg200x 系列的芯片。 SG2002 简介 SG2002 是面向边缘智能监控 IP 摄像机、智能猫眼门锁、可视门铃、居家智能等多项产品领域而推出的高性能、低功耗芯片&a…

防火墙----iptables

防火墙是位于内部网和外部网之间的屏障&#xff0c;他按照系统管理员预先定义好的规则来控制数据包的进出 一、iptables简介 防火墙会从以上至下的顺序来读取配置的策略规则&#xff0c;在找到匹配项后就立即结束匹配工作并去执行匹配项中定义的行为&#xff08;即放行或阻止&…

【CSS】absolute定位的默认位置

position: absolute; 属性会使元素脱离正常的文档流&#xff0c;并相对于最近的非 static 定位祖先元素进行定位。如果没有这样的祖先元素&#xff0c;则相对于初始包含块&#xff08;通常是视口&#xff09;进行定位。 但是当top和left没有指定具体值时&#xff0c;元素的在上…

(一)机器学习、深度学习基本概念简介

文章目录 机器学习、深度学习基本概念简介一、什么是机器学习&#xff08;一&#xff09;举例&#xff08;二&#xff09;不同类型的函数&#xff08;三&#xff09;机器是怎么找这个函数的&#xff08;1&#xff09;Function with Unknown Parameters&#xff08;2&#xff09…

CentOS8 启动错误,enter emergency mode ,开机直接进入紧急救援模式,报错 Failed to mount /home 解决方法

先看现场问题截图&#xff1a; 1.根据提示 按 ctrld 输入 root 密码&#xff0c;进入系统。 2. 在紧急模式下运行&#xff1a;journalctl -xe &#xff0c;查看相关日志&#xff0c;找到关键点&#xff1a; Failed to mount /home 3.接着执行修复命令&#xff1a; xfs_repa…

mysql 大数据查询

基于 mysql 8.0 基础介绍 com.mysql.cj.protocol.ResultsetRows该接口表示的是应用层如何访问 db 返回回来的结果集 它有三个实现类 ResultsetRowsStatic 默认实现。连接 db 的 url 没有增加额外的参数、单纯就是 ip port schema 。 @Test public void generalQuery() t…

智能零售柜商品识别

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

【ArcGIS微课1000例】0128:ArcGIS制作规划图卫星影像地图虚化效果

文章目录 一、效果展示二、加载数据三、效果制作四、注意事项一、效果展示 二、加载数据 订阅专栏后,从csdn私信查收完整的实验数据资料,从中选择并解压,加载0128.rar中的卫星影像及矢量范围数据,如下所示: 三、效果制作 1. 创建掩膜图层 新建一个矢量图层,因为主要是…

【Goland】——Gin 框架简介与安装

文章目录 1. Gin 框架概述1.1 什么是 Gin 框架&#xff1f;1.2 为什么选择 Gin&#xff1f;1.3 使用场景 2. 安装 Go 与 Gin 框架2.1 安装 Go 语言环境2.2 初始化 Go 项目2.3 安装 Gin 框架 3. 编写第一个 Gin 应用3.1 Gin 最小化示例代码代码解读3.2 运行程序3.3 测试服务 4. …

【新华妙笔-注册/登录安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

【SpringBoot】公共字段自动填充

问题引入 JavaEE开发的时候&#xff0c;新增字段&#xff0c;修改字段大都会涉及到创建时间(createTime)&#xff0c;更改时间(updateTime)&#xff0c;创建人(craeteUser)&#xff0c;更改人(updateUser)&#xff0c;如果每次都要自己去setter()&#xff0c;会比较麻烦&#…

对称加密与非对称加密:密码学的基石及 RSA 算法详解

对称加密与非对称加密&#xff1a;密码学的基石及 RSA 算法详解 在当今数字化的时代&#xff0c;信息安全至关重要。对称加密和非对称加密作为密码学中的两种基本加密技术&#xff0c;为我们的数据安全提供了强大的保障。本文将深入探讨对称加密和非对称加密的特点、应用场景&…

用go语言后端开发速查

文章目录 一、发送请求和接收请求示例1.1 发送请求1.2 接收请求 二、发送form-data格式的数据示例 用go语言发送请求和接收请求的快速参考 一、发送请求和接收请求示例 1.1 发送请求 package mainimport ("bytes""encoding/json""fmt""ne…

微服务day09

DSL查询 快速入门 GET /items/_search {"query": {"match_all": {}} } 叶子查询 GET /items/_search {"query": {"match_all": {}} }GET /items/_search {"query": {"multi_match": {"query": "脱…

鸿蒙NEXT应用示例:切换图片动画

【引言】 在鸿蒙NEXT应用开发中&#xff0c;实现图片切换动画是一项常见的需求。本文将介绍如何使用鸿蒙应用框架中的组件和动画功能&#xff0c;实现不同类型的图片切换动画效果。 【环境准备】 电脑系统&#xff1a;windows 10 开发工具&#xff1a;DevEco Studio NEXT B…