残差网络(Residual Network, ResNet)是在2015年继AlexNet、VGG、GoogleNet 三个经典的CNN网络之后提出的,并在ImageNet比赛classification任务上拔得头筹,ResNet因其简单又实用的优点,现已在检测,分割,识别等领域被广泛的应用。
在VGG19中卷积层+全连接层达到19层,在GoogLeNet中网络史无前例的达到了22层。那么,网络的精度会随着网络的层数增多而增多吗?在深度学习中,网络层数增多一般会伴着下面几个问题:
- 计算量大,时间长(通过GPU集群来解决)
- 过拟合(增加数据量、Dropout正则化等)
- 梯度消失/梯度爆炸(BN)
ResNet作者发现随着网络层数的增加,网络发生了退化现象(degradation):随着网络层数的增多,训练集loss逐渐下降,然后趋于饱和,当你再增加网络深度的话,训练集loss反而会增大。注意这并不是过拟合,因为在过拟合中训练loss是一直减小的。ResNet有效的解决了退化现象。
残差块与残差网络
直接看代码吧:
先定义一个残差块:
def res_block_v1(x, input_filter, output_filter):
res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding='same')(x)
res_x = BatchNormalization()(res_x)
res_x = Activation('relu')(res_x)
res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding='same')(res_x)
res_x = BatchNormalization()(res_x)
if input_filter == output_filter:
identity = x
else: #需要升维或者降维
identity = Conv2D(kernel_size=(1,1), filters=output_filter, strides=1, padding='same')(x)
x = keras.layers.add([identity, res_x])
output = Activation('relu')(x)
return output
按照代码画出残差块的结构图:
x
l
+
1
=
x
ˉ
l
+
B
N
(
C
o
n
v
2
D
(
r
e
l
u
(
B
N
(
C
o
n
v
2
D
(
x
l
)
)
)
)
)
x_{l+1}=\bar x_l + BN(Conv2D(relu(BN(Conv2D(x_l)))))
xl+1=xˉl+BN(Conv2D(relu(BN(Conv2D(xl)))))
再构建残差网络:
def resnet_v1(x):
x = Conv2D(kernel_size=(3,3), filters=16, strides=1, padding='same', activation='relu')(x)
x = res_block_v1(x, 16, 16)
x = res_block_v1(x, 16, 32)
x = Flatten()(x)
outputs = Dense(10, activation='softmax', kernel_initializer='he_normal')(x)
return outputs
从代码中可以看到这里实现的残差网络就是残差块的简单堆叠!
抽象一下:
残差网络是由一系列残差块组成的,一个残差块可以用表示为:
x
l
+
1
=
x
ˉ
l
+
F
(
x
l
,
W
l
)
x_{l+1} = \bar x_l + F(x_l,W_l)
xl+1=xˉl+F(xl,Wl)
由上面的公式可以看出,残差块由两部分组成直接隐射部分(
x
ˉ
l
\bar x_l
xˉl)和残差部分
F
(
x
l
,
W
l
)
F(x_l,W_l)
F(xl,Wl)。
x
ˉ
l
\bar x_l
xˉl是由
x
l
x_l
xl线性变换得到:
当input_filter == output_filter时,
x
ˉ
l
=
x
l
\bar x_l=x_l
xˉl=xl;
当input_filter != output_filter时,代码中是一个1*1的卷积,本质就是
w
i
∗
x
l
w_i*x_l
wi∗xl也就是一个权重参数乘以所有的特征值得到一个feature map,一共得到output_filter个feature map。作用是用来进行升维或降维用。
残差块为什么叫”残差“块?
先来厘清下残差与误差的区别
从残差块的代码中可以看出,残差块就是一个小型网络。残差块的输入是
x
ˉ
l
\bar x_l
xˉl——观测值,输出是
x
l
+
1
x_{l+1}
xl+1——预测值,预测值-观测值=
x
l
+
1
−
x
ˉ
l
=
F
(
x
l
,
W
l
)
x_{l+1}-\bar x_l=F(x_l,W_l)
xl+1−xˉl=F(xl,Wl),所以
F
(
x
l
,
W
l
)
F(x_l,W_l)
F(xl,Wl)就表示残差。
残差网络为什么叫”残差“网络?
因为网络的第
l
+
1
l+1
l+1块的输入,比第
l
l
l块的输入多了残差!
如果我们跳跃多层来看,第
l
+
k
l+k
l+k块比第
l
l
l块多了什么:
x
l
+
k
=
x
ˉ
l
+
∑
i
=
1
k
F
(
x
i
,
W
i
)
x_{l+k}=\bar x_l+\sum_{i=1}^kF(x_i,W_i)
xl+k=xˉl+i=1∑kF(xi,Wi)
可以看出第
l
+
k
l+k
l+k块比第
l
l
l块多的
k
k
k个块的残差,即
∑
i
=
1
k
F
(
x
i
,
W
i
)
\sum_{i=1}^kF(x_i,W_i)
∑i=1kF(xi,Wi)。这也反映了残差网络为什么不仅能解决退化现象而且还能比浅层网络表达能力更强:深层网络使用了浅层网络的特征加之额外的辅助信息。
残差网络构建的过程:
- 先构建一个plain的网络;
- 在plain网络的卷积之间插入IdentityMapping;
来看ResNet作者设计的34层ResNet结构与VGG网络结构的对比(参加比赛使用的网络达到了152层):
图中实线的箭头是没有维度变化的直接映射(即input_filter == output_filter),虚线是有维度变化的映射(即input_filter != output_filter)。
左侧是普通网络,右侧是残差网络,青色线是18层网络,红色线是34层网络。可见残差网络不仅解决了退货问题而且使得表现地更好。
残差块构建的不同,决定了残差网络地类型。
普通残差网络
即上面代码残差块所对应的残差网络:
Deeper Bottleneck Architectures
Deeper Bottleneck Architectures残差网络在50层以上,作者为了降低训练时间而设计的。
参考文章:
https://zhuanlan.zhihu.com/p/42706477
https://www.jianshu.com/p/18593ee1f220