博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深度学习003:LeNet-5网络研究,并用keras框架复现、pytorch框架复现
阅读量:6692 次
发布时间:2019-06-25

本文共 11711 字,大约阅读时间需要 39 分钟。

1 前言

我在2018年6月份左右,系统性的速学习了一次吴恩达的《深度学习》系列课程。由于当时时间紧,虽然有完成课程中的作业和练习,但没有做详细的课程内容总结,所以导致现在对深度学习的印象零零散散,疑问多多。

在有一定的基础后,详细研究并复现经典的深度学习网络,无疑是最好的自学方法之一。

2 LeNet-5背景知识

(1)发明时间:1998年。

(2)发明人:LeCun 和 Bengio 等人。

(3)功能:识别手写数字0~9。

(4)成功应用:当年美国很多银行把它应用在支票数字识别上。那个年代深度学习就能够落地应用,真的不简单。当然其成功落地的原因,远不止模型设计这么简单,通过浏览46页的论文会发现,里面还做了其他相关工作。

(5)原始论文地址:(英文)

(6)官方模型演示:

 

(7)(强烈推荐)模型的3D可视化演示:http://scs.ryerson.ca/~aharley/vis/conv/

3 LeNet-5详解

LeNet-5的整体网络结构图如下:

3.1 输入层:INPUT

输入图像大小统一要归一化为32×32,灰度图。

 

3.2 卷积层:C1

输入图片:32*32

卷积核大小:5*5

卷积核种类:6

输出featuremap大小:28*28 (32-5+1)=28

神经元数量:28*28*6

可训练参数:(5*5+1) * 6

连接数:(5*5+1)*6*28*28=122304

注意:有122304个连接,但是我们只需要学习156个参数,主要是通过权值共享实现的。

156个参数怎么计算的?

          

上图就是6个卷积核经过模型训练后,最终被定型的“颜色”(就是一个5*5矩阵的可视化),每一个有5*5个格子,而每个格子的值会在网络训练过程中不断的自动优化,单个5*5的卷积核本身就有25个可训练参数。

此外,每一个卷积核在和图像做卷积计算时,还需加一个bias参数,这个值也是一个可训练参数。

所以,总的可训练参数就是:(5*5 + 1)* 6 = 156

(PS:上述6个卷积核,一一对应下图的卷积结果图,可看出第3、4号卷积核成“十字叉”对称,而3,4号卷积结果图的数字边缘高亮部分也成互补形态。)

 

3.3 池化层:S2

输入:28*28

采样区域:2*2

步长:2

输出featureMap大小:14*14(28/2)

神经元数量:14*14*6

连接数:(2*2+1)*6*14*14

S2中每个特征图的大小是C1中特征图大小的1/4

 

3.4 卷积层:C3

输入:S2中6个特征图的组合输入,共有16种输入方式,如下图所示:

作者使用这种数据输入方式的原因:

(1)可以减少计算量。(1998年计算能力有限)

(2)打破了网络的对称性,不完全连接能够保证C3中不同特征图提取不同特征。(作者认为这个是重点原因)

卷积核大小:5*5

卷积核种类:16

输出featureMap大小:10*10 (14-5+1)=10

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

解释:上述计算中,第1个“6”指的是C3层第1-6号特征图,他们的输入选择的是S2中任意3个特征图,后面依次类推。

连接数:10*10*1516=151600

C3与S2中前3个图相连的卷积结构如下图所示:

3.5 池化层:S4

输入:10*10

采样区域:2*2

步长:2

输出featureMap大小:5*5(10/2)

神经元数量:5*5*16=400

连接数:16*(2*2+1)*5*5=2000

S4中每个特征图的大小是C3中特征图大小的1/4

3.6 卷积层:C5(也可以理解成“全连接层”)

输入:S4层的全部16个单元特征map(与s4全相连)

卷积核大小:5*5

卷积核个数:120

输出featureMap大小:1*1(5-5+1)

可训练参数/连接:120*(16*5*5+1)=48120

C5层的网络结构如下:

3.7 全连接层:F6

输入:c5 120维向量

计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数输出。

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

详细说明:F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。ASCII编码图如下:

 

F6层的连接方式如下:

3.8 Output层-全连接层:F7

 Output层也是全连接层,共有10个节点,分别代表数字0到9,且如果节点i的值为0,则网络识别的结果是数字i。采用的是径向基函数(RBF)的网络连接方式。假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:

上式w_ij 的值由i的比特图编码确定,i从0到9,j取值从0到7*12-1。RBF输出的值越接近于0,则越接近于i,即越接近于i的ASCII编码图,表示当前网络输入的识别结果是字符i。

LeNet-5识别数字3的过程如下:

 

3.9 LeNet-5网络3D全貌图

 

3.10 LeNet-5网络总结

(1)没有用 padding 技术,对图片边缘的信息处理不够。

(2)池化为了加快收敛,防止过拟合。池化用的是平均池化 average_pooling, 当下主要是最大值池化 max_pooling。

(3)激活函数用的 tanh, 当下主要用 relu,因为 relu 收敛更快。

(4)filters 每一步都在增大,主要是为了避免丢失信息。用多个不同的 kernel 去学习前一层 feature map 中的特征。

(5)训练的时候用到了数据增强 augmentation。

(6)优化器,应该是用的 SGD。

(7)没有 dropout 技术。

(8)模型较小,学的特征不够多,无法处理较大较复杂的数据集。

(9)原始论文中C3与S2并不是全连接而是部分连接,这样能减少部分计算量。而现代CNN模型中,比如AlexNet,ResNet等,都采取全连接的方式了。

(10)按照原文描述,网络最后一层为高斯连接层。而我们为了简单起见还是用了全连接层。

LeNet其实是一个比较“古老”的模型了,我们不必追求完美的复现,理解其中关键的概念即可。

 

3.11 主要参考资料

(1)https://www.jianshu.com/p/0c6c4c9eee27

(2)https://cuijiahua.com/blog/2018/01/dl_3.html

(3)https://blog.csdn.net/saw009/article/details/80590245

(4)https://blog.csdn.net/luke_sanjayzzzhong/article/details/89641592

3.12 备注

在寻找资料研究LeNet-5网络中,发现在细节方面有不同的版本,特别是在代码复现过程中,我寻找了20篇以上的复现代码,在C3层没有一个是真正按照论文中的方法来组合式输入数据的,全部是将S2层数据简单的作为C3层输入。当然,这种数据输入方式,应该也算是神经网络调参的一种,原始论文中那种方式很难用keras框架实现(如果有谁能用Keras复现论文中那种方式,希望能够留言)。

 

4 Keras复现

深度学习框架:TensorFlow 2.0.0-alpha0

数据集:mnist

由于mnist数据集中图像大小时28*28,所以LeNet网络将会变成如下形式:

4.1 加载数据

import tensorflow as tffrom tensorflow import kerasfrom tensorflow.keras import layersimport numpy as npmnist_data = np.load('mnist.npz')

4.2 制作数据集

import numpy as npmnist_data = np.load('mnist.npz')print(mnist_data.files)x_train = mnist_data['x_train']y_train = mnist_data['y_train']x_test = mnist_data['x_test']y_test = mnist_data['y_test']print(x_train.shape, ' ', y_train.shape)print(x_test.shape, ' ', y_test.shape)

4.3 可视化数据

import matplotlib.pyplot as pltplt.imshow(x_train[0])plt.show()print(y_train[0])

 

4.4 调整数据结构

x_train = x_train.reshape((-1, 28, 28, 1))x_test = x_test.reshape((-1, 28, 28, 1))

4.5 LeNet-5网络模型搭建

from tensorflow.keras import layers,Inputfrom tensorflow.keras.layers import Dense,Conv2D,MaxPool2D,Flatten,AveragePooling2Dfrom tensorflow.keras.models import Model# 这部分返回一个张量inputs = Input(shape=(28, 28, 1), name = 'Input')# C1卷积层x = Conv2D(input_shape=(28,28,1), filters=6, kernel_size=(5,5), strides=(1,1), activation='tanh', name = 'C1')(inputs)# S2池化层x = AveragePooling2D(pool_size=(2,2), strides=(2,2), name = 'S2')(x)# C3卷积层x = Conv2D(filters=16, kernel_size=(5,5), strides=(1,1), activation='tanh', name = 'C3')(x)# S4池化层x = AveragePooling2D(pool_size=(2,2), strides=(2,2), name = 'S4')(x)# C5卷积层x = Conv2D(filters=120, kernel_size=(4,4), strides=(1,1), activation='tanh', name = 'C5')(x)# Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten不影响batch的大小。x = Flatten()(x)# F6全连接层x = Dense(84, activation='tanh', name = 'F6')(x)# 全连接层predictions = Dense(10, activation='softmax', name = 'Output')(x)model = Model(inputs=inputs, outputs=predictions)

4.5 显示模型结构

model.compile(optimizer=keras.optimizers.Adam(),             loss=keras.losses.SparseCategoricalCrossentropy(),             metrics=['accuracy'])model.summary()

4.6 开始训练!

history = model.fit(x_train, y_train, batch_size=64, epochs=5, validation_split=0.1)

4.7 显示训练结果

plt.plot(history.history['accuracy'])plt.plot(history.history['val_accuracy'])plt.legend(['training', 'valivation'], loc='upper left')plt.show()

 

4.8 用测试集评估模型

res = model.evaluate(x_test, y_test)

4.9 输出模型中间层的特征图结果

layer_name = 'S2' #S2池化层intermediate_layer_model = Model(inputs = model.input,                                 outputs = model.get_layer(layer_name).output)intermediate_output = intermediate_layer_model.predict(x_train)
intermediate_output.shape

intermediate_output[1].shape

plt.imshow(intermediate_output[0,:,:,0]) #输出6万张训练图中,第1个mnist图像(上图中那个‘5’)经过S2池化层后的第一个特征图结果。(S2池化层,总共会产生6个特征图)plt.show()

4.10 用模型测试单张图片

import cv2import numpy as npimage = cv2.imread('7.jpg',0)image = cv2.resize(image, (28, 28))image = np.expand_dims(image, axis=0)image = image.reshape((-1, 28, 28, 1))predict = model.predict(image)print(predict)

疑问:

我输入的图片如下图所示:

但是predict的结果并没有显示出他属于哪个数字,模型预测结果数组中,每个元素代表哪一类如何确认?(手动验证除外)

4.11 将模型结构图保存到本地

#输出模型图from tensorflow.keras.utils import plot_modelplot_model(model, to_file='model.png')

 

5 Pytorch复现

pytorch版本:1.1

5.1 定义网络的结构和前向传播过程

#定义网络的结构和前向传播过程import torch.nn as nnfrom collections import OrderedDictclass LeNet5(nn.Module):    def __init__(self):        super(LeNet5, self).__init__()        # (1,6,~~~)        # 1:输入数据的通道数        # 6:卷积核个数        # stride步长默认为1        # nn.Sequential,将若干个功能层组合到一起。        self.convnet = nn.Sequential(OrderedDict([            ('c1', nn.Conv2d(1, 6, kernel_size=(5, 5))),            ('relu1', nn.ReLU()),            ('s2', nn.MaxPool2d(kernel_size=(2, 2), stride=2)),            ('c3', nn.Conv2d(6, 16, kernel_size=(5, 5))),            ('relu3', nn.ReLU()),            ('s4', nn.MaxPool2d(kernel_size=(2, 2), stride=2)),            ('c5', nn.Conv2d(16, 120, kernel_size=(5, 5))),            ('relu5', nn.ReLU())        ]))                # 定义后半部分的全连接层        self.fc = nn.Sequential(OrderedDict([            ('f6', nn.Linear(120, 84)),            ('relu6', nn.ReLU()),            ('f7', nn.Linear(84, 10)),            ('sig7', nn.LogSoftmax(dim=-1))        ]))            #定义网络的前向运算    def forward(self, img):        output = self.convnet(img)        # 在第一个全连接层与卷积层连接的位置        # 需要将特征图拉成一个一维向量        output = output.view(img.size(0),-1)        output = self.fc(output)        return output

pytorch中另一种构造网络的方式:

self.conv1 = nn.Sequential(            #padding=2,将图片由28*28变成32*32            nn.Conv2d(1,6, kernel_size=(5, 5), stride=1, padding=2),             nn.ReLU(),            nn.MaxPool2d(kernel_size=2, stride=2)        )        self.conv2 = nn.Sequential(            nn.Conv2d(6,16,5),            nn.ReLU(),            nn.MaxPool2d(kernel_size=(2, 2), stride=2)        )        self.conv3 = nn.Sequential(            nn.Conv2d(16,120,5),            nn.ReLU()        )        self.fc1 = nn.Sequential(            nn.Linear(120, 84),            nn.ReLU()        )        self.fc2 = nn.Linear(84, 10)    #网络前向传播过程    def forward(self, x):        x = self.conv1(x)        x = self.conv2(x)        x = self.conv3(x)        #全连接层均使用的nn.Linear()线性结构,输入输出维度均为一维,所以需要把数据拉为1维        x = x.view(x.size(0), -1)        x = self.fc1(x)        x = self.fc2(x)        # x = self.fc3(x)        return x

 

5.2 定义数据读取,以及网络的优化过程,并开始训练

# 定义数据读取,以及网络的优化过程。# 实现网络模型的训练。from LeNet import LeNet5import torchimport torch.nn as nnimport torch.optim as optimfrom torchvision.datasets.mnist import MNISTimport torchvision.transforms as transformsfrom torch.utils.data import DataLoader# 读取数据data_train = MNIST('../data/mnist',                  download=True,                  transform=transforms.Compose([                      transforms.Resize((32, 32)),                      transforms.ToTensor()]))data_test = MNIST('../data/mnist',                 train=False,                 download=True,                 transform=transforms.Compose([                     transforms.Resize((32, 32)),                     transforms.ToTensor()]))# num_workers=8 使用多进程加载数据data_train_loader = DataLoader(data_train, batch_size=256, shuffle=True, num_workers=8)data_test_loader = DataLoader(data_test, batch_size=1024, num_workers=8)# 初始化网络net = LeNet5()# 定义损失函数criterion = nn.CrossEntropyLoss()# 定义网络优化方法optimizer = optim.Adam(net.parameters(), lr=2e-3)# 定义训练阶段def train(epoch):    net.train()    loss_list, batch_list = [], []    for i, (image, labels) in enumerate(data_train_loader):        # 初始0梯度        optimizer.zero_grad()        # 网络前向运行        output = net(image)        # 计算网络的损失函数        loss = criterion(output, labels)                #存储每一次的梯度与迭代次数        loss_list.append(loss.detach().cpu().item())        batch_list.append(i+1)                if 1 % 10 == 0:            print('Train - Epoch %d, Batch: %d, Loss: %f' % (epoch, i, loss.detach().cpu().item()))        # 反向传播梯度        loss.backward()        # 优化更新权重        optimizer.step()    # 保存网络模型结构    torch.save(net.state_dict(), 'model//' + str(epoch) + 'LeNet5_model.pkl')    # 定义验证阶段def test():    net.eval()    total_correct = 0    avg_loss = 0.0    # 取消梯度,避免测试阶段out of memory    with torch.no_grad():        for i, (images, labels) in enumerate(data_test_loader):            output = net(images)            avg_loss += criterion(output, labels).sum()            # 计算准确率            pred = output.detach().max(1)[1]            total_correct += pred.eq(labels.view_as(pred)).sum()                avg_loss /= len(data_test)        print('Test Avg. Loss: %f, Accuracy: %f' % (avg_loss.detach().cpu().item(), float(total_correct) / len(data_test)))                def train_and_test(epoch):    train(epoch)    test()def main():    for e in range(1, 16):        train_and_test(e)                   if __name__ == '__main__':    main()

训练结果如下:

  

 

5.2 如有训练好的模型,直接加载模型,并测试模型效果:

# 利用训练好的网络权重来重现网络模型from LeNet import LeNet5import torch from torchvision.datasets.mnist import MNISTimport torchvision.transforms as transforms from torch.utils.data import DataLoader# 读取数据data_test = MNIST('./data/mnist',                 train=False,                 download=True,                 transform=transforms.Compose([                     transforms.Resize((32, 32)),                     transforms.ToTensor()]))# num_workers = 8 使用多进程加载数据data_test_loader = DataLoader(data_test, batch_size=1024, num_workers=8)# 初始化网络net = LeNet5()net.load_state_dict(torch.load(r'model\15LeNet5_model.pkl'))def deploy():    # 验证阶段    # net.eval()    total_correct = 0    for i, (image, labels) in enumerate(data_test_loader):        output = net(image)        # 计算准确率        pred = output.detach().max(1)[1]        total_correct += pred.eq(labels.view_as(pred)).sum()    print('Accuracy: %f' % (float(total_correct) / len(data_test)))    def main():    deploy()    if __name__ == '__main__':    main()

结果如下:

 

 

转载于:https://www.cnblogs.com/Edison25/p/11039673.html

你可能感兴趣的文章
SQL语句教程(04) AND OR
查看>>
Oracle中的JOIN
查看>>
html中iframe控制父页面刷新
查看>>
每天一个linux命令(50):crontab命令
查看>>
linux命令7--cat命令&nl命令
查看>>
.NET底层开发技术
查看>>
RHEL regiester
查看>>
c/c++中的一些基础知识
查看>>
练习:输出整数每一位,计算算数,9出现次数,输出图案,水仙花数
查看>>
操作系统的发展
查看>>
HEVC码流简单分析
查看>>
搭建蚂蚁笔记(服务器)
查看>>
lnmp
查看>>
Oracle教程之Oralce OMF功能详解(三)--使用Oralce OMF管理控制文件
查看>>
C# extern 修饰符的用法
查看>>
Zabbix修正错误两例(只提供解决思路)
查看>>
Redhat6.X 配置HP3PAR7200存储多路径过程
查看>>
Java基础系列19:使用JXL或者POI生成和解析Excel文件
查看>>
使用xshell打开centos中文显示为乱码
查看>>
达内实习——数据库编程、文件读写数据
查看>>