经典CNN的实现 ResNet
ResNet诞生于2015年,当年ImageNet竞赛冠军,Top5错误率为3.57%
论文出处:Kaiming He, Xiangyu Zhang, Shaoqing Ren. Deep Residual Learning for Image Recognition. In CPVR, 2016.
引入残差结构
从之前介绍的 4 种 CNN 经典网络结构我们也可以看出,网络层数的发展趋势是不断加深的。 这是由于深度网络本身集成了低层/中层/高层特征和分类器,以多层首尾相连的方式存在,所以可以通过增加堆叠的层数(深度)来丰富特征的层次,以取得更好的效果。
然而,单纯地增加网络的层数会导致**梯度消失(爆炸)**问题,它从根源上导致了函数无法收敛。通过标准初始化(normalized initialization)以及中间标准化层 (intermediate normalization layer),已经可以较好地解决这个问题了,这使得深度为数十层 的网络在反向传播过程中,可以通过随机梯度下降(SGD)的方式开始收敛。 但是,当深度更深的网络也可以开始收敛时,网络退化的问题就显露了出来:随着网络深度的增加,准确率先是达到瓶颈(这是很常见的),然后便开始迅速下降。需要注意的是, 这种退化并不是由过拟合引起的。对于一个深度比较合适的网络来说,继续增加层数反而会导致训练错误率的提升。
#mermaid-svg-nQciERliqblr7KPm {font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-nQciERliqblr7KPm .error-icon{fill:#552222;}#mermaid-svg-nQciERliqblr7KPm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nQciERliqblr7KPm .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-nQciERliqblr7KPm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nQciERliqblr7KPm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nQciERliqblr7KPm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nQciERliqblr7KPm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nQciERliqblr7KPm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nQciERliqblr7KPm .marker.cross{stroke:#333333;}#mermaid-svg-nQciERliqblr7KPm svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nQciERliqblr7KPm .label{font-family:”trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-nQciERliqblr7KPm .cluster-label text{fill:#333;}#mermaid-svg-nQciERliqblr7KPm .cluster-label span{color:#333;}#mermaid-svg-nQciERliqblr7KPm .label text,#mermaid-svg-nQciERliqblr7KPm span{fill:#333;color:#333;}#mermaid-svg-nQciERliqblr7KPm .node rect,#mermaid-svg-nQciERliqblr7KPm .node circle,#mermaid-svg-nQciERliqblr7KPm .node ellipse,#mermaid-svg-nQciERliqblr7KPm .node polygon,#mermaid-svg-nQciERliqblr7KPm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nQciERliqblr7KPm .node .label{text-align:center;}#mermaid-svg-nQciERliqblr7KPm .node.clickable{cursor:pointer;}#mermaid-svg-nQciERliqblr7KPm .arrowheadPath{fill:#333333;}#mermaid-svg-nQciERliqblr7KPm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nQciERliqblr7KPm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nQciERliqblr7KPm .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-nQciERliqblr7KPm .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-nQciERliqblr7KPm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nQciERliqblr7KPm .cluster text{fill:#333;}#mermaid-svg-nQciERliqblr7KPm .cluster span{color:#333;}#mermaid-svg-nQciERliqblr7KPm p.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nQciERliqblr7KPm :root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;} 增加网络层数 梯度消失或梯度爆炸 引入批标准化等解决 继续增加网络层数 网络退化
例如下面的例子:
为了应对这个问题,ResNet 引入残差结构
,通过对残差结构的运用,ResNet 使得训练数百层的网络成为了可能,从而具有非常 强大的表征能力。
需要注意的是,ResNet块中的“+”是特征图对应元素值相加(矩阵值相加
),而Inception块中的“+”是沿深度方向叠加(千层蛋糕层数叠加)
也就是说,每个ResNet块的输入经过两条路径:
- 一后二卷积操作
- 身份图
- 在输出中,将两者对应的元素值相加作为输出。
上图中的实线和虚线都表示恒等映射;
- 实线表示通道相同,计算方式为 H(x) = F(x) + x
- 虚线表示通道不同,计算方式为 H(x) = F(x) + Wx,其中 W 为卷积操作,目的是调整 x 的维 度(通道数)
注意:1*1卷积操作可通过步长改变特征图尺寸,通过卷积核个数改特征图深度。
这里说明一下1 * 1的卷积运算是如何降低特征深度:下面以5 * 5的卷积运算为例说明这个问题。
假设网络上一层的输出为 128 * 100 * 100 (C * H * W )
- (1)通过 32 * 5 * 5(32 个大小 为 5 * 5 的卷积核)的卷积层(步长为 1、全零填充)后,输出为 32 * 100 * 100 ,卷积层的 参数量为 32 * 5 * 5 * 128 = 102400;
- (2)如果先通过 32 * 1 * 1 的卷积层(输出为 32 * 100 * 100), 再通过 32 * 5 * 5 的卷积层,输出仍为 32 * 100 * 100,但卷积层的参数量变为 32 * 1 * 1 * 128 + 32 * 5 * 5 * 32 = 29696,仅为原参数量的 30 %左右,这就是小卷积核的降维作用。
实现残差结构
为了实现这种残差结构,我们定义一个新的ResnetBlock 类。
class ResnetBlock(Model):
def __init__(self,filters,strides=1,residual_path=False):
super(ResnetBlock, self).__init__()
self.filters=filters
self.strides=strides
self.residual_path=residual_path
self.c1=Conv2D(filters,(3,3),strides=strides,padding='same',use_bias=False)
self.b1=BatchNormalization()
self.a1=Activation('relu')
self.c2=Conv2D(filters,(3,3),strides=strides,padding='same',use_bias=False)
self.b2 = BatchNormalization()
# residual_path为True时,对输入进行下采样,即用1x1的卷积核做卷积操作,保证x能和F(x)维度相同,顺利相加
if residual_path:
self.down_c1 = Conv2D(filters, (1, 1), strides=strides, padding='same', use_bias=False)
self.down_b1 = BatchNormalization()
self.a2 = Activation('relu')
def call(self,x):
residual=x # residual等于输入值本身,即residual=x
# 将输入通过卷积、BN层、激活层,计算F(x)
x = self.c1(inputs)
x = self.b1(x)
x = self.a1(x)
x = self.c2(x)
y = self.b2(x)
if self.residual_path:
residual = self.down_c1(x)
residual = self.down_b1(residual)
out = self.a2(y + residual) # 最后输出的是两部分的和,即F(x)+x或F(x)+Wx,再过激活函数
return out
构建出 ResNet 模型
class MyResnet18(Model):
def __init__(self, block_list, initial_filters=64): # block_list表示每个block有几个卷积层
super(MyResnet18, self).__init__()
self.num_blocks = len(block_list) # 共有几个block
self.block_list = block_list
self.out_filters = initial_filters
# 先是一个3*3的卷积
self.c1 = Conv2D(self.out_filters, (3, 3), strides=1, padding='same', use_bias=False)
self.b1 = BatchNormalization()
self.a1 = Activation('relu')
self.blocks = tf.keras.models.Sequential()
# 构建ResNet网络结构
for block_id in range(len(block_list)): # 第几个resnet block
for layer_id in range(block_list[block_id]): # 第几个卷积层
if block_id != 0 and layer_id == 0: # 对除第一个block以外的每个block的输入进行下采样
block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
else:
block = ResnetBlock(self.out_filters, residual_path=False)
self.blocks.add(block) # 将构建好的block加入resnet
self.out_filters *= 2 # 下一个block的卷积核数是上一个block的2倍
self.p1 = tf.keras.layers.GlobalAveragePooling2D()
self.f1 = tf.keras.layers.Dense(10, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())
def call(self, x):
x = self.c1(x)
x = self.b1(x)
x = self.a1(x)
x = self.blocks(x)
x = self.p1(x)
y = self.f1(x)
return y
model = MyResnet18([2, 2, 2, 2])
x = self.a1(x)
x = self.blocks(x)
x = self.p1(x)
y = self.f1(x)
return y
model = MyResnet18([2, 2, 2, 2])
文章出处登录后可见!