计算机系统应用教程网站

网站首页 > 技术文章 正文

回顾五个强大的CNN架构介绍及Python示例

btikc 2024-09-04 03:18:47 技术文章 14 ℃ 0 评论

让我们来回顾一些强大的卷积神经网络,它们为今天的计算机视觉成就奠定了基础,这些成就是通过深度学习获得的。

LeNet-5

LeNet-5是一个7层卷积神经网络,部署在许多银行系统中,用于识别支票上的手写数字。

LeNet-5 - 架构

手写数字被数字化为像素大小的灰度图像--32×32。那时,计算能力有限,因此该技术无法扩展到大规模图像。

该模型包含7层(不包括输入层)。由于它是一个相对较小的架构,让我们逐层解释:

  1. 第1层:卷积层,核大小为5×5,步长为1×1,总共为6个核。因此,大小为32x32x1的输入图像的输出为28x28x6。层中的总参数= 5 * 5 * 6 + 6(偏差项)
  2. 第2层:具有2×2核大小的池化层,总共2×2和6个核。这个池化层的行为与之前的文章略有不同。将接收器中的输入值相加,然后乘以可训练参数(每个filter 1个),最后将结果加到可训练的偏差(每个filter 1个)。最后,将sigmoid激活应用于输出。因此,来自前一层大小为28x28x6的输入被子采样为14x14x6。层中的总参数= [1(可训练参数)+ 1(可训练偏差)] * 6 = 12
  3. 第3层:与第1层类似,此层是具有相同配置的卷积层,除了它有16个filters而不是6个。因此,前一个大小为14x14x6的输入提供10x10x16的输出。层中的总参数= 5 * 5 * 16 + 16 = 416。
  4. 第4层:与第2层类似,此层是一个池化层,这次有16个filters。请记住,输出通过sigmoid激活函数传递。来自前一层的大小为10x10x16的输入被子采样为5x5x16。层中的总参数=(1 + 1)* 16 = 32
  5. 第5层:卷积层,核大小为5×5,filters为120。由于输入大小为5x5x16,因此我们无需考虑步幅,因此输出为1x1x120。层中的总参数= 5 * 5 * 120 = 3000
  6. 第6层:这是一个包含84个参数的dense层。因此,120个units的输入转换为84个units。总参数= 84 * 120 + 84 = 10164.此处使用的激活函数相当独特。我要说的是,你可以在这里尝试你的任何选择,因为按照今天的标准,这个任务非常简单。
  7. 输出层:最后,使用具有10个units的dense层。总参数= 84 * 10 + 10 = 924。

跳过所使用的损失函数的细节及其使用原因,我建议在最后一层使用softmax激活的交叉熵损失。尝试不同的训练计划和学习率。

LeNet-5 - Python代码

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())

AlexNet

2012年,Hinton的深度神经网络将世界上最重要的计算机视觉挑战图像网络中的损失从26%减少到15.3%。

该网络与LeNet非常相似,但更深,拥有大约6000万个参数。

AlexNet - 架构

当然这个数字看起来吓人。这是因为网络被分成两个半部分,每个部分在两个不同的gpu上同时被训练。让我们做一下简单的为我们带来一个更简单的版本的图片:

该架构由5个卷积层和3个全连接层组成。这8个层与当时的两个新概念相结合--MaxPooling和ReLU激活为他们的模型提供了优势。

您可以在上图中看到各种层及其配置。这些层如下表所示:

注意:ReLU激活应用于除最后一个softmax图层之外的每个卷积和全连接层的输出。

作者使用了各种其他技术 - dropout,augmentation 和Stochastic Gradient Descent with momentum。

AlexNet - Python代码

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())

VGGNet

2014年imagenet挑战的亚军被命名为VGGNet。由于其简单的统一结构,它以更简单的形式提出了一种更简单的深度卷积神经网络的形式。

VGGNet - 架构

VGGNet有两条简单的经验法则:

  • 每个卷积层都有配置 - 核大小= 3×3,stride = 1×1,padding = same。唯一不同的是filters的数量。
  • 每个Max Pooling层都有配置 - windows size= 2×2和stride = 2×2。因此,我们在每个Pooling层的图像大小减半。

输入图像是224×224像素的RGB图像。所以输入大小= 224x224x3

总参数= 1.38亿。大多数这些参数由全连接层贡献。

  • 第一个FC层= 4096 *(7 * 7 * 512)+ 4096 = 102,764,544
  • 第二个FC层= 4096 * 4096 + 4096 = 16,781,312
  • 第三个FC层= 4096 * 1000 + 4096 = 4,100,096

FC层贡献的总参数= 123,645,952。

VGGNet - 代码

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())

GoogLeNet / Inception

2014年imagenet竞赛的获胜者 - GoogLeNet(又名Inception v1)。它使用了一个inception 模块,一个新颖的概念,具有较小的卷积,允许将参数数量减少到仅400万。

使用这些Inception module的原因:

  1. 每个层类型从输入中提取不同的信息。从3×3层收集的信息将与从5×5层收集的信息不同。我们怎么知道哪一种transformation 是最好的呢?所以我们全部使用它们!
  2. 使用1×1卷积减少尺寸!考虑一个128x128x256输入。如果我们通过20个大小为1×1的过滤器,我们将获得128x128x20的输出。因此,我们在3×3或5×5卷积之前应用它们,以减少用于降维的inception block中这些层的输入filters的数量。

GoogLeNet / Inception - 架构

完整的初始架构:

您可能会在此结构中看到一些带有softmax的“辅助分类器”。在这里引用论文 - “通过增加连接到这些中间层的辅助分类器,我们期望鼓励分类器的较低阶段的区分,增加传播后的梯度信号,并提供附加的正则化。”

但是这是什么意思?基本上他们的意思是:

  1. 在较低阶段的discrimination :我们将在网络中训练较低层,其中梯度来自较早阶段的层以用于输出概率。这可以确保网络在较早的时候对不同的对象有一些区别。
  2. 增加传播回来的梯度信号:在深度神经网络中,通常,回流的梯度(使用反向传播)变得非常小,以至于网络的早期层很难学习。因此,较早的分类层通过传播强梯度信号来训练网络而变得有用。
  3. 提供额外的正规化:深度神经网络往往overfit(或导致高方差)数据,同时小神经网络往往underfit(或导致高偏差)。较早的分类器规范了更深层的过度拟合效果!

辅助分类器的结构:

注意:

#1×1表示inception module中1×1卷积的filters 。

#3×3 reduce表示在inception module中3×3卷积之前的1×1卷积中的filters 。

#5×5 reduce表示在inception module中5×5卷积之前的1×1卷积中的filters 。

#3×3表示inception module中3×3卷积的filters 。

#5×5表示inception module中5×5卷积的filters 。

Pool Proj表示在inception module中Max Pool之前的1×1卷积中的filters 。

它使用了批量归一化,图像失真和RMSprop。

GoogLeNet / Inception - 代码

from keras import layers
from keras.models import Model
from functools import partial
conv1x1 = partial(layers.Conv2D, kernel_size=1, activation='relu')
conv3x3 = partial(layers.Conv2D, kernel_size=3, padding='same', activation='relu')
conv5x5 = partial(layers.Conv2D, kernel_size=5, padding='same', activation='relu')
def inception_module(in_tensor, c1, c3_1, c3, c5_1, c5, pp):
 conv1 = conv1x1(c1)(in_tensor)
 conv3_1 = conv1x1(c3_1)(in_tensor)
 conv3 = conv3x3(c3)(conv3_1)
 conv5_1 = conv1x1(c5_1)(in_tensor)
 conv5 = conv5x5(c5)(conv5_1)
 pool_conv = conv1x1(pp)(in_tensor)
 pool = layers.MaxPool2D(3, strides=1, padding='same')(pool_conv)
 merged = layers.Concatenate(axis=-1)([conv1, conv3, conv5, pool])
 return merged
def aux_clf(in_tensor):
 avg_pool = layers.AvgPool2D(5, 3)(in_tensor)
 conv = conv1x1(128)(avg_pool)
 flattened = layers.Flatten()(conv)
 dense = layers.Dense(1024, activation='relu')(flattened)
 dropout = layers.Dropout(0.7)(dense)
 out = layers.Dense(1000, activation='softmax')(dropout)
 return out
def inception_net(in_shape=(224,224,3), n_classes=1000, opt='sgd'):
 in_layer = layers.Input(in_shape)
 conv1 = layers.Conv2D(64, 7, strides=2, activation='relu', padding='same')(in_layer)
 pad1 = layers.ZeroPadding2D()(conv1)
 pool1 = layers.MaxPool2D(3, 2)(pad1)
 conv2_1 = conv1x1(64)(pool1)
 conv2_2 = conv3x3(192)(conv2_1)
 pad2 = layers.ZeroPadding2D()(conv2_2)
 pool2 = layers.MaxPool2D(3, 2)(pad2)
 inception3a = inception_module(pool2, 64, 96, 128, 16, 32, 32)
 inception3b = inception_module(inception3a, 128, 128, 192, 32, 96, 64)
 pad3 = layers.ZeroPadding2D()(inception3b)
 pool3 = layers.MaxPool2D(3, 2)(pad3)
 inception4a = inception_module(pool3, 192, 96, 208, 16, 48, 64)
 inception4b = inception_module(inception4a, 160, 112, 224, 24, 64, 64)
 inception4c = inception_module(inception4b, 128, 128, 256, 24, 64, 64)
 inception4d = inception_module(inception4c, 112, 144, 288, 32, 48, 64)
 inception4e = inception_module(inception4d, 256, 160, 320, 32, 128, 128)
 pad4 = layers.ZeroPadding2D()(inception4e)
 pool4 = layers.MaxPool2D(3, 2)(pad4)
 aux_clf1 = aux_clf(inception4a)
 aux_clf2 = aux_clf(inception4d)
 inception5a = inception_module(pool4, 256, 160, 320, 32, 128, 128)
 inception5b = inception_module(inception5a, 384, 192, 384, 48, 128, 128)
 pad5 = layers.ZeroPadding2D()(inception5b)
 pool5 = layers.MaxPool2D(3, 2)(pad5)
 avg_pool = layers.GlobalAvgPool2D()(pool5)
 dropout = layers.Dropout(0.4)(avg_pool)
 preds = layers.Dense(1000, activation='softmax')(dropout)
 model = Model(in_layer, [preds, aux_clf1, aux_clf2])
 model.compile(loss="categorical_crossentropy", optimizer=opt,
	 metrics=["accuracy"])
 return model
if __name__ == '__main__':
 model = inception_net()
 print(model.summary())

ResNet

2015年的imagenet竞赛带来了Top-5的错误率3.57%。这是由于使用了ResNet(残差网络)模型。该网络引入了一种称为“skip connections”的新方法。

该想法作为一种解决办法来解决一个深度神经网络的问题,因为我们一直在添加层。但直觉来说,不应该这样。如果具有k层的网络执行为y,则具有k + 1层的网络应该至少执行y。

观察一个假设:直接映射很难学习。因此,不是学习层的输出和它的输入之间的映射,而是学习它们之间的差异 - 学习残差。

比方说,x是输入,H(x)是学习输出。所以,我们需要学习F(x)= H(x) - x。我们可以通过首先创建一个层来学习F(x)然后将x添加到F(x)来实现这一点,从而实现H(x)。结果,我们在下一层发送与之前一样的H(x)!这导致我们在上面看到的残差块。

结果令人惊讶,因为通常使深度神经网络的消失梯度问题被消除了。我们可以这样说,skip connections或shortcuts为前面的层提供了梯度的捷径,跳过了一堆层。

ResNet - 架构

论文提到了更深层ResNets的瓶颈使用 - 50/101/152。不使用上述残差块,网络使用1×1卷积来增加和减少信道数量的维数。

ResNet - 代码

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())

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表