基于卷积神经网络的图像分类:AlexNet
0.综述
AlexNet和VGG是深度学习应用于图像分类问题的经典之作。这两个网络的Paper里包含了很多重要的概念以及网络训练时的技巧。
AlexNet是2012年发表在NIPS(机器学习领域的顶级会议)上的,论文全称是:
《ImageNet Classification with Deep Convolutional Neural Networks》
论文的地址如下:
https://www.onacademic.com/detail/journal_1000039913864210_2a08.html
此网络主要应用于图像分类问题,由于第一作者是加拿大多伦多大学的Alex Krizhevsky,此网络被命名为AlexNet。作为经典中的经典,AlexNet中的一些网络训练技巧和防止过拟合技巧至今仍然被使用,因此这篇Paper非常有必要去研读和学习。
本论文包括以下八个部分的内容:
- 0.摘要:简单介绍了AlextNet的结构以及取得的成成果。
- 1.介绍:神经网络在有了算力更好的GPU和更多的数据集后可以取得更好的效果。
- 2.数据集:ILSVRC数据集和ImageNet数据集。
- 3.网络架构:Relu、两个GPU训练、LRN以及网络整体结构。
- 4.缓解过拟合:数据增强以及Dropout。
- 5.网络训练细节:网络超参数设置,权重及偏置的初始化。
- 6.结果:介绍了AlexNet在比赛中的成绩以及卷积结果可视化,同一类图像的欧氏距离更加接近。
- 7.讨论:结论说明了神经网络可以很好地完成图像分类任务。
1.Paper研究背景和研究成果
1.1 研究背景
首先是研究背景,深度学习离不开大量的训练数据和高性能的计算硬件,LabelMe和ImageNet等数量足够大的数据集的出现和GPU等硬件的计算能力的提升为深度学习应用于图像问题提供了必要基础。
1.2 研究成果
下图是AlexNet在ILSVRC-2012上取得的成绩,它以Top-5 15.3%的错误率取得了冠军,成绩远高于当时的Sift+FVs算法。
其中1 CNN表示AlexNet经过一次训练时的成绩,而5 CNNs是AlexNet训练五次后的平均成绩,1 CNN* 是在最后一个pooling层后额外添加第六个卷积层后使用ImageNet2011秋数据集预训练结果来微调后取得的成绩,而最后一个7 CNNs*是两个网络进行预训练微调,与5 CNNs取的平均成绩。
2.AlexNet网络结构及部分参数计算
AlexNet网络结构如下图所示:
可以看出,AlexNet包含5个卷积层,3个全连接层,3个MaxPooling层。网络计算层次分为了两个通道,在特定的层次上通信,这是因为当时的GPU计算能力不足以支撑它们在一个通道上进行计算,所以使用了两块GPU来计算。这里要注意论文中结构图里的输入是224x224,但是实际是227x227。
以单通道来看,网络结构及参数(参数包括可训练的参数数量及结构中的连接数量)分布如下:
网络层 | KernelSize | Depth | Strides | Padding | 输入尺寸 | 输出尺寸 | 参数个数 | 连接数 |
---|---|---|---|---|---|---|---|---|
conv_1 | 11x11 | 96 | 4 | VALID | 227x227x3 | 55x55x96 | (11x11x3+1)x96 | (227-11)/4+1=55 |
maxpool_1 | 3x3 | – | 2 | VALID | 55x55x96 | 27x27x96 | 0 | (55-3)/2+1=27 |
conv_2 | 5x5 | 256 | 1 | SAME | 27x27x96 | 27x27x256 | (5x5x48+1)x256 | 27/1 = 27 |
maxpool_2 | 3x3 | – | 2 | – | 27x27x256 | 13x13x256] | 0 | (27-3)/2+1=13 |
conv_3 | 3x3 | 384 | 1 | SAME | 13x13x256 | 13x13x384 | (3x3x256+1)x384 | 13/1 = 13 |
conv_4 | 3x3 | 192 | 1 | SAME | 13x13x384 | 13x13x384 | (3x3x192+1)x384 | 13/1 = 13 |
conv_5 | 3x3 | 192 | 1 | SAME | 13x13x384 | 13x13x256 | (3x3x192+1)x256 | 13/1 = 13 |
maxpool_3 | 3x3 | – | 2 | – | 13x13x256 | 6x6x256 | 0 | (13-3)/2+1=6 |
fc_6 | 6x6x256 | 4096 | 6x6x256x4096 | |||||
fc7 | 4096 | 4096 | 4096x4096 | |||||
fc_8 | 4096 | number_class | 4096xnumber_class |
卷积的两个细节:
- 输入特征图的通道数,也就是depth等于卷积核的通道数。
- 有多少个卷积核就有多少个输出特征图。
3.数据增强与超参数设置
第三部分为网络超参数的设置及训练,主要包括网络的一些超参数的具体值,及alexnet采取了哪种训练策略。在AlexNet中,数据增强主要包括以下几个方法:
- 1.随机地从256*256的原始图像中截取224x224大小的区域(以及水平翻转及镜像),相当于增加了2x(256-224)^2= 2048倍的数据量。 如果没有数据增强,仅靠原始的数据量,参数众多的CNN会陷入过拟合中,使用了数据增强后可以大大减轻过拟合,提升泛化能力。
- 2.对图像的RGB数据进行PCA处理,并对主成分做一个标准差为0.1的高斯扰动,增加一些噪声,这个Trick可以让错误率再下降1%。
- 3.进行预测时,则是取图片的四个角加中间共5个位置,并进行左右翻转,-共获得10张图片,对他们进行预测并对1 0次结果求均值。
而超参数设置主要包括以下几个:
- 批量大小: batchsize = 128。
- 权重衰减: weight decay = 0.0005。
- 学习率: learning rate = 0.01 衰减率为0.1。
- 轮数: epoches = 90。
- 卷积核初始化方式:均值为0方差为1的高斯分布。
- 偏置初始化方式2,4.5卷积层及全连接层初始化为1.剩余层初始化为0。
总结而来,AlexNet具有以下特点:
- 1.成功使用ReLU作为CNN的激活函数,并验证其效果在较深的网络超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度弥散问题。
- 2.训练时使用Dropout随机忽略一部分 神经元,以避免模型过拟合。Dropout虽有单独的论文论述,但是AlexNet将其实用化,通过实践证实了它的效果。在AlexNet中 主要是最后几个全连接层使用了Dropout。
- 3.在CNN中使用重叠的最大池化。此前CNN中普遍使用平均池化,AlexNet全 部使用最大池化,避免平均池化的模糊化效果。并且让步长比池化核的尺寸小,这样池化层的输出之间会有重叠和覆盖,提升了特征的丰富性。
- 4.提出了LRN层,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。(后来的vgg证明这个作用不大)
- 5.使用CUDA加速深度卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算。AlexNet使用了两块GTX 580 GPU进行训练,同时AlexNet的设计让GPU之间的通信只在网络的某些层进行,控制了通信的性能损耗。
- 6.数据增强,随机地从256+256的 原始图像中截取224224大小的区域(以及水平翻转的镜像)对图像的RGB数据进行PCA处理,并对主成分做-个标准差为0.1的高斯扰动,增加一些噪声,这个Trick可 以让错误率再下降1%。
LRN:
b
(
x
,
y
)
i
=
a
(
x
,
y
)
i
/
(
k
+
α
∑
j
=
m
a
x
(
0
,
i
−
n
/
2
)
m
i
n
(
N
−
1
,
i
+
n
/
2
)
(
a
(
x
,
y
)
j
)
2
)
β
b^i_{(x,y)}=a^i_{(x,y)}/{{(k+α\sum_{j=max(0,i-n/2)}^{min(N-1,i+n/2)}(a_{(x,y)}^j)^2)}^β}
b(x,y)i=a(x,y)i/(k+αj=max(0,i−n/2)∑min(N−1,i+n/2)(a(x,y)j)2)β
其中 a ( x , y ) i a^i_{(x,y)} a(x,y)i 表示Relu在第i个kernel的(x,y)位置的输出,
4.AlexNet的TensorFlow实现
"""AlexNet
# References:
- [ImageNet classification with deep convolutional neural networks](
https://proceedings.neurips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf)
"""
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.optimizers import *
from tensorflow.keras.utils import *
# Build AlexNet with Keras Functional API
def AlexNet(input_shape=(224,224,3),classes=1000,include_top=True):
# check include_top and classes
if include_top and classes!=1000:
raise ValueError("if include_top is True,classes should be 1000.")
input_ = tf.keras.Input(shape=input_shape,dtype=tf.float32)
net = ZeroPadding2D(padding=((1,2),(1,2)))(input_)
# first conv layer
net = Conv2D(filters=96,kernel_size=11,strides=4,padding='valid',activation='relu',name='conv_1')(net)
net = BatchNormalization(axis=1)(net)
net = MaxPool2D(pool_size=3,strides=2,padding='valid',name='maxpool_1')(net)
# second conv layer
net = Conv2D(filters=256,kernel_size=5,strides=1,padding='same',activation='relu',name='conv_2')(net)
net = BatchNormalization(axis=1)(net)
net = MaxPool2D(3,2,padding='valid',name='maxpool_2')(net)
# third conv layer
net = Conv2D(filters=384,kernel_size=3,strides=1,padding='same',activation='relu',name='conv_3')(net)
# forth and fifth conv layer
net = Conv2D(filters=384,kernel_size=3,strides=1,padding='same',activation='relu',name='conv_4')(net)
net = Conv2D(filters=256,kernel_size=3,strides=1,padding='same',activation='relu',name='conv_5')(net)
net = MaxPool2D(3,2,padding='valid',name='maxpool3')(net)
if include_top:
net = Flatten(name='flatten')(net)
net = Dense(4096, activation='relu',name='fc1')(net)
net = Dropout(0.5,name='dropout_1')(net)
net = Dense(4096, activation='relu',name='fc2')(net)
net = Dropout(0.5,name='dropout_2')(net)
net = Dense(classes, activation='softmax',name='predictions')(net)
model = tf.keras.Model(input_, net, name='AlexNet')
# load weights if necessary
if weights != None:
model.load_weights(weights)
print("Loading weigths from "+weights+" finished!")
return model
if __name__=='__main__':
# set env and gpu
import tensorflow as tf
import os
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
os.environ['CUDA_VISIBLE_DEVICES']='-1'
phy_gpus = tf.config.experimental.list_physical_devices('GPU')
for gpu in phy_gpus:
tf.config.experimental.set_memory_growth(gpu,True)
# test model
model = AlexNet(weights=None,input_shape=(224,224,3),include_top=True,classes=1000)
model.summary()
# =================================================================
# Total params: 62,378,672
# Trainable params: 62,378,508
# Non-trainable params: 164