Lesson 16.6&Lesson 16.6 复现经典架构:LeNet5& 复现经典架构 (2):AlexNet

4 复现经典网络:LeNet5与AlexNet

4.1 现代CNN的奠基者:LeNet5

使用卷积层和池化层能够创造的最简单的网络是什么样呢?或许就是下面这样的架构:Lesson 16.6&Lesson 16.6 复现经典架构:LeNet5& 复现经典架构 (2):AlexNet_第1张图片
首先,图像从左侧输入,从右侧输出,数据传输方向与DNN一致。整个网络由2个卷积层、2个平均池化层和2个全连接层组成,虽然没有标注出来,但每个卷积层和全连接层后都使用激活函数tanh或sigmoid。

这个架构就是著名的LeNet5架构,它在1998年被LeCun等人在论文《Gradient-Based Learning Applied to Document Recognition》中正式提出,它被认为是现代卷积神经网络的奠基者。在LeNet5被提出后,几乎所有的卷积网络都会连用卷积层、池化层与全连接层(也就是线性层)。现在,这已经成为一种非常经典的架构:卷积层作为输入层,紧跟激活函数,池化层紧跟在一个或数个卷积+激活的结构之后。在卷积池化交替进行数次之后,转向线性层+激活函数,并使用线性层结尾,输出预测结果。由于输入数据结构的设置,以上架构图中的网络可能与论文中有一些细节上的区别(论文见下载资料),在其他教材中,你或许会见到添加了更多卷积层、或添加了更多池化层的相似架构,这些都是根据LeNet的核心思想“卷积+池化+线性”来搭建的LeNet5的变体。

在上面的架构中,每层的结构和参数都标识得非常清楚,在PyTorch中实现其架构的代码如下:

import torch
from torch import nn
from torch.nn import functional as F
data = torch.ones(size=(10,1,32,32))
class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,6,5)
        self.pool1 = nn.AvgPool2d(kernel_size=2,stride=2)
        self.conv2 = nn.Conv2d(6,16,5)
        self.pool2 = nn.AvgPool2d(kernel_size=2,stride=2)
        self.fc1 = nn.Linear(16*5*5,120) #这里的上层输入是图像中的全部像素
        self.fc2 = nn.Linear(120,84)
    
    def forward(self,x):
        x = F.tanh(self.conv1(x))
        x = self.pool1(x)
        x = F.tanh(self.conv2(x))
        x = self.pool2(x)
        x = x.view(-1,16*5*5) #需要将数据的特征部分“拉平”才能够进入FC层
        x = F.tanh(self.fc1(x))
        output = F.softmax(self.fc2(x),dim=1)
net = Model()
net(data) #卷积网络的一大特点是:改变输入数据的尺寸,整个网络就会开始花式报错
#你可以试试将数据32*32的结构修改至28*28
#安装torchinfo库,查看网络结构
!pip install torchinfo
from torchinfo import summary
net = Model()
summary(net, input_size=(10, 1, 32, 32))
#==========================================================================================
#Layer (type:depth-idx)                   Output Shape              Param #
#==========================================================================================
#model                                    --                        --
#├─Conv2d: 1-1                            [10, 6, 28, 28]           156
#├─AvgPool2d: 1-2                         [10, 6, 14, 14]           --
#├─Conv2d: 1-3                            [10, 16, 10, 10]          2,416
#├─AvgPool2d: 1-4                         [10, 16, 5, 5]            --
#├─Linear: 1-5                            [10, 120]                 48,120
#├─Linear: 1-6                            [10, 84]                  10,164
#==========================================================================================
#Total params: 60,856
#Trainable params: 60,856
#Non-trainable params: 0
#Total mult-adds (M): 4.22
#==========================================================================================
#Input size (MB): 0.04
#Forward/backward pass size (MB): 0.52
#Params size (MB): 0.24
#Estimated Total Size (MB): 0.81
#==========================================================================================

这是我们在之前的课程中学到的写法,这种写法适合层数较少、层次简单的神经网络,但这个网络和我们之前写的只有线性层的网络比起来还是略有些复杂了。在这段代码中你可以清晰地看见卷积层、池化层以及全连接层是如何链接在一起共同作用的。我们将“层”放在init中定义,而将“函数”放在forward中定义,令init中的结构与forward中的结构一一对应,并使用forward函数执行向前传播流程。

从今天的眼光来看,LeNet5无疑是很简单并且有些弱小的卷积网络,然而,当加入学习率衰减等优化方法并训练恰当时,单一的LeNet5模型可以在Fashion-MNIST数据集上获得超过91%的训练准确率(可参考kaggle kernel:https://www.kaggle.com/jaeboklee/pytorch-fashion-mnist-lenet5-ensemble)。

这个效果已经比只有线性层的网络提升了约5%的成绩。虽然简单,LeNet5却切实展示了卷积神经网络的实力。

4.2 从浅层到深度:AlexNet

在二十一世纪的前十年,LeNet5作为现代卷积网络的奠基者并没有引起太大的水花,大多数人并不相信机器提取的特征能够比人亲自提取的特征更强大,事实上,LeNet5也确实无法在复杂任务上战胜传统计算机视觉方法。卷积网络在被提出的几十年内一直在蛰伏,直到2012年,AlexNet横空出世,真正第一次证明了,当数据量达标、训练得当时,卷积神经网络提取的特征效果远远胜过人类提取的特征。

Lesson 16.6&Lesson 16.6 复现经典架构:LeNet5& 复现经典架构 (2):AlexNet_第2张图片
AlexNet诞生于有“视觉界奥林匹克”之称的大规模视觉识别挑战比赛ILSVRC(ImageNet Large Scale Visual Recognition Challenge)。ILSVRC使用ImageNet数据集,共有一千四百多万幅图像,共2万多个类别,图像的尺寸有224×224,227×227,256×256,299×299等不同类型。在ILSVRC中,参赛队伍需要使用大约包含一百万张图片的训练集来训练模型,并识别出测试集中的一千个类别,再按“错误率”从低到高进行排名(具体错误率是如何计算的,将在我们使用ImageNet进行训练的时候来说明)。在AlexNet出现之前,最好成绩一直由手工提取特征+支持向量机的算法获得,最低错误率为25.8%。2012年,AlexNet进入ILSVRC竞赛,一下将错误率降低到了15.3%。在过去的ImageNet竞赛中,哪怕有一个百分点的提升都是非常不错的成绩,而深度学习首次入场就取得了10个百分点的提升,这给整个图像领域带来了巨大的震撼,从那之后图像领域的前沿研究就全面向深度学习倾斜了。AlexNet无疑是真正让“深度视觉”出圈的架构,其架构如下所示:

Lesson 16.6&Lesson 16.6 复现经典架构:LeNet5& 复现经典架构 (2):AlexNet_第3张图片Lesson 16.6&Lesson 16.6 复现经典架构:LeNet5& 复现经典架构 (2):AlexNet_第4张图片
AlexNet总共有11层,其中有5个卷积层、3个池化层、2个全连接隐藏层,1个全连接输出层,实际上,在全连接层的前后AlexNet使用了Dropout层,但在大部分架构上都不会把DP表现出来。

在有的文献上,你可能看到人们称AlexNet为8层(不计算池化层)。实际上,在学术界,我们对于神经网络的“层”的计数方法没有明确的定义,有的文献会将所有卷积中的元素都算做“层”,例如、池化、BN、Dropout等都是层,而有的文献认为,只有带有训练参数的才能够被称为“层”,其他对数据进行处理的、不带训练参数的功能(如池化、激活函数、Dropout等)都属于参数层的附属。大部分文献中的架构图只会写出卷积、池化以及全连接层是如何排列,即便文献中提到使用了Dropout或BN等计算方式,可能也不一定会明确这些计算在整体网络架构的哪一部分。在PyTorch中进行计算时,就连激活函数也可以是单独的层。因此,人们对于一个神经网络究竟有几层是没有明确定义的。在学习架构时,不用去纠结整体的层数,而要从层与层之间的组合入手。例如,AlexNet的架构若用文字来表现,则可以打包成4个组合:

输入→(卷积+池化)→(卷积+池化)→(卷积x3+池化)→(线性x3)→输出

相对的,LeNet5的架构可以打包成3个组合:

输入→(卷积+池化)→(卷积+池化)→(线性x2)→输出

虽然省略了不少细节,但是这样的组合比架构图更容易记忆,可以快速帮助你判断眼前的代码究竟依赖于怎样的经典架构来建立。

和只有6层(包括池化层)的LeNet5比起来,AlexNet主要做出了如下改变:

1、相比之下,卷积核更小、网络更深、通道数更多,这代表人们已经认识到了图像数据天生适合于多次提取特征,“深度”才是卷积网络的未来。LeNet5是基于MNIST数据集创造,MNIST数据集中的图片尺寸大约只有30*30的大小,LeNet5采用了5x5的卷积核,图像尺寸/核尺寸大约在6:1。而基于ImageNet数据集训练的AlexNet最大的卷积核只有11x11,且在第二个卷积层就改用5x5,剩下的层中都使用3x3的卷积核,图像尺寸/核尺寸至少也超过20:1。小卷积核让网络更深,但也让特征图的尺寸变得很小,为了让信息尽可能地被捕获,AlexNet也使用了更多的通道。小卷积核、多通道、更深的网络,这些都成为了卷积神经网络后续发展的指导方向。
2、使用了ReLU激活函数,摆脱Sigmoid与Tanh的各种问题。
3、使用了Dropout层来控制模型复杂度,控制过拟合。
4、引入了大量传统或新兴的图像增强技术来扩大数据集,进一步缓解过拟合。
5、使用GPU对网络进行训练,使得“适当的训练“(proper training)成为可能。

除此之外,在原论文中,作者Alex Krizhevsky还提出了其他创新,例如,他们使用了overlap pooling的技术——一般的池化层在进行扫描的时候,都是步幅 >= 核尺寸,而在AlexNet中,池化层中的步幅是小于核尺寸的,这就让池化过程中的扫描区域出现重叠(overlap),根据论文所示,这个技术可以缓解过拟合。从以上这些点,很容易看出为什么AlexNet对于卷积神经网络而言如此地重要,它几乎从算法、算力、数据、架构技巧等各个方面影响了现代卷积神经网络地发展。AlexNet之后,ImageNet竞赛上就很少看到传统视觉算法的身影了。当然,竞赛环境与工业环境有极大的区别,在实际落地的过程中,深度学习还受到各种限制,因此传统方法依然很关键。

现在,让我们在PyTorch中来复现AlexNet的架构:

import torch
from torch import nn
from torch.nn import functional as F

data = torch.ones(size=(10,3,227,227)) #假设图像的尺寸为227x227

class Model(nn.Module):
    def __init__(self):
        super().__init__()       
        #大卷积核、较大的步长、较多的通道
        self.conv1 = nn.Conv2d(3,96,kernel_size=11, stride=4) 
        self.pool1 = nn.MaxPool2d(kernel_size=3,stride=2)
        
        #卷积核、步长恢复正常大小,进一步扩大通道
        self.conv2 = nn.Conv2d(96,256,kernel_size=5, padding=2) 
        self.pool2 = nn.MaxPool2d(kernel_size=3,stride=2)
        
        #连续的卷积层,疯狂提取特征
        self.conv3 = nn.Conv2d(256,384,kernel_size=3,padding=1) 
        self.conv4 = nn.Conv2d(384,384,kernel_size=3,padding=1)
        self.conv5 = nn.Conv2d(384,256,kernel_size=3,padding=1)
        self.pool3 = nn.MaxPool2d(kernel_size=3,stride=2)
        
        #全连接层
        self.fc1 = nn.Linear(256*6*6,4096) #这里的上层输入是图像中的全部像素
        self.fc2 = nn.Linear(4096,4096)
        self.fc3 = nn.Linear(4096,1000) #输出ImageNet的一千个类别
    
    def forward(self,x):
        
        x = F.relu(self.conv1(x))
        x = self.pool1(x)
        
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        
        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))
        x = F.relu(self.conv5(x))
        x = self.pool3(x)
        
        x = x.view(-1,256*6*6) #需要将数据的特征部分“拉平”才能够进入FC层
        
        x = F.relu(F.dropout(self.fc1(x),0.5)) #dropout:随机让50%的权重为0
        x = F.relu(F.dropout(self.fc2(x),0.5)) 
        output = F.softmax(self.fc3(x),dim=1)
net = Model()
net(data)

from torchinfo import summary
summary(net,input_size=((10,3,227,227)))
# ==========================================================================================
# Layer (type:depth-idx)                   Output Shape              Param #
# ==========================================================================================
# model                                    --                        --
# ├─Conv2d: 1-1                            [10, 96, 55, 55]          34,944
# ├─MaxPool2d: 1-2                         [10, 96, 27, 27]          --
# ├─Conv2d: 1-3                            [10, 256, 27, 27]         614,656
# ├─MaxPool2d: 1-4                         [10, 256, 13, 13]         --
# ├─Conv2d: 1-5                            [10, 348, 13, 13]         802,140
# ├─Conv2d: 1-6                            [10, 348, 13, 13]         1,090,284
# ├─Conv2d: 1-7                            [10, 256, 13, 13]         802,048
# ├─MaxPool2d: 1-8                         [10, 256, 6, 6]           --
# ├─Linear: 1-9                            [10, 4096]                37,752,832
# ├─Linear: 1-10                           [10, 4096]                16,781,312
# ├─Linear: 1-11                           [10, 1000]                4,097,000
# ==========================================================================================
# Total params: 61,975,216
# Trainable params: 61,975,216
# Non-trainable params: 0
# Total mult-adds (G): 10.68
# ==========================================================================================
# Input size (MB): 6.18
# Forward/backward pass size (MB): 51.77
# Params size (MB): 247.90
# Estimated Total Size (MB): 305.85
# ==========================================================================================

与LeNet5实现时候的情况一致,由于输入数据的结构可能与原论文有区别,我们的网络架构中的小细节可能与原论文有些许不同。从代码量来看,其实AlexNet并没有比LeNet5复杂太多,当结构清晰时,我们甚至认为AlexNet有些简单。但在AlexNet出世之前,学术界普遍认为深度网络是不可能训练的,而今天我们都了解,只要有足够的算力和数据,即便是几亿参数的巨量网络也是可以训练的。AlexNet所带来的观念上的转变才是真正推动卷积神经网络向前发展的核心。

只要有架构图,复现经典架构其实并不是一件难事(照着敲代码,一点也不难),但我们不太可能完全记住经典架构中的每个细节。在复现架构时,我们应该注意哪些方面呢?

1、首先,代码可以不自己写,但一定要通
2、了解此架构的核心思想,以及在此思想下其结构大概如何排布。对于比较简单的网络(比如AlexNet),可以记住具体的层的结构(包括参数大概的大小),但对于复杂网络,可以不用难为自己,理解思想为主,看见代码能认出是该网络,并且看见代码之后能够理解每层具体在做什么,做的事情如何与该网络的核心思想一致。
3、了解此架构被提出的背景,即可了解它的突破与局限
4、发现架构与自己熟悉的不一致,先多谷歌下不同的架构图,或许你看到的只是别人基于经典架构修改的变体。实在纠结,再回看提出框架的具体论文。

到这里,相信大家对卷积层和池化层的应用已经有了基本的了解。作为进阶内容,大家可以试着摆脱课件中的代码,自己对着架构图复现网络架构,你甚至可以试着计算一下各层特征图的大小,将此架构改写为更简洁的形式,并利用这些架构在经典数据集上进行各种尝试。

你可能感兴趣的:(深度学习——PyTorch,网络,pytorch,深度学习)