【实验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 = modeself.transforms = transformsself.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×5self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5)# 汇聚层:汇聚窗口为2×2,步长为2self.pool2 = nn.MaxPool2d(2, stride=2)# 卷积层:输入通道数为6,输出通道数为16,卷积核大小为5×5,步长为1self.conv3 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1)# 汇聚层:汇聚窗口为2×2,步长为2self.pool4 = nn.AvgPool2d(2, stride=2)# 卷积层:输入通道数为16,输出通道数为120,卷积核大小为5×5self.conv5 = nn.Conv2d(in_channels=16, out_channels=120, kernel_size=5, stride=1)# 全连接层:输入神经元为120,输出神经元为84self.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:continuemodel_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]             156MaxPool2d-2            [-1, 6, 14, 14]               0Conv2d-3           [-1, 16, 10, 10]           2,416AvgPool2d-4             [-1, 16, 5, 5]               0Conv2d-5            [-1, 120, 1, 1]          48,120Linear-6                   [-1, 84]          10,164Linear-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,  # 输出通道数:6kernel_size=5,  # 卷积核大小:5x5stride=1,  # 步长:1padding=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,  # 输出通道数:6kernel_size=5,  # 卷积核大小:5x5stride=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,  # 输出通道数:6kernel_size=5,  # 卷积核大小:5x5stride=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 outputdef 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 = 0for 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_accdef evaluate(model, criterion, test_loader):model.eval()val_rights = []total_loss = 0with 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博客  一位学长的博客哈哈哈

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python酷库之旅-第三方库Pandas(221)

目录 一、用法精讲 1036、pandas.DatetimeIndex.to_pydatetime方法 1036-1、语法 1036-2、参数 1036-3、功能 1036-4、返回值 1036-5、说明 1036-6、用法 1036-6-1、数据准备 1036-6-2、代码示例 1036-6-3、结果输出 1037、pandas.DatetimeIndex.to_series方法 10…

【WPF】Prism学习(三)

Prism Commands 1.复合命令&#xff08;Composite Commanding&#xff09; 这段内容主要介绍了在应用程序中如何使用复合命令&#xff08;Composite Commands&#xff09;来实现多个视图模型&#xff08;ViewModels&#xff09;上的命令。以下是对这段内容的解释&#xff1a; …

【Oracle篇】掌握SQL Tuning Advisor优化工具:从工具使用到SQL优化的全方位指南(第六篇,总共七篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

【一键整合包及教程】AI照片数字人工具EchoMimic技术解析

在数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度改变着我们的生活。EchoMimic&#xff0c;作为蚂蚁集团旗下支付宝推出的开源项目&#xff0c;不仅为数字人技术的发展掀开了新的一页&#xff0c;更为娱乐、教育、虚拟现实、在线会议等多个领域带…

基于Lora通讯加STM32空气质量检测WIFI通讯

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着环境污染问题的日益严重&#xff0c;空气质量的监测与管理已经…

GitLab 降级安装出现 500 错误,如何解决?

本文分享 GitLab 中文版在降级的过程中出现 500 错误的修复方法。 写在前面 强烈不建议大家自行降级&#xff0c;如果真有降级需求&#xff0c;要么自己能力过硬&#xff0c;要么寻求专业服务【https://dl.gitlab.cn/cm33bsfv】&#xff0c;要不出问题很麻烦&#xff01; 问…

2024-11-16 串的存储结构

一、顺序存储。 1.首先定一个静态数组&#xff0c;然后定义i记录串的实际长度。&#xff08;缺点&#xff1a;长度不可变&#xff09; 2.使用malloc申请动态空间&#xff0c;定义指针指向串的地址。&#xff08;需手动ferr&#xff09; 方案一&#xff1a; 数组末尾记录长度 …

PCHMI串口接收实验

插入的唯一一行代码 config1.START((Control)this, System.Reflection.Assembly.GetExecutingAssembly().GetTypes(), null);

代码随想录第46期 单调栈

这道题主要是单调栈的简单应用 class Solution { public:vector<int> dailyTemperatures(vector<int>& T) {vector<int> result(T.size(),0);stack<int> st;st.push(0);for(int i1;i<T.size();i){if(T[i]<T[st.top()]){st.push(i);}else{wh…

Spring 中的 BeanDefinitionParserDelegate 和 NamespaceHandler

一、BeanDefinitionParserDelegate Spring在解析xml文件的时候&#xff0c;在遇到<bean>标签的时候&#xff0c;我们会使用BeanDefinitionParserDelegate对象类解析<bean>标签的内容&#xff0c;包括<bean>标签的多个属性&#xff0c;例如 id name class in…

ODC 如何精确呈现SQL耗时 | OceanBase 开发者工具解析

前言 在程序员或DBA的日常工作中&#xff0c;编写并执行SQL语句如同日常饮食中的一餐一饭&#xff0c;再寻常不过。然而&#xff0c;在使用命令行或黑屏客户端处理SQL时&#xff0c;常会遇到编写难、错误排查缓慢以及查询结果可读性不佳等难题&#xff0c;因此&#xff0c;图形…

Bugku CTF_Web——No one knows regex better than me

Bugku CTF_Web——No one knows regex better than me 进入靶场 一段PHP代码 <?php error_reporting(0); $zero$_REQUEST[zero]; $first$_REQUEST[first]; $second$zero.$first; if(preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i",$second)){$key$…

爬虫——JSON数据处理

第三节&#xff1a;JSON数据处理 在爬虫开发中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;是最常见的数据格式之一&#xff0c;特别是在从API或动态网页中抓取数据时。JSON格式因其结构简单、可读性强、易于与其他系统交互而广泛应用于前端与后端的数…

OpenHarmony-1.启动流程

OpenHarmony启动流程 1.OpenHarmony 标准系统启动引导流程 OpenHarmony标准系统默认支持以下几个镜像&#xff1a; 每个开发板都需要在存储器上划分好分区来存放上述镜像&#xff0c;SOC启动时都由bootloader来加载这些镜像&#xff0c;具体过程包括以下几个大的步骤&#xf…

力扣刷题日记之150.逆波兰表达式求值

今天继续给大家分享一道力扣的做题心得今天这道题目是 150.逆波兰表达式求值 题目如下&#xff0c;题目链接&#xff1a;https://leetcode.cn/problems/evaluate-reverse-polish-notation 1&#xff0c;题目分析 这道题说是一道中等难度的题目&#xff0c;其实如果理解了其中的…

Redis五大基本类型——String字符串命令详解(命令用法详解+思维导图详解)

目录 一、String字符串类型介绍 二、常见命令 1、SET 2、GET 3、MGET 4、MSET 使用MGET 和 使用多次GET的区别 5、DEL 6、SETNX SET、SET NX和SET XX执行流程 7、INCR 8、INCRBY 9、DECR 10、DECYBY 11、INCRBYFLOAT 12、APPEND 13、GETRANGE 14、SETRANGE …

如何知道表之间的关系(为了知识图谱的构建)

今天就简单点&#xff0c;把今天花时间做的一个程序说下。 我们在做常规知识图谱的时候&#xff0c;面临一个问题就是要知道关系是如何建立。如果表的数量比较少&#xff0c;人工来做还是比较容易的。 如果有非常多的表&#xff0c;并且这些表之间的关联关系都不清楚的情况下…

【软件测试】一个简单的自动化Java程序编写

文章目录 自动化自动化概念回归测试常见面试题 自动化测试金字塔 Web 自动化测试驱动 Selenium一个简单的自动化示例安装 selenium 库使⽤selenium编写代码 自动化 自动化概念 自动的代替人的行为完成操作。自动化在生活中处处可见 生活中的自动化可以减少人力的消耗&#x…

网络学习第四篇

引言&#xff1a; 我们在第三篇的时候出现了错误&#xff0c;我们要就行排错&#xff0c;那么我们要知道一下怎么配置静态路由实现ping通&#xff0c;这样子我们才知道下一跳到底是什么&#xff0c;为什么这样子做。 实验目的 理解和掌握静态路由的基本概念和配置方法。 实…

LeetCode题解:17.电话号码的数字组合【Python题解超详细,回溯法、多叉树】,知识拓展:深度优先搜索与广度优先搜索

题目描述 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出…