Residual Net and its variation

本文深入探讨了残差网络(ResNet)的设计动机,即为了解决深度网络中的退化问题,通过引入残差块简化优化过程。文章详细解析了残差块的结构与公式,展示了其在网络构建及卷积神经网络中的应用。并通过对比不同的残差单元,分析了后激活与前激活的优劣,强调了保持快捷连接纯净的重要性。
摘要由CSDN通过智能技术生成

Motivation

1. degradation problem

随着网络的加深,有可能观察到模型performance的下降。下降的原因可能并不是overfitting,而是训练更深的网络比较困难:
如果让新增加的layers均为identity mapping,那么更深的网络应该至少保持和以前一样的performance。但是实际上,让模型学到这个方案很困难。

2.solution

由上面的例子可以知道,即使identity mapping对最后的performance很重要,模型也很难通过优化学习到它,这个问题增加了优化的难度。如果额外增加一个shortcut connection,让模型直接拥有identity mapping的能力,就可以降低模型的优化难度。
在加入了shortcut connection之后,模型只需要根据输入的信息,学习相对于输入而言的剩余信息即可,而不用从离最优点很远的地方开始优化。相当于加入了先验信息,缩小了参数的搜寻空间,降低了模型困在局部最优的可能性。

Residual block

1.structure

‘Deep Residual Learning for Image Recognition’
注意:这里先把identity mapping的结果和residual block的输出加起来,再输入非线性激活函数,是residual中的post-activation方式,后面会展开介绍。

2.formular

x ^ l + 1 = h ( x l ) + F ( x l , W l ) \hat{x}^{l+1}=h(x^l)+F(x^l,W_l) x^l+1=h(xl)+F(xl,Wl)

x l + 1 = f ( x ^ l + 1 ) x^{l+1}=f(\hat{x}^{l+1}) xl+1=f(x^l+1)

h ( . ) : h(.): h(.): 提供现成的identity mapping,也就是上图灰色箭头的位置。
h ( F ( x l , W l ) ) : h(F(x^l,W_l)): h(F(xl,Wl)): residual path上的mapping,可以为几层叠加的非线性layers,是需要优化的部分。
f ( . ) : f(.): f(.): activation function, 此处为Relu.

Application

1. residual block

在这里插入图片描述
可以简单地通过叠加不同大小的residual block来创建网络。residual path上的结构可以根据task和data set的特点来自由选取,也可加入BN来稳定模型。

2. CNN

由于pooling或者stride,有可能shortcut路径的输出和identity路径的输出大小和深度不一致,此时有两种解决方法:

  • 按固定stride做zero-padding。
  • 加一个transformation,可以是stride>1的convolutional layer,也可以是线性的projection layer。

来使这两个feature map维度一致,之后element-wise相加即可。

Variation: post-activation & pre-activation

1. introduction

根据identity path和residual path上的layers的种类和相对顺序的不同,residual unit可以分为很多种。到底哪一种拥有最好的performance,使模型能更稳定,收敛速度更快?这需要详细分析residual unit到底如何帮助模型更好地优化。

2. derivation

由之前的章节可知,residual block的transformation如下:

x ^ l + 1 = h ( x l ) + F ( x l , W l ) \hat{x}^{l+1}=h(x^l)+F(x^l,W_l) x^l+1=h(xl)+F(xl,Wl)

x l + 1 = f ( x ^ l + 1 ) x^{l+1}=f(\hat{x}^{l+1}) xl+1=f(x^l+1)

x l : x^l: xl:residual block的输入。
x l + 1 : x^{l+1}: xl+1residual block的输出。
h ( . ) : h(.): h(.): identity path上的mapping。
h ( F ( x l , W l ) ) : h(F(x^l,W_l)): h(F(xl,Wl)): residual path上的mapping。
f ( . ) : f(.): f(.): addition之后的activation function.

现在假设 f ( . ) f(.) f(.)也是identity mapping,则可以得到:

x L = x L − 1 + F ( x L − 1 , W L − 1 ) x^L=x^{L-1}+F(x^{L-1}, W_{L-1}) xL=xL1+F(xL1,WL1)

x L − 1 = x L − 2 + F ( x L − 2 , W L − 2 ) x^{L-1}=x^{L-2}+F(x^{L-2}, W_{L-2}) xL1=xL2+F(xL2,WL2)
. . . ... ...
代入可得:

x L = x l + ∑ i = l L − 1 F ( x i , W i ) x^L=x^{l}+\sum_{i=l}^{L-1} F(x^{i}, W_{i}) xL=xl+i=lL1F(xi,Wi)

那么反向传播时,根据chain rule可知,任意一层的gradient为:

∂ L o s s ∂ x l = ∂ L o s s ∂ x L ∂ x L ∂ x l = ∂ L o s s ∂ x L ∗ ( 1 + ∂ ∑ i = l L F ( x i , W i ) ∂ x l ) \frac{\partial Loss}{\partial x^l}=\frac{\partial Loss}{\partial x^L}\frac{\partial x^L}{\partial x^l}=\frac{\partial Loss}{\partial x^L}*(1+\frac{\partial \sum_{i=l}^{L} F(x^{i}, W_{i})}{\partial x^l}) xlLoss=xLLossxlxL=xLLoss(1+xli=lLF(xi,Wi))

括号中的1表明,最后一层神经元的gradient可以通过identity path直接传到之前的任何一层。括号中的第二项对应了gradient在residual path上的传播。
由此可知,由于有了shortcut connection,gradient flow变得更加稳定,不会出现梯度消失的问题。而且由于层与层之间的梯度用于相加,减少了由于反复乘以同一weight matrix而产生的梯度爆炸的可能性。
也就是说,通过假设 h ( . ) h(.) h(.) f ( . ) f(.) f(.)是identity mapping,让shortcut connection更加clean,可以创建出一个供信息直接正反向传播的path,而通过上面的推导和分析可以看出,这种信息的直接传播有利于模型的稳定和收敛。

3. comparison

如果 h ( . ) h(.) h(.)是identity mapping的假设不成立,即使只是一个线性变化:
h ( x ) = λ x h(x)=\lambda x h(x)=λx
则推导出的层与层间的关系如下:
x L = ∏ i = l L − 1 λ i x l + ∑ i = l L − 1 F ( x i , W i ) x^L=\prod_{i=l}^{L-1}\lambda_i x^{l}+\sum_{i=l}^{L-1} F(x^{i}, W_{i}) xL=i=lL1λixl+i=lL1F(xi,Wi)

∂ L o s s ∂ x l = ∂ L o s s ∂ x L ∗ ( ∏ i = l L − 1 λ i + ∂ ∑ i = l L F ( x i , W i ) ∂ x l ) \frac{\partial Loss}{\partial x^l}=\frac{\partial Loss}{\partial x^L}*(\prod_{i=l}^{L-1}\lambda_i+\frac{\partial \sum_{i=l}^{L} F(x^{i}, W_{i})}{\partial x^l}) xlLoss=xLLoss(i=lL1λi+xli=lLF(xi,Wi))

显然,当层数很深时 ∏ i = l L − 1 λ i \prod_{i=l}^{L-1}\lambda_i i=lL1λi 的值会不稳定,爆炸/消失。

如果 f ( . ) f(.) f(.)是identity mapping的假设不成立,也会带来类似的效果。
总结来说,保持shortcut path的clean可以最大程度上利用residual block的优势,让信息更好地正反向传播,模型拥有强的identity mapping的能力,从而降低optimization的难度。

4. conclusion

在这里插入图片描述
那么在图示的5种residual block中,后两种显然是比较理想的结构。他们之间的不同是ReLU和BN的位置关系。可以看到(d)图中的residual block在正向传播时,第一个ReLU的输入没有经过BN处理,这也许是它提升效果不明显的原因。

Analysis

图(e)中的residual block能明显优化模型的performance:

  1. identity path为正向的信息传播和反向的gradient传播均提供了高效的途径,cleaner的shortcut connection有能力降低模型的优化难度,稳定训练过程,达到更好的效果。
  2. residual path里面加入了BN layer,也能帮助模型更快收敛,使weights稳定更新,并且加入了正则化效果。

Reference

https://arxiv.org/abs/1512.03385
https://arxiv.org/abs/1603.05027

Deep Residual U-Net是一种基于U-Net和ResNet的图像分割网络。它采用了U-Net的编码器-解码器结构,同时在每个解码器块中使用了ResNet的残差块(DR块)来提高特征提取能力。DR块通过引入SE(Squeeze-and-Excitation)机制来增强编码器的全局特征提取能力,同时使用1×1卷积来改变特征图的维度,以确保3×3卷积滤波器不受前一层的影响。此外,为了避免网络太深的影响,在两组Conv 1×1-Conv 3×3操作之间引入了一条捷径,允许网络跳过可能导致性能下降的层,并将原始特征转移到更深的层。Deep Residual U-Net在多个图像分割任务中都取得了优秀的性能。 以下是Deep Residual U-Net的编码器-解码器结构示意图: ```python import torch.nn as nn class DRBlock(nn.Module): def __init__(self, in_channels, out_channels): super(DRBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(out_channels, out_channels, kernel_size=1) self.relu = nn.ReLU(inplace=True) self.se = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(out_channels, out_channels // 16, kernel_size=1), nn.ReLU(inplace=True), nn.Conv2d(out_channels // 16, out_channels, kernel_size=1), nn.Sigmoid() ) def forward(self, x): identity = x out = self.conv1(x) out = self.relu(out) out = self.conv2(out) out = self.relu(out) out = self.conv3(out) out = self.se(out) * out out += identity out = self.relu(out) return out class DRUNet(nn.Module): def __init__(self, in_channels, out_channels, init_features=32): super(DRUNet, self).__init__() features = init_features self.encoder1 = nn.Sequential( nn.Conv2d(in_channels, features, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(features, features, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) self.encoder2 = nn.Sequential( DRBlock(features, features * 2), nn.Conv2d(features * 2, features * 2, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 2, features * 2), nn.Conv2d(features * 2, features * 2, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) self.encoder3 = nn.Sequential( DRBlock(features * 2, features * 4), nn.Conv2d(features * 4, features * 4, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 4, features * 4), nn.Conv2d(features * 4, features * 4, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2) self.encoder4 = nn.Sequential( DRBlock(features * 4, features * 8), nn.Conv2d(features * 8, features * 8, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 8, features * 8), nn.Conv2d(features * 8, features * 8, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.pool4 = nn.MaxPool2d(kernel_size=2, stride=2) self.bottleneck = nn.Sequential( DRBlock(features * 8, features * 16), nn.Conv2d(features * 16, features * 16, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 16, features * 16), nn.Conv2d(features * 16, features * 16, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.upconv4 = nn.ConvTranspose2d(features * 16, features * 8, kernel_size=2, stride=2) self.decoder4 = nn.Sequential( DRBlock(features * 16, features * 8), nn.Conv2d(features * 8, features * 8, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 8, features * 8), nn.Conv2d(features * 8, features * 8, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.upconv3 = nn.ConvTranspose2d(features * 8, features * 4, kernel_size=2, stride=2) self.decoder3 = nn.Sequential( DRBlock(features * 8, features * 4), nn.Conv2d(features * 4, features * 4, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 4, features * 4), nn.Conv2d(features * 4, features * 4, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.upconv2 = nn.ConvTranspose2d(features * 4, features * 2, kernel_size=2, stride=2) self.decoder2 = nn.Sequential( DRBlock(features * 4, features * 2), nn.Conv2d(features * 2, features * 2, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features * 2, features * 2), nn.Conv2d(features * 2, features * 2, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.upconv1 = nn.ConvTranspose2d(features * 2, features, kernel_size=2, stride=2) self.decoder1 = nn.Sequential( DRBlock(features * 2, features), nn.Conv2d(features, features, kernel_size=3, padding=1), nn.ReLU(inplace=True), DRBlock(features, features), nn.Conv2d(features, features, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) self.conv = nn.Conv2d(features, out_channels, kernel_size=1) def forward(self, x): enc1 = self.encoder1(x) enc2 = self.encoder2(self.pool1(enc1)) enc3 = self.encoder3(self.pool2(enc2)) enc4 = self.encoder4(self.pool3(enc3)) bottleneck = self.bottleneck(self.pool4(enc4)) dec4 = self.upconv4(bottleneck) dec4 = torch.cat((enc4, dec4), dim=1) dec4 = self.decoder4(dec4) dec3 = self.upconv3(dec4) dec3 = torch.cat((enc3, dec3), dim=1) dec3 = self.decoder3(dec3) dec2 = self.upconv2(dec3) dec2 = torch.cat((enc2, dec2), dim=1) dec2 = self.decoder2(dec2) dec1 = self.upconv1(dec2) dec1 = torch.cat((enc1, dec1), dim=1) dec1 = self.decoder1(dec1) return self.conv(dec1) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值