【实验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实现手写体数字识别
👉🏼目录👈🏼 🍒1. 数据 1.1 准备数据 1.2 数据预处理 🍒2. 模型构建 2.1 模型测试 2.2 测试网络运算速度 2.3 输出模型参数量 2.4 输出模型计算量 🍒3. 模型训练 🍒4.模…...
chatgpt训练需要什么样的gpu硬件
训练像ChatGPT这样的大型语言模型对GPU硬件提出了极高的要求,因为这类模型的训练过程涉及大量的计算和数据处理。以下是训练ChatGPT所需的GPU硬件的关键要素: ### 1. **高性能计算能力** - **Tensor Cores**: 现代深度学习训练依赖于Tensor Cores&#…...
Kubernetes常用命令
Kubernetes常用命令 一、集群管理 kubectl cluster-info:显示集群信息,包括控制平面地址和服务的 URL。 kubectl get nodes:查看集群中的节点列表,包括节点状态、IP 地址等信息。 kubectl describe node <node-name>&…...
Flutter:key的作用原理(LocalKey ,GlobalKey)
第一段代码实现的内容:创建了3个块,随机3个颜色,每次点击按钮时,把第一个块删除 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启动项目报错解决办法
在启动此项目报错: 类似于: django.core.exceptions.ImproperlyConfigured: Requested setting EMOJI_IMG_TAG, but settings are not c启动方式选择django方式启动,以普通python方式启动会报错 2. 这句话提供了对遇到的错误的一个重要线索…...
详细描述一下Elasticsearch搜索的过程?
大家好,我是锋哥。今天分享关于【详细描述一下Elasticsearch搜索的过程?】面试题。希望对大家有帮助; 详细描述一下Elasticsearch搜索的过程? Elasticsearch 的搜索过程是其核心功能之一,允许用户对存储在 Elasticsea…...
Spring、SpringMVC、SpringBoot、Mybatis小结
Spring Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架) Spring框架的核心特性包括依赖注入(Dependency Injection ,DI)、面向切面编程(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日志
在虚拟化环境中,实时获取虚拟机内核日志对于系统管理员和开发者来说至关重要。传统的 dmesg 工具可以方便地查看本地系统的内核日志,但在KVM(基于内核的虚拟机)环境下,获取虚拟机内部的内核日志则复杂得多。为了简化这…...
植物明星大乱斗15
能帮到你的话,就给个赞吧 😘 文章目录 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 数据库操作 在本篇文章中,我们将实现一个用户注册和登录的服务。我们将为此构建一个简单而高效的 API,包括请求参数和响应参数的定义。 一、Mysql连接 1. 创建数据库和表 在 MySQL 中创建名为 test_zero的数据库,并创建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/ 下面,有个名为vimrc 的文件,这是系统中公共的vim配置文件对所有用户都有效 我们现在创建一个普通用户 dm 创建好以后,我们退出重新链接 再切换到普通用户下 再输入密码(是不显示的,输入完后,…...
(计算机毕设)基于SpringBoot+Vue的房屋租赁系统的设计与实现
博主可接毕设设计!!! 各种毕业设计源码只要是你有的题目我这里都有源码 摘 要 社会的发展和科学技术的进步,互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。互…...
【含开题报告+文档+PPT+源码】基于SpringBoot的医院药房管理系统
开题报告 在科技迅速发展的今天,各行各业都在积极寻求与现代技术的融合,以提升自身的运营效率和竞争力。医疗行业作为关乎国计民生的关键领域,其信息化建设的步伐尤为迅速。医院药房作为医疗体系中的核心环节,其管理效率和服务质…...
基于SpringBoot的“数码论坛系统设计与实现”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“数码论坛系统设计与实现”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 数码板…...
Linux-第2集-打包压缩 zip、tar WindowsLinux互传
欢迎来到Linux第2集,这一集我会非常详细的说明如何在Linux上进行打包压缩操作,以及解压解包 还有最最重要的压缩包的网络传输 毕竟打包压缩不是目的,把文件最终传到指定位置才是目的 由于打包压缩分开讲没有意义,并且它们俩本来…...
项目进度计划表:详细的甘特图的制作步骤
甘特图(Gantt chart),又称为横道图、条状图(Bar chart),是一种用于管理时间和任务活动的工具。 甘特图由亨利劳伦斯甘特(Henry Laurence Gantt)发明,是一种通过条状图来…...
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 是一种系统级编程语言,旨在提供安全、并发和高性能的功能。rustup-init.exe 是官方提供的安装器,用于将 Rust 安装到 Windows 操作系统中,并配置相关环境。…...
集群聊天服务器(12)nginx负载均衡器
目录 负载均衡器nginx负载均衡器优势 如何解决集群聊天服务器跨服务器通信问题?nginx的TCP负载均衡配置nginx配置 负载均衡器 目前最多只能支持2w台客户机进行同时聊天 所以要引入集群,多服务器。 但是客户连哪一台服务器呢?客户并不知道哪一…...
数据挖掘英语及概念
分类 classify 上涨或跌 回归 regression 描述具体数值 分类模型评估 1.混淆(误差)矩阵 confusion matrix 2.ROC曲线 receiver operating characteristic curve 接收者操作特征曲线 3.AUC面积 area under curve ROC曲线下与坐标轴围成的面积&#x…...
springboot第82集:消息队列kafka,kafka-map
官网下载链接:https://kafka.[apache].org/downloads 我下载的是[Scala]2.12 - kafka_2.12-3.1.0.tgz kafka只需要解压下载的压缩包就行了,我这里解压的路径是D:\kafka_2.12-3.1.0,kafka的运行需要依赖zookeeper,当前版本已经内置…...
sql server查看当前正在执行的sql
#统计某类sql执行次数,并按总体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设计学生宿舍监测控制系统-分享
目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 电路图采用Altium Designer进行设计: 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 本项目旨在利用STM32单片机为核心,结合传感器技术、无线通信技…...
HAproxy 详解
一、基本概念 1.1 什么是 HAproxy? HAproxy(High Availability Proxy)是一个开源的高性能负载均衡器和反向代理服务器,它主要用于在网络上分发流量,以提高网站或应用程序的可用性和性能。HAproxy 可以处理大量的并发…...
间接采购管理:主要挑战与实战策略
间接采购支出会悄然消耗掉企业的现金流,即使是管理完善的公司也难以避免。这是因为间接支出不直接关联特定客户、产品或项目,使采购人员难以跟踪。但正确管理间接支出能为企业带来显著收益——前提是要有合适的工具。本文将分享管理间接支出的关键信息与…...
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| 分类: iOS 应用开发|字号 订阅 neogui2008-09-01 15:57Q: 如何在XCode中使用gcc编译生成的.a库文件?1. 把你的.a文件添加到^projectName下的任意一個組里(例如默認的Classes組)。2. 找到Target > ^targe…...
做catia数据的网站/百度学术论文查重官网
一、canvas介绍 <canvas> 标签用于绘制图像(通过脚本,通常是 JavaScript),<canvas> 元素本身并没有绘制能力(它仅仅是图形的容器) - 必须使用脚本来完成实际的绘图任务。 不支持canvas的浏览器:ie8及以下 绘制环境:getcontext…...
wordpress固定/百度seo原理
建议50:正确检测数组类型由于数组和对象的数据同源性,导致在JavaScript编程中经常会出现:在必须使用数组时使用了对象,或者在必须使用对象时使用了数组。选用数组或对象的规则很简单:当属性名是小而连续的整数时&#…...
网站里的注册怎么做/网站首页推广
原因是前台文件太大,nginx不通过。修改Nginx就可以了,在http中加入。 client_max_body_size 8M; client_body_buffer_size 128k; fastcgi_intercept_errors on;...
做网站的调研报告/b站推出的短视频app哪个好
Map排序的实际应用场景我们知道,Map不同于List,它是无序的,但我们实际工作中某些业务场景是需要Map按照一定的顺序排列组合的,有些需要按键排序,有些则需要按值排序。比如说我们现在返回的Map封装了我们所需要的数据&a…...
淘宝官网首页入口手机/首页关键词优化价格
速度慢有很多种情况,比如 DNS解析CDN(可能用了国外的cdn,也会导致很慢的)服务器IO(阿里云的io做的不是很好,频繁的操作io,可能很慢)数据库当然跟访问用户的网络也有关系 可以仔细排…...