目录
论文阅读
代码解析
小结
论文阅读
1.介绍
在Resnet V1的论文中介绍的‘Residual Units'可以用公式表示如下:
表示这个unit的输入,表示这个unit的输出,是残差函数,resnet V1的论文中建议,是RELU。
是一个很关键的选择,能让网络表现不错的效果。
在这篇文章中,作者会研究信息传播的路径对结果的影响。如果和都是等式映射,那么信号会直接从一个单元传播给另一个单元。作者的经验发现如果和越接近等式,那么网络训练起来会更加容易。
作者分析和比较了各种形式的,比如缩放,比如1x1的卷积计算等,都没有收敛速度快,loss值降得更低。这个实验表明更干净的信息传递通道可以使网络更容易接近最优值。
而为了构建的结构,作者将激活函数调整了位置,使用先激活,而不是之前的后激活,这样就得到了一种新的残差网络结构,而且这个结构取得了不错的成绩。
(a)是原来的残差结构,(b)是新的残差结构。可以看到改进后是先进行BN和RELU计算,然后再用weight进行计算。
2.分析深度残差网络
对于原始的残差单元,如果,那么,那么公式可以变成
将所有的的值都带入,可以得到
这个公式显示了一些属性:
- 任意深度的都可以由加上一个残差公式组成,所以就意味着和之间就有残差关系
- 公式也成立,这表明可以表示为前面所有层的残差公式算出来的值相加,再加上
根据公式4,我们进行反向传播求导
对的求导可以分成两个公式,一个是,这部分跟参数w没有关系,另外一个式子是,这个式子跟参数w有关系。所以信息能够反向传播到任意浅层的单元。
另外公式5也表明,因为不可能永远为-1,所以对的求偏导不可能永远为0,所以mini batch也不会出现梯度消失的现象。
讨论
公式4和公式5表明,无论是正向传播还是反向传播,信号可以直接从任意一层传播到另一层。要公式4成立需要满足两个条件,一个是,另一个是。在上面的残差结构示意图中,用灰色通道表示这部分。如果h和f都是等式,那么灰色部分就会很干净。
3.等式连接的重要性
现在来考虑一下,假设,仍然是等式,那么公式3可以变成
这样可以推导出公式
次公式可以简化为
对求偏导可以得到公式
跟公式5非常像,只是第一个相加的式子前面需要乘以,对于非常深的网络,如果对所有 > 1,那么这个元素会变得很大;如果对所有 < 1,那么这个元素会非常小并且消失掉。这样会阻碍反向传播中信息的传递,我们实验也发现网络会更难训练。
如果是更复杂的公式,相当于前面相乘的因子是,同样的会影响信息的传播。
作者试验了以下几种结构
图(a)就是resnet v1中提到的结构,一共有110层,有54个残差单元,每个残差单元有两层3 x 3的卷积层。现在让都为RELU,比较一下和不同带来的影响。
图(b)是做一个常数的缩放,=0.5。作者做了两种尝试,一种是不做缩放,一种是做的缩放。结果测试集错误率有12.35%,比原始结构图a显示的还要高。
图(c)叫做exclusive gating,,其中。让做的缩放,让做的缩放。这种结构对的初始化要求比较高,测试下来最好的情况测试集错误率是8.7%,结果并不是太理想。因为如果接近1,的shortcut路径更加接近于等式,有利于信息的传播,但是这种情况下接近0,抑制了通道信息的传播。
图(d)针对图(c)引发的思考,考虑只对的shortcut做的控制,对不做控制。这种情况下的选择也很重要,如果为0则接近0.5,训练结果是测试集错误率高达12.86%。如果选择-6,那么接近于1,测试集错误率是6.91%,这是因为接近于1就近似shortcut是等式。
图(e)是使用1x1的卷积计算来替代的等式,实验发现如果只有16个残差单元效果还不错,但是如果残差单元增加到54个,错误率会升高到12.22%。
图(f)是在shortcut的path上使用比例为0.5的dropout,训练没有成功,0.5的dropout类似于0.5的缩放的效果,影响了信息的传播。
以上试验都可以证明在shortcut path上增加任何处理都会影响最后的结果,只有用等式效果会最好。
4.使用激活函数
在上面讨论的公式5和公式8中是假设了激活函数为等式,但是实际上我们的激活函数一般为RELU,下面就来研究一下激活函数的影响。我们想让为等式,就需要重新调整RELU和BN等的顺序。
图(a)是原始的结构,为RELU
图(b)是相加后再进行BN和RELU,此时表示BN和RELU。可以预见这种结果效果会更差,因为BN和RELU阻碍了信息的传递,试验结果也确实如此,loss在训练开始阶段下降很慢,最后的结果也不如原始的结构。
图(c)是在相加之前做RELU。一个很简单的让变成等式的方法就是将RELU移到相加操作的前面,但是这会导致之后输出的值永远是非负数,直觉上残差函数输出的值应该在(-,+)之间。这会导致正向传播的信息单调增加,影响了表达能力,训练结果是错误率7.84%也确实比原始的错误率要高。
是选择前激活还是后激活呢?
对于上面的公式1和公式2,由于上一节的讨论,我们现在就认为,所以
现在假设是一种非对称的形式,就是前面一个,后面一个中的改写为,那么上面的公式可以变成
现在将换成来表达,就可以得到下面的公式(从上面的推导过程看论文中应该有错误,是)
现在这个公式9和公式4很像,这个公式表明我们可以应用一种非对称激活函数,在shortcut部分是等式,在残差函数部分是先进行激活函数,在与权重w进行计算。所以我们现在就要尝试使用前激活的方式,有下面两种方式。
图(d)表示调整RELU的位置进行前激活。这种结构的表现和原始的结构类似,可能是因为激活函数之前没有享受到BN带来的好处。
图(e)表示调整RELU和BN的位置进行前激活。这种结构会有比较明显的效果提升。
使用图(e)结构带来的好处是双重的:
更容易优化:这个影响在训练1001层的resnet的时候特别明显,使用原始的结构,training error在训练之初下降的非常慢,因为如果相加上进行RELU激活对传递过来的负数信息是有影响的。而如果是等式,信息可以直接从一个单元传递到另一个单元,1001层的resnet loss下降的非常快。另外对于层数少一些的resnet比如164层,为relu似乎对性能的影响很小。
降低过拟合:将BN放入RELU的前面可以带来正则化的效果,降低过拟合,会得到更高的精度。
5.训练结果
略
代码分析
代码地址:resnet_v2
resnet_v2代码的实现和resnet_v1基本都是类似的,只是在bottleneck函数的实现上稍有差别,因为bottleneck函数就代表了一个residual block。整体代码架构不清楚的可以阅读之前的一篇博文地址。在本文中我想只需要比较一下两个网络bottleneck的实现就可以很清晰的看出resnet v2的改进了。
Resnet V1
@slim.add_arg_scope
def bottleneck(inputs,
depth,
depth_bottleneck,
stride,
rate=1,
outputs_collections=None,
scope=None,
use_bounded_activations=False):
...
residual = slim.conv2d(inputs, depth_bottleneck, [1, 1], stride=1,
scope='conv1')
residual = resnet_utils.conv2d_same(residual, depth_bottleneck, 3, stride,
rate=rate, scope='conv2')
residual = slim.conv2d(residual, depth, [1, 1], stride=1,
activation_fn=None, scope='conv3')
if use_bounded_activations:
# Use clip_by_value to simulate bandpass activation.
residual = tf.clip_by_value(residual, -6.0, 6.0)
output = tf.nn.relu6(shortcut + residual)
else:
output = tf.nn.relu(shortcut + residual)
...
从可以可以很清楚的看到是先做了卷积运算(BN和RELU包含在slim.conv2d中实现,是先卷积,然后BN,最后RELU),在相加后再做了RELU计算。
Resnet V2
@slim.add_arg_scope
def bottleneck(inputs, depth, depth_bottleneck, stride, rate=1,
outputs_collections=None, scope=None):
...
preact = slim.batch_norm(inputs, activation_fn=tf.nn.relu, scope='preact')
...
residual = slim.conv2d(preact, depth_bottleneck, [1, 1], stride=1,
scope='conv1')
residual = resnet_utils.conv2d_same(residual, depth_bottleneck, 3, stride,
rate=rate, scope='conv2')
residual = slim.conv2d(residual, depth, [1, 1], stride=1,
normalizer_fn=None, activation_fn=None,
scope='conv3')
output = shortcut + residual
代码中可以看出是先调用了slim.batch_norm进行了BN和RELU,然后是正常的卷积,在相加后没有做任何计算了。
小结
Resnet V2的论文写的非常清晰易懂,公式也不复杂,基本是猜想加上实验的方式得出的结论,是一篇很容易阅读的论文。提出的二代残差结构对训练非常深的网络,比如1001层,会有很好的效果。
以上为本文所有内容,感谢阅读,欢迎留言。