2、深度卷积神经网络AlexNet
-
ImageNet
数据集:一个开源的图片数据集,包含超过 1400万张图片和图片对应的标签,包含2万多个类别。自从
2010
年以来,ImageNet
每年举办一次比赛,即:ImageNet
大规模视觉识别挑战赛ILSVRC
,比赛使用 1000 个类别图片。2017年7月,
ImageNet
宣布ILSVRC
于2017
年正式结束,因为图像分类、物体检测、物体识别任务中计算机的正确率都远超人类,计算机视觉在感知方面的问题基本得到解决,后续将专注于目前尚未解决的问题。 -
ImageNet
中使用两种错误率作为评估指标:-
top-5
错误率:对一个图片,如果正确标记在模型输出的前 5 个最佳预测(即:概率最高的前5个)中,则认为是正确的,否则认为是错误的。最终错误预测的样本数占总样本数的比例就是
top-5
错误率。 -
top-1
错误率:对一个图片,如果正确标记等于模型输出的最佳预测(即:概率最高的那个),则认为是正确的,否则认为是错误的。最终错误预测的样本数占总样本数的比例就是
top-1
错误率。
-
2.1 AlexNet简述
2.1.1 AlexNet概述
在LeNet提出后,卷积神经⽹络在计算机视觉和机器学习领域中很有名⽓。但卷积神经⽹络并没有主导这些领域。这是因为虽然LeNet在⼩数据集上取得了很好的效果,但是在更⼤、更真实的数据集上训练卷积神经⽹络的性能和可⾏性还有待研究。事实上,在上世纪90年代初到2012年之间的⼤部分时间⾥,神经⽹络往往被其他机器学习⽅法超越,如⽀持向量机(support vector machines)。
Alex Krizhevsky
【Hinton的博士,AlexNet一作】、Ilya Sutskever
【OpenAI联合创始人和首席科学家】和Geoff Hinton
【2018 年,与 Yoshua Bengio、Yann LeCun 共同获得了图灵奖】提出了⼀种新的卷积神经⽹络变体AlexNet,在2012年ImageNet挑战赛中,AlexeNet
以远超第二名的成绩夺冠,使得深度学习重回历史舞台,具有重大历史意义。
AlexNet论文下载地址:https://proceedings.neurips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf
【论文摘要翻译如下】
"""
我们训练了一个大型的深度卷积神经网络,将ImageNet LSVRC-2010竞赛中的120万张高分辨率图像分类成
1000种不同的类别。在测试数据上,我们取得了Top1 37.5%和Top-5 17.0%的错误率,这个结果已经远超
以前的最高水平。该神经网络具有6千万个参数和650,000个神经元,它由5个卷积层和3个全连接层构成,其
中部分卷积层后边跟有最大池化层,全连接层后边则是一个1000路的softmax分类器。为了使训练速度更
快,我们使用了非饱和神经元和一种卷积操作的非常高效的GPU实现。为了减少全连接层中的过拟合,我们使
用了一个最近开发的被称作“暂退法”的正则化方法,该方法已被证明十分有效。在IOLSVRC-2012竞赛中,我
们正式提出这个模型的一种变体,并以15.3%的top-5测试误差率赢得榜首,相比而言,第二好的参赛队伍则
只取得26.2%的成绩。
"""
2.1.2 LeNet和AlexNet的对比
AlexNet和LeNet的架构⾮常相似,这⾥我们提供了⼀个稍微精简版本的AlexNet,去除了当年需要两个⼩型GPU同时运算的设计特点。
左边为LeNet,右边为AlexNet
AlexNet和LeNet的设计理念⾮常相似,但也存在显著差异。
⾸先,AlexNet⽐相对较小的LeNet5要深得多。
AlexNet由⼋层组成:五个卷积层、两个全连接隐藏层和⼀个全连接输出层。
其次,AlexNet使⽤ReLU⽽不是sigmoid作为其激活函数。
2.1.3 AlexNet的网络结构
可以看到,由于早期GPU显存有限,原版的AlexNet采⽤了双数据流设计,使得每个GPU只负责存储和计算模型的⼀半参数。幸运的是,现在GPU显存相对充裕,所以我们现在很少需要跨GPU分解模型
AlexNet
有5个广义卷积层和3个广义全连接层。
- 广义的卷积层:包含了卷积层、池化层、
ReLU
、LRN
层等。 - 广义全连接层:包含了全连接层、
ReLU
、Dropout
层等。
网络结构如下表所示:
-
输入层会将
3@224x224
的三维图片预处理变成3@227x227
的三维图片。 -
第二层广义卷积层、第四层广义卷积层、第五层广义卷积层都是分组卷积,仅采用本
GPU
内的通道数据进行计算。第一层广义卷积层、第三层广义卷积层、第六层连接层、第七层连接层、第八层连接层执行的是全部通道数据的计算。
-
第二层广义卷积层的卷积、第三层广义卷积层的卷积、第四层广义卷积层的卷积、第五层广义卷积层的卷积均采用
same
填充。当卷积的步长为1,核大小为
3x3
时,如果不填充0,则feature map
的宽/高都会缩减 2 。因此这里填充0,使得输出feature map
的宽/高保持不变。其它层的卷积,以及所有的池化都是
valid
填充(即:不填充 0 )。 -
第六层广义连接层的卷积之后,会将
feature map
展平为长度为 4096 的一维向量。
编号 | 网络层 | 子层 | 核/池大小 | 核数量 | 步长 | 填充 | 激活函数 | 输入尺寸 | 输出尺寸 |
---|---|---|---|---|---|---|---|---|---|
第0层 | 输入层 | - | - | - | - | - | - | - | 3@224x224 |
第1层 | 广义卷积层 | 卷积 | 11x11 | 96 | 4 | - | ReLU | 3@227x227 | 96@55x55 |
第1层 | 广义卷积层 | 池化 | 3x3 | - | 2 | - | - | 96@55x55 | 96@27x27 |
第1层 | 广义卷积层 | LRN | - | - | - | - | - | 96@27x27 | 96@27x27 |
第2层 | 广义卷积层 | 卷积 | 5x5 | 256 | 1 | 2 | ReLU | 96@27x27 | 256@27x27 |
第2层 | 广义卷积层 | 池化 | 3x3 | - | 2 | - | - | 256@27x27 | 256@13x13 |
第2层 | 广义卷积层 | LRN | - | - | - | - | - | 256@13x13 | 256@13x13 |
第3层 | 广义卷积层 | 卷积 | 3x3 | 384 | 1 | 1 | ReLU | 256@13x13 | 384@13x13 |
第4层 | 广义卷积层 | 卷积 | 3x3 | 384 | 1 | 1 | ReLU | 384@13x13 | 384@13x13 |
第5层 | 广义卷积层 | 卷积 | 3x3 | 256 | 1 | 1 | ReLU | 384@13x13 | 256@13x13 |
第5层 | 广义卷积层 | 池化 | 3x3 | - | 2 | - | - | 256@13x13 | 256@6x6 |
第6层 | 广义连接层 | 卷积 | 6x6 | 4096 | 1 | - | ReLU | 256@6x6 | 4096@1x1 |
第6层 | 广义连接层 | dropout | - | - | - | - | - | 4096@1x1 | 4096@1x1 |
第7层 | 广义连接层 | 全连接 | - | - | - | - | ReLU | 4096 | 4096 |
第7层 | 广义连接层 | dropout | - | - | - | - | - | 4096 | 4096 |
第8层 | 广义连接层 | 全连接 | - | - | - | - | - | 4096 | 1000 |
我们用pytorch进行实现
import torch.nn as nn
import torch
class AlexNet(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
# 使⽤⼀个11*11的更⼤窗⼝来捕捉对象。
# 同时,步幅为4,以减少输出的⾼度和宽度。
# 另外,输出通道的数⽬远⼤于LeNet
nn.Conv2d(1, 96, kernel_size=11, stride=4),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 减⼩卷积窗⼝,使⽤填充为2来使得输⼊与输出的⾼和宽⼀致,且增⼤输出通道数
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 使⽤三个连续的卷积层和较⼩的卷积窗⼝。
# 除了最后的卷积层,输出通道的数量进⼀步增加。
# 在前两个卷积层之后,汇聚层不⽤于减少输⼊的⾼度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这⾥,全连接层的输出数量是LeNet中的好⼏倍。使⽤dropout层来减轻过拟合
nn.Linear(9216, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
# 最后是输出层。由于这⾥使⽤Fashion-MNIST,所以⽤类别数为10,⽽⾮论⽂中的1000
nn.Linear(4096, 10)
)
def forward(self, X):
X = self.model(X)
return X
if __name__ == '__main__':
net = AlexNet()
# 测试神经网络是否可运行
# inputs = torch.rand(size=(1, 1, 227, 227), dtype=torch.float32)
# outputs = net(inputs)
# print(outputs.shape)
X = torch.rand(size=(1, 1, 227, 227), dtype=torch.float32)
for layer in net.model:
X = layer(X)
print(layer.__class__.__name__, 'output shape:', X.shape)
"""
第1层:广义卷积层
Conv2d output shape: torch.Size([1, 96, 55, 55])
ReLU output shape: torch.Size([1, 96, 55, 55])
MaxPool2d output shape: torch.Size([1, 96, 27, 27])
第2层:广义卷积层
Conv2d output shape: torch.Size([1, 256, 27, 27])
ReLU output shape: torch.Size([1, 256, 27, 27])
MaxPool2d output shape: torch.Size([1, 256, 13, 13])
第3层:广义卷积层
Conv2d output shape: torch.Size([1, 384, 13, 13])
ReLU output shape: torch.Size([1, 384, 13, 13])
第4层:广义卷积层
Conv2d output shape: torch.Size([1, 384, 13, 13])
ReLU output shape: torch.Size([1, 384, 13, 13])
第5层:广义卷积层
Conv2d output shape: torch.Size([1, 256, 13, 13])
ReLU output shape: torch.Size([1, 256, 13, 13])
MaxPool2d output shape: torch.Size([1, 256, 6, 6])
第6层:广义连接层
Flatten output shape: torch.Size([1, 9216])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
第7层:广义连接层
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
第8层:广义连接层
Linear output shape: torch.Size([1, 10])
"""
2.2 AlexNet的创新点
AlexNet
成功的主要原因在于:
- 使用
ReLU
激活函数。 - 使用
dropout
、数据集增强 、重叠池化等防止过拟合的方法。 - 使用百万级的大数据集来训练。
- 使用
GPU
训练,以及的LRN
使用。 - 使用带动量的
mini batch
随机梯度下降来训练。
2.2.1 局部响应规范化
局部响应规范层LRN
:目地是为了进行一个横向抑制,使得不同的卷积核所获得的响应产生竞争。
LRN
层现在很少使用,因为效果不是很明显,而且增加了内存消耗和计算时间。
2.2.2 数据集增强
-
AlexNet
中使用的数据集增强手段:-
随机裁剪、随机水平翻转:原始图片的尺寸为
256xx256
,裁剪大小为224x224
。-
每一个
epoch
中,对同一张图片进行随机性的裁剪,然后随机性的水平翻转。理论上相当于扩充了数据集 倍。 -
在预测阶段不是随机裁剪,而是固定裁剪图片四个角、一个中心位置,再加上水平翻转,一共获得 10 张图片。
用这10张图片的预测结果的均值作为原始图片的预测结果。
-
-
PCA
降噪:对RGB
空间做PCA
变换来完成去噪功能。同时在特征值上放大一个随机性的因子倍数(单位1
加上一个 的高斯绕动),从而保证图像的多样性。- 每一个
epoch
重新生成一个随机因子。 - 该操作使得错误率下降
1%
。
- 每一个
-
-
AlexNet
的预测方法存在两个问题:- 这种
固定裁剪四个角、一个中心
的方式,把图片的很多区域都给忽略掉了。很有可能一些重要的信息就被裁剪掉。 - 裁剪窗口重叠,这会引起很多冗余的计算。
改进的思路是:
- 执行所有可能的裁剪方式,对所有裁剪后的图片进行预测。将所有预测结果取平均,即可得到原始测试图片的预测结果。
- 减少裁剪窗口重叠部分的冗余计算。
- 这种
具体做法为:将全连接层用等效的卷积层替代,然后直接使用原始大小的测试图片进行预测。将输出的各位置处的概率值按每一类取平均(或者取最大),则得到原始测试图像的输出类别概率。
2.2.3 重叠池化
-
一般的池化是不重叠的,池化区域的大小与步长相同。
Alexnet
中,池化是可重叠的,即:步长小于池化区域的大小。重叠池化可以缓解过拟合,该策略贡献了
0.4%
的错误率。 -
为什么重叠池化会减少过拟合,很难用数学甚至直观上的观点来解答。一个稍微合理的解释是:重叠池化会带来更多的特征,这些特征很可能会有利于提高模型的泛化能力。
2.2.4 优化算法
2.3 AlexNet在Fashion-MNIST数据集上的应用代码
2.3.1 创建AlexNet网络模型
import torch.nn as nn
import torch
class AlexNet(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
# 这⾥,我们使⽤⼀个11*11的更⼤窗⼝来捕捉对象。
# 同时,步幅为4,以减少输出的⾼度和宽度。
# 另外,输出通道的数⽬远⼤于LeNet
nn.Conv2d(1, 96, kernel_size=11, stride=4,padding=1), # 1、填充设置为1
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 减⼩卷积窗⼝,使⽤填充为2来使得输⼊与输出的⾼和宽⼀致,且增⼤输出通道数
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 使⽤三个连续的卷积层和较⼩的卷积窗⼝。
# 除了最后的卷积层,输出通道的数量进⼀步增加。
# 在前两个卷积层之后,汇聚层不⽤于减少输⼊的⾼度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这⾥,全连接层的输出数量是LeNet中的好⼏倍。使⽤dropout层来减轻过拟合
nn.Linear(6400, 4096), nn.ReLU(), # 2、输出相对2.1.3降为6400
nn.Dropout(p=0.5),
nn.Linear(4096, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
# 最后是输出层。由于这⾥使⽤Fashion-MNIST,所以⽤类别数为10,⽽⾮论⽂中的1000
nn.Linear(4096, 10)
)
def forward(self, X):
X = self.model(X)
return X
if __name__ == '__main__':
net = AlexNet()
# 测试神经网络是否可运行
inputs = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
outputs = net(inputs)
print(outputs.shape)
# X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
# for layer in net.model:
# X = layer(X)
# print(layer.__class__.__name__, 'output shape:', X.shape)
2.3.2 读取Fashion-MNIST数据集
其他所有的函数,与经典神经网络(1)LeNet及其在Fashion-MNIST数据集上的应用完全一致。
'''
Fashion-MNIST图像的分辨率(28×28像素)低于ImageNet图像。为了解决这个问题,增加到224×224
'''
batch_size = 128
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
2.3.2 模型训练
from _02_AlexNet import AlexNet
# 初始化模型
net = AlexNet()
# 与LeNet相比,使⽤更⼩的学习速率训练,这是因为⽹络更深更⼴、图像分辨率更⾼,训练卷积神经⽹络就更昂贵
# 注意需要用GPU进行训练
lr, num_epochs = 0.01, 10
train_ch(net, train_iter, test_iter, num_epochs, lr, try_gpu())
loss 0.327, train acc 0.881, test acc 0.885
4149.6 examples/sec on cuda:0