EverydayOneCat
⬆️??!??!⬆️ 😯😯⁉️ 🐯⬇️
LeNet
LeNet介绍
LeNet可以说是第一个运用到卷积神经网络CNN的模型,它包含了深度学习的基本模块:卷积层,池化层,全连接层。是其他深度学习模型的基础, 这里我们对LeNet进行深入分析。同时,通过实例分析,加深对与卷积层和池化层的理解。
LeNet共有7层,不包含输入,每层都包含可训练参数;每个层有多个Feature Map,每个Feature Map是通过一种卷积滤波器提取输入的一种特征,然后每个Feature Map有多个神经元。
或者我们用一张更加直观的图来展现LeNet模型的各个层:
model
基于上图我们可以轻松写出模型:
1 | class LeNet(nn.Module): |
train
接下来我们通过CIFAR10数据集写一个Demo来测试LeNet的实际训练效果
1 | transforms = torchvision.transforms.Compose( |
predict
有了基于数据集训练好的模型,我们可以加载其参数对未知图片进行预测分类
1 | transforms = torchvision.transforms.Compose([ |
AlexNet
AlexNet介绍
AlexNet是2012年ISLVRC 2012(ImageNet Large Scale Visual RecognitionChallenge)竞赛的冠军网络,分类准确率由传统的 70%+提升到 80%+。它是由Hinton和他的学生Alex Krizhevsky设计的。也是在那年之后,深度学习开始迅速发展
该网络亮点在于:
- 首次使用GPU作为网络加速训练
- 使用了ReLU激活函数,而不是传统的Sigmoid激活函数以及Tanh激活函数(Sigmoid存在梯度丢失、微分困难等问题)
- 使用了LRN局部响应归一化
- 在全连接层的前两层中使用了 Dropout 随机失活神经元操作,以减少过拟合
DropOut
我们知道在CNN中,我们经过一系列Convolution、Maxpooling后,虽然少了很多参数,但是Flatten后丢入全连接层input参数还是很多的,而参数过多就可能会导致过拟合问题。为此我们引入DropOut机制,在网络正向传播过程中随机失活一部分神经元,以减少过拟合。
model
我们总结一下上图AlexNet的过程,可以总结出如下表格
基于此我们写出model.py
为了加快训练,代码只使用了一半的网络参数,相当于只用了原论文中网络结构的下半部分(正好原论文中用的双GPU,我的电脑只有一块GPU)(后来我又用完整网络跑了遍,发现一半参数跟完整参数的训练结果acc相差无几)
1 | class AlexNet(nn.Module): |
数据集处理
测试AlexNet网络我们采用花数据集,数据集下载地址:http://download.tensorflow.org/example_images/flower_photos.tgz
包含 5 中类型的花,每种类型有600~900张图像不等。
由于此数据集不像 CIFAR10 那样下载时就划分好了训练集和测试集,因此需要自己划分。
编写split_data.py分类脚本:
1 | import os |
运行此脚本即可将数据分为训练集和验证集两部分
train
1 | data_transforms = { |
predict
1 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
预测结果
VGG
VGG介绍
VGG在2014年由牛津大学著名研究组VGG (Visual Geometry Group) 提出,斩获该年ImageNet竞赛中 Localization Task (定位任务) 第一名 和 Classification Task (分类任务) 第二名。
网络中的亮点:通过堆叠多个3x3的卷积核来替代大尺度卷积核(减少所需参数)
论文中提到,可以通过堆叠两个3x3的卷积核替代5x5的卷积核,堆叠三个3x3的卷积核替代7x7的卷积核。(即拥有相同的感受野)
CNN感受野
在卷积神经网络中,决定某一层输出结果中一个元素所对应的输入层的区域大小,被称作感受野(receptive field)。
通俗的解释是,输出feature map上的一个单元 对应 输入层上的区域大小。
以下图为例,输出层 layer3 中一个单元 对应 输入层 layer2 上区域大小为2×2(池化操作),对应输入层 layer1 上大小为5×5
感受野计算公式为$ F(i) = (F(i+1)-1) * Stride + Ksize $
论文中提到,可以通过堆叠两个3x3的卷积核替代5x5的卷积核,堆叠三个3x3的卷积核替代7x7的卷积核。接下来我们来证明(注:VGG网络中卷积的Stride默认为1)
堆叠3×3卷积核后训练参数是否真的减少了?
CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度
- 使用7×7卷积核所需参数个数:$77C*C=49C^2$
- 堆叠三个3×3的卷积核所需参数个数:$33CC+33CC+33C*C=27C^2$
VGG-16
VGG网络有多个版本,一般常用的是VGG-16模型,其网络结构如下如所示:
model
跟AlexNet中网络模型的定义一样,VGG网络也是分为 卷积层提取特征 和 全连接层进行分类 这两个模块
不同的是,VGG网络有 VGG-13、VGG-16等多种网络结构,能不能将这几种结构统一成一个模型呢?以上图的A、B、D、E模型为例,其全连接层完全一样,卷积层只有卷积核个数稍有不同。
我们可以用字典来统一模型
1 | # official pretrain weights |
这里遇到了python中一个奇怪的写法,参数前面加或者*,可能大佬觉得很简单,我从Java转过来不太了解
查阅资料发现其实就是可变参数的写法,具体参考Python 函数参数前面一个星号()和两个星号(*)的区别
Train和Predict的实现其实和上面的AlexNet如出一辙,只是需要注意的是由于VGG网络模型较深,需要使用GPU进行训练(而且要内存大一点的GPU。
GoogLeNet
GoogLeNet介绍
GoogLeNet在2014年由Google团队提出(与VGG网络同年,GoogLeNet中的L大写是为了致敬LeNet),斩获当年ImageNet竞赛中Classification Task (分类任务) 第一名。
GoogLeNet创新点:
- 引入了Inception结构(融合不同尺度的特征信息)
- 使用1x1的卷积核进行降维以及映射处理
- 添加两个辅助分类器帮助训练
- 丢弃全连接层,使用平均池化层(大大减少模型参数)
Inception结构
传统的CNN结构如AlexNet、VggNet都是串联的结构,即将一系列的卷积层和池化层进行串联得到的结构。
GoogLeNet 提出了一种并联结构,下图是论文中提出的inception原始结构,将特征矩阵同时输入到多个分支进行处理,并将输出的特征矩阵按深度进行拼接,得到最终输出。
在 inception 的基础上,还可以加上降维功能的结构,如下图所示,在原始 inception 结构的基础上,在分支2,3,4上加入了卷积核大小为1x1的卷积层,目的是为了降维(减小深度),减少模型训练参数,减少计算量。
注意:每个分支所得的特征矩阵高和宽必须相同,这样才能保证最后维度相加
1×1卷积核的降维功能
同样是对一个深度为512的特征矩阵使用64个大小为5x5的卷积核进行卷积,不使用1x1卷积核进行降维的 话一共需要819200个参数,如果使用1x1卷积核进行降维一共需要50688个参数,明显少了很多。
CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度
辅助分类器(Auxiliary Classifier)
AlexNet 和 VGG 都只有1个输出层,GoogLeNet 有3个输出层,其中的两个是辅助分类层。
在训练模型时,将两个辅助分类器的损失乘以权重(论文中是0.3)加到网络的整体损失上,再进行反向传播。
辅助分类器的两个分支有什么用呢?
- 作用一:可以把他看做inception网络中的一个小细节,它确保了即便是隐藏单元和中间层也参与了特征计算,他们也能预测图片的类别,他在inception网络中起到一种调整的效果,并且能防止网络发生过拟合。
- 作用二:给定深度相对较大的网络,有效传播梯度反向通过所有层的能力是一个问题。通过将辅助分类器添加到这些中间层,可以期望较低阶段分类器的判别力。在训练期间,它们的损失以折扣权重(辅助分类器损失的权重是0.3)加到网络的整个损失上。
论文参数描述:
model
GoogLeNet完整结构
下面是原论文中给出的网络参数列表
可以看出无论是参数还是层数都很多,我们分模块来编写
首先是最基础的卷积层,由于每次卷积后都需要一次ReLu激活,我们整合在一起
1 | #基础卷积层(卷积+ReLU) |
接下来编写Inception模块,所需要使用到参数有#1x1
, #3x3reduce
, #3x3
, #5x5reduce
, #5x5
, poolproj
,这6个参数,分别对应着所使用的卷积核个数。
#1x1
对应着分支1上1x1的卷积核个数#3x3reduce
对应着分支2上1x1的卷积核个数#3x3
对应着分支2上3x3的卷积核个数#5x5reduce
对应着分支3上1x1的卷积核个数#5x5
对应着分支3上5x5的卷积核个数poolproj
对应着分支4上1x1的卷积核个数。
1 | # Inception结构 每个分支所得的特征矩阵高和宽必须相同 |
接着是辅助分类器模块,我们对照论文给的参数来编写
这里的training是一个布尔类型,当我们实例化一个model之后,可以通过model.train()和model.eval()来控制模型的状态,在训练模式下training=True,验证模式下training=False
1 | #辅助分类器 |
最后整合起来
1 | import torch.nn as nn |
train
训练部分跟AlexNet和VGG类似,有两点需要注意:
实例化网络时的参数
1
net = GoogLeNet(num_classes=5, aux_logits=True, init_weights=True)
GoogLeNet的网络输出 loss 有三个部分,分别是主干输出loss、两个辅助分类器输出loss(权重0.3)
1
2
3
4
5logits, aux_logits2, aux_logits1 = net(images.to(device))
loss0 = loss_function(logits, labels.to(device))
loss1 = loss_function(aux_logits1, labels.to(device))
loss2 = loss_function(aux_logits2, labels.to(device))
loss = loss0 + loss1 * 0.3 + loss2 * 0.3
predict
预测部分跟AlexNet和VGG类似,需要注意在实例化模型时不需要 辅助分类器
1 | # create model |
但是在加载训练好的模型参数时,由于其中是包含有辅助分类器的,需要设置strict=False
1 | missing_keys, unexpected_keys = model.load_state_dict(torch.load(model_weight_path), strict=False) |
ResNet
ResNet介绍
原论文地址:Deep Residual Learning for Image Recognition(作者是CV大佬何凯明团队)
ResNet在2015年由微软实验室提出,斩获当年ImageNet竞赛中分类任务第一名,目标检测第一名。获得COCO数据集中目标检测第一名,图像分割第一名。
ResNet网络的亮点:
- 提出 Residual 结构(残差结构),并搭建超深的网络结构(可突破1000层)
- 使用 Batch Normalization 加速训练(丢弃dropout)
下图是ResNet34层模型的结构简图:
传统CNN存在的问题
在ResNet网络提出之前,传统的卷积神经网络都是通过将一系列卷积层与池化层进行堆叠得到的。
一般我们会觉得网络越深,特征信息越丰富,模型效果应该越好。但是实验证明,当网络堆叠到一定深度时,会出现两个问题:
梯度消失或梯度爆炸
梯度消失:若每一层的误差梯度小于1,反向传播时,网络越深,梯度越趋近于0
梯度爆炸:反之,若每一层的误差梯度大于1,反向传播时,网路越深,梯度越来越大退化问题(degradation problem):在解决了梯度消失、爆炸问题后,仍然存在深层网络的效果可能比浅层网络差的现象
总结就是,当网络堆叠到一定深度时,反而会出现深层网络比浅层网络效果差的情况,如下图
对于梯度消失或梯度爆炸问题,ResNet论文提出通过数据的预处理以及在网络中使用 BN(Batch Normalization)层来解决。
对于退化问题,ResNet论文提出了 residual结构(残差结构)来减轻退化问题,下图是使用residual结构的卷积网络,可以看到随着网络的不断加深,效果并没有变差,而是变的更好了。(虚线是train error,实线是test error)
Batch Normalization
我们在图像预处理过程中通常会对图像进行标准化处理,这样能够加速网络的收敛,如下图所示,对于Conv1来说输入的就是满足某一分布的特征矩阵,但对于Conv2而言输入的feature map就不一定满足某一分布规律了(注意这里所说满足某一分布规律并不是指某一个feature map的数据要满足分布规律,理论上是指整个训练样本集所对应feature map的数据要满足分布规律)。而我们Batch Normalization的目的就是使我们的feature map满足均值为0,方差为1的分布规律。
具体原理和实现参照这篇博文:https://blog.csdn.net/qq_37541097/article/details/104434557
使用BN时需要注意的问题:
- 训练时要将traning参数设置为True,在验证时将trainning参数设置为False。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。
- batch size尽可能设置大点,设置小后表现可能很糟糕,设置的越大求的均值和方差越接近整个训练集的均值和方差。
- 建议将bn层放在卷积层(Conv)和激活层(例如Relu)之间,且卷积层不要使用偏置bias,因为没有用。
Residual
为了解决深层网络中的退化问题,可以人为地让神经网络某些层跳过下一层神经元的连接,隔层相连,弱化每层之间的强联系。这种神经网络被称为 残差网络 (ResNets)。
残差网络由许多隔层相连的神经元子模块组成,我们称之为 残差块 Residual block。
我们可以根据Residual残差结构写出$X_L$与上面任意一层$X_l$之间的关系
对于resnet残差连接可以用“传话筒”游戏来通俗理解:类似于《王牌》中的传话筒,腾哥在看到了“狗中赤兔”这个词后,形象地演给花花看,花花又演给晓彤看,最后晓彤演给玲姐看,结果玲姐看完一脸懵~。可以看出,“狗中赤兔”在传递过程中信息是不断减少的,腾哥获得了最多的信息,而玲姐获得的最少,这就类似于浅层网络获得的信息多,而深层少,最终深层网络无法理解传来的信息,也就是玲姐猜不出来题。(这一现象称之为“梯度消失”,就是指信息一层层不断减少直至消失)那怎么办呢?为了解决这个问题,腾哥就跳过花花晓彤,单独给玲姐演了一遍,结果玲姐顿悟—“狗中赤兔”!这相当于浅层网络绕开中间网络,把信息直接传给了深层网络,深层网络秒懂。残差连接就是将信息直接传给深层网络,避免了浅层网络对信息的削减。(还有一种“梯度爆炸”现象,是指每一层网络传递的信息越来越多,导致深层网络直接“死机”了)
ResNet中的残差结构
实际应用中,残差结构的 short cut 不一定是隔一层连接,也可以中间隔多层,ResNet所提出的残差网络中就是隔多层。
跟VggNet类似,ResNet也有多个不同层的版本,而残差结构也有两种对应浅层和深层网络:
ResNet | 残差结构 | |
---|---|---|
浅层网络 | ResNet18/34 | BasicBlock |
深层网络 | ResNet50/101/152 | Bottleneck |
下图中左侧残差结构称为 BasicBlock,右侧残差结构称为 Bottleneck
对于深层的 Bottleneck,1×1的卷积核起到降维和升维(特征矩阵深度)的作用,同时可以大大减少网络参数。
可以计算一下,假设两个残差结构的输入特征和输出特征矩阵的深度都是256维,如下图:(注意左侧结构的改动)
那么两个残差结构所需的参数为:
左侧:$33256256+33256256=1179648$
右侧:$1125664+336464+11256*64=69632$明显搭建深层网络时,使用右侧的残差结构更合适。
Short cut的维度处理
我们来看ResNet18层网络结构图,可以发现有些残差块的 short cut 是实线的,而有些则是虚线的。
这些虚线的 short cut 上通过1×1的卷积核进行了维度处理(特征矩阵在长宽方向降采样,深度方向调整成下一层残差结构所需要的channel)。
原文的表注中已说明,conv3_x, conv4_x, conv5_x所对应的一系列残差结构的第一层残差结构都是虚线残差结构。因为这一系列残差结构的第一层都有调整输入特征矩阵shape的使命(将特征矩阵的高和宽缩减为原来的一半,将深度channel调整成下一层残差结构所需要的channel)
需要注意的是,对于ResNet50/101/152,其实conv2_x所对应的一系列残差结构的第一层也是虚线残差结构,因为它需要调整输入特征矩阵的channel。根据表格可知通过3x3的max pool之后输出的特征矩阵shape应该是[56, 56, 64],但conv2_x所对应的一系列残差结构中的实线残差结构它们期望的输入特征矩阵shape是[56, 56, 256](因为这样才能保证输入输出特征矩阵shape相同,才能将捷径分支的输出与主分支的输出进行相加)。所以第一层残差结构需要将shape从[56, 56, 64] —> [56, 56, 256]。注意,这里只调整channel维度,高和宽不变(而conv3_x, conv4_x, conv5_x所对应的一系列残差结构的第一层虚线残差结构不仅要调整channel还要将高和宽缩减为原来的一半)。
ResNet 18/34:
ResNet 50/101/152:
model
定义ResNet18/34的残差结构,即BasicBlock
1 | # ResNet18/34的残差结构,用的是2个3x3的卷积 |
定义ResNet50/101/152的残差结构,即Bottleneck
1 | # ResNet50/101/152的残差结构,用的是1x1+3x3+1x1的卷积 |
定义ResNet网络结构
1 | class ResNet(nn.Module): |
定义resnet18/34/50/101/152
1 | def resnet34(num_classes=1000, include_top=True): |
train
由于ResNet网络较深,直接训练的话会非常耗时,因此用迁移学习的方法导入预训练好的模型参数,以下是ResNet官方与训练好的模型下载地址:
1 | model_urls = { |
这里数据的预处理我们需要和官方的预处理一样,保证后面的迁移学习正确率:
1 | data_transform = { |
载入模型时需要用到迁移学习:
1 | net = resnet34() |
ResNext
ResNext其实就是在ResNet的基础上吸收了Inception结构,并引入了一个新的概念——分组Group——也称为cardinality
如下图所示,上面是我们传统的k*k的n维卷积核,下面是我们把input分为g组,卷积核也分为g组,所需要的参数,可以看出,分组分的越多,所需要的参数就越小。
和之前介绍的精心设计的Inception模块不一样,ResNext的Inception每个结构使用相同的拓扑结构。
——————————————————————————————————————————————
ResNext没太搞懂,以后再完善
——————————————————————————————————————————————
迁移学习
迁移学习是一个比较大的领域,我们这里说的迁移学习是指神经网络训练中使用到的迁移学习。
在迁移学习中,我们希望利用源任务(Source Task)学到的知识帮助学习目标任务 (Target Task)。例如,一个训练好的图像分类网络能够被用于另一个图像相关的任务。再比如,一个网络在仿真环境学习的知识可以被迁移到真实环境的网络。迁移学习一个典型的例子就是载入训练好VGG网络,这个大规模分类网络能将图像分到1000个类别,然后把这个网络用于另一个任务,如医学图像分类。
为什么可以这么做呢?如下图所示,神经网络逐层提取图像的深层信息,这样,预训练网络就相当于一个特征提取器。
使用迁移学习的优势:
- 能够快速的训练出一个理想的结果
- 当数据集较小时也能训练出理想的效果
注意:使用别人预训练模型参数时,要注意别人的预处理方式。
常见的迁移学习方式:
- 载入权重后训练所有参数
- 载入权重后只训练最后几层参数
- 载入权重后在原网络基础上再添加一层全连接层,仅训练最后一个全连接层
结语
RNG干碎T1吧🤔