👉🏼目录👈🏼
🍒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博客 一位学长的博客哈哈哈 |