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

【实验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博客  一位学长的博客哈哈哈

相关文章:

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

&#x1f449;&#x1f3fc;目录&#x1f448;&#x1f3fc; &#x1f352;1. 数据 1.1 准备数据 1.2 数据预处理 &#x1f352;2. 模型构建 2.1 模型测试 2.2 测试网络运算速度 2.3 输出模型参数量 2.4 输出模型计算量 &#x1f352;3. 模型训练 &#x1f352;4.模…...

chatgpt训练需要什么样的gpu硬件

训练像ChatGPT这样的大型语言模型对GPU硬件提出了极高的要求&#xff0c;因为这类模型的训练过程涉及大量的计算和数据处理。以下是训练ChatGPT所需的GPU硬件的关键要素&#xff1a; ### 1. **高性能计算能力** - **Tensor Cores**: 现代深度学习训练依赖于Tensor Cores&#…...

Kubernetes常用命令

Kubernetes常用命令 一、集群管理 kubectl cluster-info&#xff1a;显示集群信息&#xff0c;包括控制平面地址和服务的 URL。 kubectl get nodes&#xff1a;查看集群中的节点列表&#xff0c;包括节点状态、IP 地址等信息。 kubectl describe node <node-name>&…...

Flutter:key的作用原理(LocalKey ,GlobalKey)

第一段代码实现的内容&#xff1a;创建了3个块&#xff0c;随机3个颜色&#xff0c;每次点击按钮时&#xff0c;把第一个块删除 import dart:math; import package:flutter/material.dart; import package:flutter_one/demo.dart;void main() {runApp(const App()); }class App…...

R语言基础入门详解

文章目录 R语言基础入门详解一、引言二、R语言环境搭建1、安装R和RStudio1.1、步骤1.2、获取工作目录 三、R语言基础2、语法基础2.1、赋值操作2.2、注释 3、数据类型与结构3.1、向量3.2、矩阵 4、基本操作4.1、数据读取4.2、数据可视化 四、R语言使用示例4.1、统计分析示例4.2、…...

django启动项目报错解决办法

在启动此项目报错&#xff1a; 类似于&#xff1a; django.core.exceptions.ImproperlyConfigured: Requested setting EMOJI_IMG_TAG, but settings are not c启动方式选择django方式启动&#xff0c;以普通python方式启动会报错 2. 这句话提供了对遇到的错误的一个重要线索…...

详细描述一下Elasticsearch搜索的过程?

大家好&#xff0c;我是锋哥。今天分享关于【详细描述一下Elasticsearch搜索的过程&#xff1f;】面试题。希望对大家有帮助&#xff1b; 详细描述一下Elasticsearch搜索的过程&#xff1f; Elasticsearch 的搜索过程是其核心功能之一&#xff0c;允许用户对存储在 Elasticsea…...

Spring、SpringMVC、SpringBoot、Mybatis小结

Spring Spring是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面&#xff08;AOP&#xff09;的容器&#xff08;框架&#xff09; Spring框架的核心特性包括依赖注入&#xff08;Dependency Injection &#xff0c;DI&#xff09;、面向切面编程&#xff08;Aspe…...

.NET 9 运行时中的新增功能

本文介绍了适用于 .NET 9 的 .NET 运行时中的新功能和性能改进。 文章目录 一、支持修剪的功能开关的属性模型二、UnsafeAccessorAttribute 支持泛型参数三、垃圾回收四、控制流实施技术.NET 安装搜索行为性能改进循环优化感应变量加宽Arm64 上的索引后寻址强度降低循环计数器可…...

Linux下安装mysql8.0版本

先确定我的下载安装的目录,安装文件是下载在 /opt/install 目录下面 (安装地址不同的话注意修改地址) 1.在线下载 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz2.解压 tar -xvf mysql-8.0.20-linux-glibc2.12-x86_64.t…...

kvm-dmesg:从宿主机窥探虚拟机内核dmesg日志

在虚拟化环境中&#xff0c;实时获取虚拟机内核日志对于系统管理员和开发者来说至关重要。传统的 dmesg 工具可以方便地查看本地系统的内核日志&#xff0c;但在KVM&#xff08;基于内核的虚拟机&#xff09;环境下&#xff0c;获取虚拟机内部的内核日志则复杂得多。为了简化这…...

植物明星大乱斗15

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 player.hplayer.cppparticle.hparticle.cpp player.h #pragma once #include <graphics.h> #include "vector2.h" #include "animation.h" #include "playerID.h" #include &…...

go-zero(三) 数据库操作

go-zero 数据库操作 在本篇文章中&#xff0c;我们将实现一个用户注册和登录的服务。我们将为此构建一个简单而高效的 API&#xff0c;包括请求参数和响应参数的定义。 一、Mysql连接 1. 创建数据库和表 在 MySQL 中创建名为 test_zero的数据库&#xff0c;并创建user 表 …...

SQL面试题——间隔连续问题

间隔连续问题 某游戏公司记录的用户每日登录数据如下 +----+----------+ | id| date| +----+----------+ |1001|2021-12-12| |1001|2021-12-13| |1001|2021-12-14| |1001|2021-12-16| |1001|2021-12-19| |1001|2021-12-20| |1002|2021-12-12| |1002|2021-12-16| |1002|…...

vim配置 --> 在创建的普通用户下

在目录/etc/ 下面&#xff0c;有个名为vimrc 的文件&#xff0c;这是系统中公共的vim配置文件对所有用户都有效 我们现在创建一个普通用户 dm 创建好以后&#xff0c;我们退出重新链接 再切换到普通用户下 再输入密码&#xff08;是不显示的&#xff0c;输入完后&#xff0c;…...

(计算机毕设)基于SpringBoot+Vue的房屋租赁系统的设计与实现

博主可接毕设设计&#xff01;&#xff01;&#xff01; 各种毕业设计源码只要是你有的题目我这里都有源码 摘 要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。互…...

【含开题报告+文档+PPT+源码】基于SpringBoot的医院药房管理系统

开题报告 在科技迅速发展的今天&#xff0c;各行各业都在积极寻求与现代技术的融合&#xff0c;以提升自身的运营效率和竞争力。医疗行业作为关乎国计民生的关键领域&#xff0c;其信息化建设的步伐尤为迅速。医院药房作为医疗体系中的核心环节&#xff0c;其管理效率和服务质…...

基于SpringBoot的“数码论坛系统设计与实现”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“数码论坛系统设计与实现”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 数码板…...

Linux-第2集-打包压缩 zip、tar WindowsLinux互传

欢迎来到Linux第2集&#xff0c;这一集我会非常详细的说明如何在Linux上进行打包压缩操作&#xff0c;以及解压解包 还有最最重要的压缩包的网络传输 毕竟打包压缩不是目的&#xff0c;把文件最终传到指定位置才是目的 由于打包压缩分开讲没有意义&#xff0c;并且它们俩本来…...

项目进度计划表:详细的甘特图的制作步骤

甘特图&#xff08;Gantt chart&#xff09;&#xff0c;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;是一种用于管理时间和任务活动的工具。 甘特图由亨利劳伦斯甘特&#xff08;Henry Laurence Gantt&#xff09;发明&#xff0c;是一种通过条状图来…...

Cargo Rust 的包管理器

Cargo->Rust 的包管理器 Cargi简介Cargo 的主要功能1. 创建项目2. 管理依赖3. 构建项目4. 运行项目5. 测试代码6. 检查代码7. 生成文档8. 发布和分享包 Cargo 的核心文件1. Cargo.toml2. Cargo.lock **Cargo 的生态系统** 常用命令总结Hello, Cargo! 示例 Cargi简介 Cargo …...

【Rust 编程语言工具】rustup-init.exe 安装与使用指南

rustup-init.exe 是用于安装和管理 Rust 编程语言工具链的 Windows 可执行文件。Rust 是一种系统级编程语言&#xff0c;旨在提供安全、并发和高性能的功能。rustup-init.exe 是官方提供的安装器&#xff0c;用于将 Rust 安装到 Windows 操作系统中&#xff0c;并配置相关环境。…...

集群聊天服务器(12)nginx负载均衡器

目录 负载均衡器nginx负载均衡器优势 如何解决集群聊天服务器跨服务器通信问题&#xff1f;nginx的TCP负载均衡配置nginx配置 负载均衡器 目前最多只能支持2w台客户机进行同时聊天 所以要引入集群&#xff0c;多服务器。 但是客户连哪一台服务器呢&#xff1f;客户并不知道哪一…...

数据挖掘英语及概念

分类 classify 上涨或跌 回归 regression 描述具体数值 分类模型评估 1.混淆&#xff08;误差&#xff09;矩阵 confusion matrix 2.ROC曲线 receiver operating characteristic curve 接收者操作特征曲线 3.AUC面积 area under curve ROC曲线下与坐标轴围成的面积&#x…...

springboot第82集:消息队列kafka,kafka-map

官网下载链接&#xff1a;https://kafka.[apache].org/downloads 我下载的是[Scala]2.12 - kafka_2.12-3.1.0.tgz kafka只需要解压下载的压缩包就行了&#xff0c;我这里解压的路径是D:\kafka_2.12-3.1.0&#xff0c;kafka的运行需要依赖zookeeper&#xff0c;当前版本已经内置…...

sql server查看当前正在执行的sql

#统计某类sql执行次数&#xff0c;并按总体cpu消耗时间降序排序 with a as ( select er.session_id,db_name(er.database_id) as DBNAME,sy.last_batch AS 最后执行时间, er.cpu_time ,er.total_elapsed_time/1000 as sum_elapsed_time_s, CAST(csql.text AS varchar(8000)) A…...

STM32设计学生宿舍监测控制系统-分享

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 本项目旨在利用STM32单片机为核心&#xff0c;结合传感器技术、无线通信技…...

HAproxy 详解

一、基本概念 1.1 什么是 HAproxy&#xff1f; HAproxy&#xff08;High Availability Proxy&#xff09;是一个开源的高性能负载均衡器和反向代理服务器&#xff0c;它主要用于在网络上分发流量&#xff0c;以提高网站或应用程序的可用性和性能。HAproxy 可以处理大量的并发…...

间接采购管理:主要挑战与实战策略

间接采购支出会悄然消耗掉企业的现金流&#xff0c;即使是管理完善的公司也难以避免。这是因为间接支出不直接关联特定客户、产品或项目&#xff0c;使采购人员难以跟踪。但正确管理间接支出能为企业带来显著收益——前提是要有合适的工具。本文将分享管理间接支出的关键信息与…...

2411rust,正与整128

原文 长期以来,Rust在x86-32和x86-64架构上128位整数的对齐与C语言不一致.最近已解决此问题,但该修复带来了一些值得注意的效果. 作为用户,除非如下,否则不用担心: 1,假设i128/u128对齐,而不是用align_of 2,忽略improper_ctypes*检查,并在FFI中使用这些类. 除x86-32和x86-64…...

wordpress 安装百度统计/seo

2010-11-04 12:18:11| 分类&#xff1a; iOS 应用开发|字号 订阅 neogui2008-09-01 15:57Q: 如何在XCode中使用gcc编译生成的.a库文件&#xff1f;1. 把你的.a文件添加到^projectName下的任意一個組里&#xff08;例如默認的Classes組&#xff09;。2. 找到Target > ^targe…...

做catia数据的网站/百度学术论文查重官网

一、canvas介绍 <canvas> 标签用于绘制图像(通过脚本&#xff0c;通常是 JavaScript)&#xff0c;<canvas> 元素本身并没有绘制能力(它仅仅是图形的容器) - 必须使用脚本来完成实际的绘图任务。 不支持canvas的浏览器:ie8及以下 绘制环境&#xff1a;getcontext…...

wordpress固定/百度seo原理

建议50&#xff1a;正确检测数组类型由于数组和对象的数据同源性&#xff0c;导致在JavaScript编程中经常会出现&#xff1a;在必须使用数组时使用了对象&#xff0c;或者在必须使用对象时使用了数组。选用数组或对象的规则很简单&#xff1a;当属性名是小而连续的整数时&#…...

网站里的注册怎么做/网站首页推广

原因是前台文件太大&#xff0c;nginx不通过。修改Nginx就可以了&#xff0c;在http中加入。 client_max_body_size 8M; client_body_buffer_size 128k; fastcgi_intercept_errors on;...

做网站的调研报告/b站推出的短视频app哪个好

Map排序的实际应用场景我们知道&#xff0c;Map不同于List&#xff0c;它是无序的&#xff0c;但我们实际工作中某些业务场景是需要Map按照一定的顺序排列组合的&#xff0c;有些需要按键排序&#xff0c;有些则需要按值排序。比如说我们现在返回的Map封装了我们所需要的数据&a…...

淘宝官网首页入口手机/首页关键词优化价格

速度慢有很多种情况&#xff0c;比如 DNS解析CDN&#xff08;可能用了国外的cdn&#xff0c;也会导致很慢的&#xff09;服务器IO&#xff08;阿里云的io做的不是很好&#xff0c;频繁的操作io&#xff0c;可能很慢&#xff09;数据库当然跟访问用户的网络也有关系 可以仔细排…...