LeNet-5神经网络一共五层,其中卷积层和池化层可以考虑为一个整体。网络结构如下:
输入→卷积→池化→卷积→池化→卷积(全连接)→全连接→输出
层数 | in_channel | out_channel | kernel_size | stridep | adding |
---|---|---|---|---|---|
卷积层c1 | 1 | 6 | 5 | 1 | 2 |
池化层s2 | 6 | 6 | 2 | 2 | 0 |
卷积层c3 | 6 | 16 | 5 | 1 | 0 |
池化层s4 | 6 | 16 | 2 | 2 | 0 |
卷积层c5 | 16 | 120 | |||
全连接层F6 | 120 | 84 | |||
输出层 | 84 | 10 |
使用小批量数据集训练,规模为64。
这里为了绘制一个好看的图,81行和82行参数为20,1自己训练时建议更改
import torchvision as tv # 专门用来处理图像的库
from torchvision import transforms # transforms用来对图片进行变换
import os # 用于加载旧模型使用
import numpy as np
import torch
import torch.nn as nn # 神经网络基本工具箱
import torch.nn.functional as fun
import matplotlib.pyplot as plt # 绘图模块,能绘制 2D 图表
from torchvision.transforms import ToPILImage
import torchvision
# 读取数据
def read_data(file):
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(), # 将图片类型由 PIL Image 转化成tensor类型。转换时会自动归一化
transforms.Normalize((0.5), (0.5))]) # 对图像进行标准化(均值变为0,标准差变为1)
# 从网上下载手写数字识别数据集
train_data = torchvision.datasets.MNIST(root=file, train=True, transform=transform, download=True)
test_data = torchvision.datasets.MNIST(root=file, train=False, transform=transform, download=True)
return train_data, test_data
# 定义卷积神经网络==========================================================
class ConvNet(nn.Module): # 类 ConvNet 继承自 nn.Module
def __init__(self): # 构造方法
# 下式等价于nn.Module.__init__.(self)
super(ConvNet, self).__init__() # 调用父类构造方法
# 使用了三个卷积层,两个全连接层
# 卷积层===========================================================
self.conv1 = nn.Conv2d(1, 6, 5, padding=2) # 输入1通道,输出6通道,卷积核为5*5,两端补2个零
self.conv2 = nn.Conv2d(6, 16, 5) # 输入6通道,输出16通道,卷积核为5*5
self.conv3 = nn.Conv2d(16, 120, 5) # 输入16通道,输出120通道,卷积核为5*5
# 全连接层=========================================================
self.fc1 = nn.Linear(120, 84) # 输入120,输84
self.fc2 = nn.Linear(84, 10) # 输入84,输出10
def forward(self, x):
# 最大池化步长为2
x = fun.max_pool2d(fun.relu(self.conv1(x)), 2) # 1*28*28 -> 6*28*28 -> 6*14*14
x = fun.max_pool2d(fun.relu(self.conv2(x)), 2) # 6*14*14 -> 16*10*10 -> 16*5*5
x = fun.relu(self.conv3(x)) # 16*5*5 -> 120*1*1
x = x.view(x.size()[0], -1) # 展开成一维
x = fun.relu(self.fc1(x)) # 全连接层 120 -> 84
x = self.fc2(x) # 全连接层 84 -> 10
return x
file = 'D:\\python_mnist\mnist\\train' # 数据文件地址
train_start, test_set = read_data(file)
print('训练及图像有:', len(train_start), '张。\n测试集图像有:', len(test_set), '张。')
# 打包数据集 python将多个数据打包处理,能够加快训练速度
batch_size = 64 # 批量大小为
# 将测试集和训练集每 4个 进行打包,并打乱训练集(shuffle)
train_set = torch.utils.data.DataLoader(train_start, batch_size=batch_size, shuffle=True) # 训练集
test_set = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=False) # 测试集
print("已将将数据集%2d 个打包为一组,加快训练速度" % batch_size)
# 设置卷积神经网络和训练参数=================================
print("正在加载卷积神经网络=========================================")
# 如果设备 GPU 能被调用,则转到 GPU 加快运算,否则使用CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = ConvNet().to(device) # 初始化模型
print(device)
print('可使用GPU加速' if (torch.cuda.is_available()) else '无法开启GPU加速')
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数
# 模型加载==========================================
seat = './cnn.pth' # 保存位置(名称)
if os.path.exists(seat): # 如果检测到 seat 文件
print("检测到模型文件,是否加载已训练模型(Y\\N):")
shuru = input()
if shuru == 'Y' or shuru == 'y':
model.load_state_dict(torch.load(seat))
print("已加载已训练模型")
else:
print("未加载已训练模型")
else:
print("未检测到旧模型文件")
# 训练开始==========================================
loop_MAX = 20 # 外循环次数(测试)
loop = 1 # 内循环次数(训练)
print("训练次数为:", loop * loop_MAX)
print("每过 %d 轮执行自动测试以及模型保存" % loop)
print("开始训练===================================================")
Training_accuracy = [] # 记录训练集正确率
Test_accuracy = [] # 记录测试集正确率
process = [] # 记录训练时误差
i = 0 # 函数内使用,提前定义
lentrain = len(train_set)
learning_rate = 0.003 # 基础学习率
print("基础学习率为:", learning_rate)
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # 优化器:随机梯度下降算法
for j in range(loop_MAX): # j 测试轮数
for epoch in range(loop): # 训练 loop 次 epoch 当前轮训练次数
running_loss = 0.0 # 训练误差
# 下面这个作用是每轮打乱一次,没什么大用处,不想要可以删去
train_set = torch.utils.data.DataLoader(train_start, batch_size=batch_size, shuffle=True) # 训练集
# enumerate() 函数:用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标。
for i, (images, labels) in enumerate(train_set, 0):
# 转到GPU或CPU上进行运算
images = images.to(device)
labels = labels.to(device)
outputs = model(images) # 正向传播
loss = criterion(outputs, labels) # 计算batch(四个一打包)误差
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 更新参数
# 打印loss信息
running_loss += loss.item() # batch的误差和
print("第%2d/%2d 轮循环,%6d/%6d 组,误差为:%.4f"
% (epoch + 1, loop, i + 1, lentrain, running_loss / i))
process.append(running_loss)
running_loss = 0.0 # 误差归零
# 模型测试==========================================
print("开始第%2d次测试===================================================" % (j + 1))
# 在训练集上测试====================================
correct = 0 # 预测正确图片数
total = 0 # 总图片数
ii = 0
for images, labels in train_set:
if ii > int(i / 10): # 训练集太多了,挑一点测试
break
ii = ii + 1
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
# 返回得分最高的索引(一组 64 个)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print("第%d轮训练集上的准确率为:%3d %%" % ((j + 1) * loop, 100 * correct / total), end=' ')
Training_accuracy.append(100 * correct / total)
# 在测试集上测试====================================
correct = 0 # 预测正确图片数
total = 0 # 总图片数
for images, labels in test_set:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
# 返回得分最高的索引(一组 64 个)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
total = 100 * correct / total
print("\t测试集上的准确率为:%3d %%" % total)
Test_accuracy.append(total)
# 模型保存==========================================
print("模型已训练完成,是否保存已训练模型(Y\\N):")
shuru = input()
if shuru == 'Y' or shuru == 'y':
torch.save(model.state_dict(), seat)
print("保存模型至%s======================================" % seat)
else:
print("未保存已训练模型")
# 绘制训练过程===========================================================
# 从GPU中拿出来才能用来画图
Training_accuracy = torch.tensor(Training_accuracy, device='cpu')
Test_accuracy = torch.tensor(Test_accuracy, device='cpu')
plt.figure(1) # =======================================
# 误差随时间变化
plt.plot(list(range(len(process))), process, label='loss')
plt.legend(loc='lower right') # 显示上面的label
plt.xlabel('time') # x_label
plt.ylabel('loss') # y_label
plt.title('loss about time') # 标题
plt.figure(2) # =======================================
# 正确率
plt.plot(list(range(len(Training_accuracy))), Training_accuracy, label='Train_set')
plt.plot(list(range(len(Test_accuracy))), Test_accuracy, label='Test_set')
plt.legend(loc='lower right') # 显示上面的label
plt.xlabel('time') # x_label
plt.ylabel('loss') # y_label
plt.title('Training_accuracy and Test_accuracy') # 标题
plt.figure(3) # =======================================
# 输出在测试集上一组(64个)的数据和预测结果===================
dataiter = iter(test_set) # 生成测试集的可迭代对象
images, labels = dataiter.next() # 得到一组数据
npimg = (tv.utils.make_grid(images / 2 + 0.5)).numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
print("实际标签:", labels)
show = ToPILImage() # 把tensor转为image
images = images.to(device)
labels = labels.to(device)
outputs = model(images) # 计算图片在每个类别上的分数
# 返回得分最高的索引
_, predicted = torch.max(outputs.data, 1) # 第一个数是具体值,不需要
# 一组 4 张图,所以找每行的最大值
print("预测结果:", predicted)
plt.show() # 显示========================================================
训练及图像有: 60000 张。
测试集图像有: 10000 张。
已将将数据集64 个打包为一组,加快训练速度
正在加载卷积神经网络=========================================
cuda
可使用GPU加速
未检测到旧模型文件
训练次数为: 20
每过 1 轮执行自动测试以及模型保存
开始训练===================================================
基础学习率为: 0.003
第 1/ 1 轮循环, 938/ 938 组,误差为:2.2994
开始第 1次测试===================================================
第1轮训练集上的准确率为: 19 % 测试集上的准确率为: 20 %
第 1/ 1 轮循环, 938/ 938 组,误差为:2.2794
开始第 2次测试===================================================
第2轮训练集上的准确率为: 47 % 测试集上的准确率为: 47 %
第 1/ 1 轮循环, 938/ 938 组,误差为:2.0767
开始第 3次测试===================================================
第3轮训练集上的准确率为: 65 % 测试集上的准确率为: 66 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.7186
开始第 4次测试===================================================
第4轮训练集上的准确率为: 87 % 测试集上的准确率为: 88 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.3513
开始第 5次测试===================================================
第5轮训练集上的准确率为: 91 % 测试集上的准确率为: 91 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.2641
开始第 6次测试===================================================
第6轮训练集上的准确率为: 92 % 测试集上的准确率为: 93 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.2144
开始第 7次测试===================================================
第7轮训练集上的准确率为: 93 % 测试集上的准确率为: 94 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1807
开始第 8次测试===================================================
第8轮训练集上的准确率为: 94 % 测试集上的准确率为: 95 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1568
开始第 9次测试===================================================
第9轮训练集上的准确率为: 95 % 测试集上的准确率为: 95 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1393
开始第10次测试===================================================
第10轮训练集上的准确率为: 96 % 测试集上的准确率为: 96 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1262
开始第11次测试===================================================
第11轮训练集上的准确率为: 96 % 测试集上的准确率为: 96 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1163
开始第12次测试===================================================
第12轮训练集上的准确率为: 96 % 测试集上的准确率为: 96 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1080
开始第13次测试===================================================
第13轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.1011
开始第14次测试===================================================
第14轮训练集上的准确率为: 96 % 测试集上的准确率为: 96 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.0954
开始第15次测试===================================================
第15轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.0903
开始第16次测试===================================================
第16轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.0862
开始第17次测试===================================================
第17轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.0821
开始第18次测试===================================================
第18轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.0789
开始第19次测试===================================================
第19轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
第 1/ 1 轮循环, 938/ 938 组,误差为:0.0757
开始第20次测试===================================================
第20轮训练集上的准确率为: 97 % 测试集上的准确率为: 97 %
模型已训练完成,是否保存已训练模型(Y\N):
N
未保存已训练模型
实际标签: tensor([7, 2, 1, 0, 4, 1, 4, 9, 5, 9, 0, 6, 9, 0, 1, 5, 9, 7, 3, 4, 9, 6, 6, 5,
4, 0, 7, 4, 0, 1, 3, 1, 3, 4, 7, 2, 7, 1, 2, 1, 1, 7, 4, 2, 3, 5, 1, 2,
4, 4, 6, 3, 5, 5, 6, 0, 4, 1, 9, 5, 7, 8, 9, 3])
预测结果: tensor([7, 2, 1, 0, 4, 1, 4, 9, 5, 9, 0, 6, 9, 0, 1, 5, 9, 7, 3, 4, 9, 6, 6, 5,
4, 0, 7, 4, 0, 1, 3, 1, 3, 4, 7, 2, 7, 1, 2, 1, 1, 7, 4, 2, 3, 5, 1, 2,
4, 4, 6, 3, 5, 5, 6, 0, 4, 1, 9, 5, 7, 8, 9, 3], device='cuda:0')
基本上15轮就能97%了
训练时间不长,这里就不放保存的模型文件了。