本文为 AI 研习社编译的技术博客,原标题 : Five Powerful CNN Architectures 作者 | Faisal Shahbaz 翻译 | 小哥哥、Jaruce、zackary、Disillusion 校对 | 酱番梨 整理 | 菠萝妹 原文链接: https://medium.com/@faisalshahbaz/five-powerful-cnn-architectures-b939c9ddd57b 注:本文的相关链接请点击文末【阅读原文】进行访问
让我们来看看一些强大的卷积神经网络,这些网络实现的深度学习为今天的计算机视觉的成就奠定了基础。
LeNet-5,一个7层的卷积神经网络,被很多银行用于识别支票上的手写数字。
基于梯度的学习应用于文档识别
手写数字被数字化成尺寸为32*32的图片。在这种情况下,由于计算能力的限制,这种技术无法应用于大规模的图片。
我们来理解一下这种模型的结构。除了输入层,这个模型有七层。由于结构十分的迷你,我们逐层来研究这个模型:
我建议,在最后一层使用交叉熵损失函数和softmax激活函数,在这里不再赘述损失函数的细节以及使用其的原因。请采用不同的训练计划和学习率进行训练。
from keras import layers
from keras.models import Model
def lenet_5(in_shape=(32,32,1), n_classes=10, opt='sgd'):
in_layer = layers.Input(in_shape)
conv1 = layers.Conv2D(filters=20, kernel_size=5,
padding='same', activation='relu')(in_layer)
pool1 = layers.MaxPool2D()(conv1)
conv2 = layers.Conv2D(filters=50, kernel_size=5,
padding='same', activation='relu')(pool1)
pool2 = layers.MaxPool2D()(conv2)
flatten = layers.Flatten()(pool2)
dense1 = layers.Dense(500, activation='relu')(flatten)
preds = layers.Dense(n_classes, activation='softmax')(dense1)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
return model
if __name__ == '__main__':
model = lenet_5()
print(model.summary())
在2012年,Hinton的深度神经网络参加了世界上最重要的计算机视觉挑战赛imagenet,并将top-5损失从26%减少到15.3%,这一结果让世人惊艳。
这个神经网络跟LeNetg很像,但是比它更深,有大概六千万的参数。
使用深度卷积神经网络参加ImageNet
这个计算过程看起来确实有点吓人。这是因为网络由两半组成,每一部分都在两块不同的GPU上进行训练。我们把这个过程说的容易点,用一个精简版的图来说明这个问题:
这个结构包括5个卷积层和3个全连接层。这八层也都采用了当时的两个新概念——最大池化和Relu激活来为模型提供优势。
你可以在上图中找到不同层及其相应的配置。每一层的描述如下表:
注:Relu激活函数被用在除了最后的softmax层的所有卷积层和全连接层的输出部分。
作者也使用了其他很多技术(本帖不予以一一讨论)——比如dropout,augmentatio和动量随机梯度下降。
from keras import layers
from keras.models import Model
def alexnet(in_shape=(227,227,3), n_classes=1000, opt='sgd'):
in_layer = layers.Input(in_shape)
conv1 = layers.Conv2D(96, 11, strides=4, activation='relu')(in_layer)
pool1 = layers.MaxPool2D(3, 2)(conv1)
conv2 = layers.Conv2D(256, 5, strides=1, padding='same', activation='relu')(pool1)
pool2 = layers.MaxPool2D(3, 2)(conv2)
conv3 = layers.Conv2D(384, 3, strides=1, padding='same', activation='relu')(pool2)
conv4 = layers.Conv2D(256, 3, strides=1, padding='same', activation='relu')(conv3)
pool3 = layers.MaxPool2D(3, 2)(conv4)
flattened = layers.Flatten()(pool3)
dense1 = layers.Dense(4096, activation='relu')(flattened)
drop1 = layers.Dropout(0.5)(dense1)
dense2 = layers.Dense(4096, activation='relu')(drop1)
drop2 = layers.Dropout(0.5)(dense2)
preds = layers.Dense(n_classes, activation='softmax')(drop2)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
return model
if __name__ == '__main__':
model = alexnet()
print(model.summary())
2014年IMAGENET挑战赛的亚军。因为这种统一架构十分轻巧,不少新人将之作为深度卷积神经网络的简单形式。
在下面的文章中,我们将会学习这种最常用的网络架构之一是如何从图片中提取特征的(提取图像信息将之转化为包含图片重要信息的低维数组)
VGGNet有两条需要遵守的简单经验法则:
输入是224*224的RGB图像,所以输入尺寸为224x224x3
总参数为138,000,000.这些参数的大部分都来自于全连接层:
全连接层共包含了123,645,952个参数。
from keras import layers
from keras.models import Model, Sequential
from functools import partial
conv3 = partial(layers.Conv2D,
kernel_size=3,
strides=1,
padding='same',
activation='relu')
def block(in_tensor, filters, n_convs):
conv_block = in_tensor
for _ in range(n_convs):
conv_block = conv3(filters=filters)(conv_block)
return conv_block
def _vgg(in_shape=(227,227,3),
n_classes=1000,
opt='sgd',
n_stages_per_blocks=[2, 2, 3, 3, 3]):
in_layer = layers.Input(in_shape)
block1 = block(in_layer, 64, n_stages_per_blocks[0])
pool1 = layers.MaxPool2D()(block1)
block2 = block(pool1, 128, n_stages_per_blocks[1])
pool2 = layers.MaxPool2D()(block2)
block3 = block(pool2, 256, n_stages_per_blocks[2])
pool3 = layers.MaxPool2D()(block3)
block4 = block(pool3, 512, n_stages_per_blocks[3])
pool4 = layers.MaxPool2D()(block4)
block5 = block(pool4, 512, n_stages_per_blocks[4])
pool5 = layers.MaxPool2D()(block5)
flattened = layers.GlobalAvgPool2D()(pool5)
dense1 = layers.Dense(4096, activation='relu')(flattened)
dense2 = layers.Dense(4096, activation='relu')(dense1)
preds = layers.Dense(1000, activation='softmax')(dense2)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
return model
def vgg16(in_shape=(227,227,3), n_classes=1000, opt='sgd'):
return _vgg(in_shape, n_classes, opt)
def vgg19(in_shape=(227,227,3), n_classes=1000, opt='sgd'):
return _vgg(in_shape, n_classes, opt, [2, 2, 4, 4, 4])
if __name__ == '__main__':
model = vgg19()
print(model.summary())
它使用了一个inception模块,一个新颖的概念,具有较小的卷积,可以将参数的数量减少到仅仅400万个。
Inception模块
使用这些Inception模块的原因:
GoogLeNet/Inception — 架构
完整的inception架构:
深入了解卷积
你可能会在这个结构中看到一些带有softmax的“辅助分类器”。引用这篇论文——“通过添加连接到这些中间层的辅助分类器,我们期望在分类器的较低阶段加强辨别,增加被传播回来的梯度信号,并提供额外的正则化。”
但这意味着什么呢?他们的意思是:
辅助分类器结构:
注意:这里 #1×1代表Inception模块中1×1卷积里的过滤器。 #3×3简化(reduce)代表Inception模块中3×3卷积前的1×1卷积里的过滤器。 #5×5简化(reduce)代表Inception模块中5×5卷积前的1×1卷积里的过滤器。 #3×3代表Inception模块中3×3卷积里的过滤器。 #5×5代表Inception模块中5×5卷积里的过滤器。 池项目(pool proj)代表了inception模块中最大池前的1×1卷积里的过滤器。
GoogLeNet是典型的Inception架构
它使用了批处理标准化、图像失真和RMSprop,这些我们将在以后的文章中讨论。
2015年imagenet挑战赛中,top-5错误率在3.57%左右,低于人类top-5错误率。这都要归功于微软在竞赛中使用的ResNet( Residual Network 残差网络)。这个网络提出了一种全新的方法:“跳跃连接”
残差学习:一个模块
残差网络为这样一个现象提供了解决方案——当我们不停地加深神经网络时,深度神经网络的表现会变差。但从直觉上看来,这种事情不应该发生。如果一个深度为K的网络的表现用y来衡量,那么深度为K+1的网络至少也要有y的表现才对。
这个现象带来了一个假说:直接映射是很难学习的。所以,不去学习网络输出层与输入层间的映射,而是学习它们之间的差异——残差。
例如,设x为输入,H(x)是学习到的输出。我们得学习F(x) = H(x) -x。我们可以首先用一层来学习F(x)然后将x 与F(x)相加便得到了H(x)。作为结果,我们将H(x) 送至下一层,正如我们之前做的那样。这便是我们之前看到的残差块。
结果非常惊艳,这是因为导致神经网络无法学习的梯度消失问题被消除了。跳跃连接,或者说“捷径”,给出了一条捷径,以取得之前数层网络的梯度,跳过了之间的数层。
让我们用到这里:
本文提出了利用瓶颈进行更深的 ResNets- 50/101/152。神经网络使用1×1的卷积来增加和减少通道数量的维数,而不是使用上面提到的残块。
from keras import layers
from keras.models import Model
def _after_conv(in_tensor):
norm = layers.BatchNormalization()(in_tensor)
return layers.Activation('relu')(norm)
def conv1(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=1, strides=1)(in_tensor)
return _after_conv(conv)
def conv1_downsample(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=1, strides=2)(in_tensor)
return _after_conv(conv)
def conv3(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=3, strides=1, padding='same')(in_tensor)
return _after_conv(conv)
def conv3_downsample(in_tensor, filters):
conv = layers.Conv2D(filters, kernel_size=3, strides=2, padding='same')(in_tensor)
return _after_conv(conv)
def resnet_block_wo_bottlneck(in_tensor, filters, downsample=False):
if downsample:
conv1_rb = conv3_downsample(in_tensor, filters)
else:
conv1_rb = conv3(in_tensor, filters)
conv2_rb = conv3(conv1_rb, filters)
if downsample:
in_tensor = conv1_downsample(in_tensor, filters)
result = layers.Add()([conv2_rb, in_tensor])
return layers.Activation('relu')(result)
def resnet_block_w_bottlneck(in_tensor,
filters,
downsample=False,
change_channels=False):
if downsample:
conv1_rb = conv1_downsample(in_tensor, int(filters/4))
else:
conv1_rb = conv1(in_tensor, int(filters/4))
conv2_rb = conv3(conv1_rb, int(filters/4))
conv3_rb = conv1(conv2_rb, filters)
if downsample:
in_tensor = conv1_downsample(in_tensor, filters)
elif change_channels:
in_tensor = conv1(in_tensor, filters)
result = layers.Add()([conv3_rb, in_tensor])
return result
def _pre_res_blocks(in_tensor):
conv = layers.Conv2D(64, 7, strides=2, padding='same')(in_tensor)
conv = _after_conv(conv)
pool = layers.MaxPool2D(3, 2, padding='same')(conv)
return pool
def _post_res_blocks(in_tensor, n_classes):
pool = layers.GlobalAvgPool2D()(in_tensor)
preds = layers.Dense(n_classes, activation='softmax')(pool)
return preds
def convx_wo_bottleneck(in_tensor, filters, n_times, downsample_1=False):
res = in_tensor
for i in range(n_times):
if i == 0:
res = resnet_block_wo_bottlneck(res, filters, downsample_1)
else:
res = resnet_block_wo_bottlneck(res, filters)
return res
def convx_w_bottleneck(in_tensor, filters, n_times, downsample_1=False):
res = in_tensor
for i in range(n_times):
if i == 0:
res = resnet_block_w_bottlneck(res, filters, downsample_1, not downsample_1)
else:
res = resnet_block_w_bottlneck(res, filters)
return res
def _resnet(in_shape=(224,224,3),
n_classes=1000,
opt='sgd',
convx=[64, 128, 256, 512],
n_convx=[2, 2, 2, 2],
convx_fn=convx_wo_bottleneck):
in_layer = layers.Input(in_shape)
downsampled = _pre_res_blocks(in_layer)
conv2x = convx_fn(downsampled, convx[0], n_convx[0])
conv3x = convx_fn(conv2x, convx[1], n_convx[1], True)
conv4x = convx_fn(conv3x, convx[2], n_convx[2], True)
conv5x = convx_fn(conv4x, convx[3], n_convx[3], True)
preds = _post_res_blocks(conv5x, n_classes)
model = Model(in_layer, preds)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
return model
def resnet18(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape, n_classes, opt)
def resnet34(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape,
n_classes,
opt,
n_convx=[3, 4, 6, 3])
def resnet50(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape,
n_classes,
opt,
[256, 512, 1024, 2048],
[3, 4, 6, 3],
convx_w_bottleneck)
def resnet101(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape,
n_classes,
opt,
[256, 512, 1024, 2048],
[3, 4, 23, 3],
convx_w_bottleneck)
def resnet152(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
return _resnet(in_shape,
n_classes,
opt,
[256, 512, 1024, 2048],
[3, 8, 36, 3],
convx_w_bottleneck)
if __name__ == '__main__':
model = resnet50()
print(model.summary())
想要继续查看该篇文章相关链接和参考文献?
长按链接点击打开或点击底部【阅读原文】:
https://ai.yanxishe.com/page/TextTranslation/1249