暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

LeNet-5(论文复现)

wei_shuo 2024-10-11
165

LeNet-5(论文复现)

本文所涉及所有资源均在传知代码平台可获取

Table of Contents

概述

LeNet是最早的卷积神经网络之一。1998年,Yann LeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数字识别任务中取得了巨大成功。LeNet通过连续使用卷积和池化层的组合提取图像特征。
出自论文《Gradient-Based Learning Applied to Document Recognition》

LeNet-5网络架构介绍

在这里插入图片描述

  • 输入层

    输入32×32通道数为1的图片

  • C1层(卷积层)

    使用6个5×5大小的卷积核,padding=0,stride=1,得到6个28×28大小的特征图

    激活函数: ReLU

    **可训练参数:**6×(5×5+1)=1566×(5×5+1)=156

  • S2层(池化层)

    最大池化,池化窗大小2×2,stride=2

    **可训练参数:**6×(1+1)=126×(1+1)=12,其中第一个 1 为池化对应的 2*2 感受野中最大的那个数的权重 w,第二个 1 为偏置 b。

  • C3层(卷积层)

    使用16个5×5大小的卷积核,padding=0,stride=1,得到16个10×10大小的特征图

    激活函数: ReLu

    **可训练参数:**6×(5×5×3+1)+6×(5×5×4+1)+3×(5×5×4+1)+1×(5×5×6+1)=15166×(5×5×3+1)+6×(5×5×4+1)+3×(5×5×4+1)+1×(5×5×6+1)=1516

    16 个卷积核并不是都与 S2 的 6 个通道层进行卷积操作,如下图所示,C3 的前六个特征图(0,1,2,3,4,5)由 S2 的相邻三个特征图作为输入,对应的卷积核尺寸为:5x5x3;接下来的 6 个特征图(6,7,8,9,10,11)由 S2 的相邻四个特征图作为输入对应的卷积核尺寸为:5x5x4;接下来的 3 个特征图(12,13,14)号特征图由 S2 间断的四个特征图作为输入对应的卷积核尺寸为:5x5x4;最后的 15 号特征图由 S2 全部(6 个)特征图作为输入,对应的卷积核尺寸为:5x5x6

  • S4层(池化层)

    最大池化,池化窗大小2×2,stride=2

    **可训练参数:**16×(1+1)=3216×(1+1)=32

  • C5层(卷积层/全连接层)

    由于该层卷积核的大小与输入图像相同,故也可认为是全连接层。

    C5 层是卷积层,使用 120 个 5×5x16 大小的卷积核,padding=0,stride=1进行卷积,得到 120 个 1×1 大小的特征图:5-5+1=1。即相当于 120 个神经元的全连接层。

    值得注意的是,与C3层不同,这里120个卷积核都与S4的16个通道层进行卷积操作。

    激活函数: ReLU

    **可训练参数:**120×(5×5×16+1)=48120120×(5×5×16+1)=48120

  • F6层(全连接层)

    F6 是全连接层,共有 84 个神经元,与 C5 层进行全连接,即每个神经元都与 C5 层的 120 个特征图相连。计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过 sigmoid 函数输出。

    **可训练参数:**84×(120+1)84×(120+1)

  • OUTPUT层(全连接层)

    最后的 Output 层也是全连接层,是 Gaussian Connections,采用了 RBF 函数(即径向欧式距离函数),计算输入向量和参数向量之间的欧式距离(目前已经被Softmax 取代)。

    **可训练参数:**84×1084×10

使用 LeNet-5 网络结构创建 MNIST 手写数字识别分类器

MNIST是一个非常有名的手写体数字识别数据集,训练样本:共60000个,其中55000个用于训练,另外5000个用于验证;测试样本:共10000个。MNIST数据集每张图片是单通道的,大小为28x28

在这里插入图片描述

下载并加载数据,并对数据进行预处理

# 下载MNIST数据集 train_set = datasets.MNIST(root = "./data", train = True, download = True, transform = pipline_train) test_set = datasets.MNIST(root = "./data", train = False, download = True, transform = pipline_test) # 加载数据集 train_data = torch.utils.data.DataLoader(train_set, batch_size = opt.batch_size, shuffle = True) test_data = torch.utils.data.DataLoader(test_set, batch_size = opt.batch_size, shuffle = False) train_data_size = len(train_data) test_data_size = len(test_data) print("训练数据集长度:{}\n测试数据集长度:{}".format(train_data_size, test_data_size))

若本地无MNIST数据集,会在当前目录下新建一个data文件夹存放数据

在这里插入图片描述

MNIST数据集中的图片数据以ubyte格式存储,ubyte是一种无符号字节类型,取值范围在0~255之间。MNIST数据集的图像数据文件为"train-images-idx3-ubyte.gz"和"t10k-images-idx3-ubyte.gz",其中前者存储了训练数据,后者存储了测试数据。

由于 MNIST 数据集图片尺寸是 28x28 单通道的,而 LeNet-5 网络输入 Input 图片尺寸是 32x32,使用 transforms.Resize 将输入图片尺寸调整为 32x32

pipline_train = transforms.Compose([ # 随机旋转图片 transforms.RandomHorizontalFlip(), # 将图片尺寸resize到32x32 transforms.Resize((32, 32)), # 将图片转化为Tensor格式 transforms.ToTensor(), # 正则化(当模型出现过拟合的情况时,用来降低模型的复杂度) transforms.Normalize((0.1307,), (0.3081,)) ]) pipline_test = transforms.Compose([ # 将图片尺寸resize到32x32 transforms.Resize((32, 32)), transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])

搭建 LeNet-5 神经网络结构,并定义前向传播的过程

# 搭建 LeNet-5 神经网络结构,并定义前向传播的过程 class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) # 输入通道的数量;输出通道的数量(也就是卷积核的数量);卷积核的大小 self.relu = nn.ReLU() self.maxpool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.maxpool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.conv1(x) x = self.relu(x) x = self.maxpool1(x) x = self.conv2(x) x = self.relu(x) x = self.maxpool2(x) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) output = F.log_softmax(x, dim = 1) return output

训练过程

def train_runner(model, device, trainloader, optimizer): # 训练模型, 启用 BatchNormalization 和 Dropout, 将BatchNormalization和Dropout置为True model.train() total = 0 correct = 0.0 # enumerate迭代已加载的数据集,同时获取数据和数据下标 for i, data in enumerate(trainloader, 0): inputs, labels = data # 把模型部署到device上 inputs, labels = inputs.to(device), labels.to(device) # 保存训练结果 outputs = model(inputs) # 计算损失和 # 多分类情况通常使用cross_entropy(交叉熵损失函数), 而对于二分类问题, 通常使用sigmod loss = F.cross_entropy(outputs, labels) # 初始化梯度 optimizer.zero_grad() # 反向传播 loss.backward() # 更新参数 optimizer.step() # 获取最大概率的预测结果 # dim=1表示返回每一行的最大值对应的列下标 predict = outputs.argmax(dim = 1) total += labels.size(0) correct += (predict == labels).sum().item() if i % 1000 == 0: # loss.item()表示当前loss的数值 print("Train Loss: {:.4f}, Accuracy: {:.2f}%".format(loss.item(), 100 * (correct / total))) return loss.item(), 100 * (correct / total)

测试过程

def val_runner(model, device, testloader): # 模型验证, 必须要写, 否则只要有输入数据, 即使不训练, 它也会改变权值 # 因为调用eval()将不启用 BatchNormalization 和 Dropout, BatchNormalization和Dropout置为False model.eval() # 统计模型正确率, 设置初始值 correct = 0.0 test_loss = 0.0 total = 0 best_acc = 0.0 # torch.no_grad将不会计算梯度, 也不会进行反向传播 with torch.no_grad(): for data, label in testloader: data, label = data.to(device), label.to(device) output = model(data) test_loss += F.cross_entropy(output, label).item() predict = output.argmax(dim = 1) # 计算正确数量 total += label.size(0) correct += (predict == label).sum().item() # 计算损失值 val_acc = correct / total print("Test loss: {:.4f}, Accuracy: {:.2f}%".format(test_loss / total, 100 * val_acc)) # 保存最佳模型 if val_acc > best_acc: best_acc = val_acc torch.save(model, './model-mnist_best.pth') # 保存模型 return test_loss / total, 100 * val_acc

使用方式

可直接在IDLE中运行代码,其中train.py文件用于训练网络,model.py文件用于定义网络,test.py文件用来对训练完的模型做一个测试推理。
也可直接调用命令行实现,如

python train.py --epochs 100 --lr 0.001 --batch_size 64

若不指定相关参数,train.py默认为训练10轮,学习率0.001,batch_size为64

说明

本项目的文件夹架构如下

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码中还使用了tensorboard可视化工具,以下是tensorboard可视化结果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

最终在测试样本上,average_loss降到了 0.00129,accuracy 达到了 97.28%。可以说 LeNet-5 的效果非常好!

使用test.py进行测试推理时,由于MNIST数据集中的图片数据以ubyte格式存储,需要转成图片的格式,具体转换脚本参照mnist2jpg.py

# 获取图像数据和标签 img, label = mnist_train[i] # 转换图像数据为numpy数组 img_np = np.squeeze(img.numpy()) # 展示图像 plt.imshow(img_np, cmap = 'gray') plt.axis('off') # 关闭坐标轴显示 plt.savefig('{}/mnist_image_{}.jpg'.format(save_dir, label), bbox_inches = 'tight', pad_inches = 0) plt.close()

测试图片

在这里插入图片描述

文章代码资源点击附件获取

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论