Resnet家族(持续更新中,4.15)

《Deep Residual Learning for Image Recognition》论文阅读笔记

0.基础知识:

0.1残差:
假设真实模型为:

=

真实模型的含义是,变量X通过线性形式来影响Y,但是始终存在随机波动,那么我们用u来表示这样的随机波动,或者称为随机误差。通过输入一系类x可以得到一些列y,这样就得到的一、

![=](https://img-blog.csdnimg.cn/img_convert/8d46de48fcba401e53f1461462b1c64b.png)
那么此时,Y的估计值和Y之间的差称为残差。残差是对随机误差的估计。 我感觉resnet叫做残差比较形象,深层会接受到来自浅层的残差来模拟波动,更加拟合数据,同时防止网络退化导致梯度消失或者爆炸。 ##1.残差模块 为了强调原始信息,就引入了人工的残差来低效/模拟因为人工神经网络在传播中的噪声/损失/波动,他这个人工残差的产生比较简单,就是需要一层卷积,何凯明在实验中发现两层卷积和三层卷积是比较合适的,**可不可以基于明神的残差模块,改进一下,因为这个卷积又是黑盒子,可以人工设计个残差模块,就像之前那个自注意力机制一样**。

0.2恒等映射: 对于映射f,若它的定义域A和值域B相等,并对所有的均有时,则称f为恒等映射。

0.3FLOPsfloating point operations的缩写(s表复数): 意指浮点运算数,理解为计算量。可以用来衡量算法/模型的复杂度。
torch支持计算复杂度

1.残差模块

残差模块就是来收集浅层信息,通过卷积来计算残差,反向传播来修正卷积核。原文是:the added layers are identity mapping, and the other layers are copied from the learned shallower(作者把残差产生并融合的过程恒等映射,残差就是提取特征浅层的信息),添加的层是恒等映射,其他层是从学习到的较浅模型的拷贝。 这种构造解决方案的存在表明,较深的模型不应该产生比其对应的较浅模型更高的训练误差。残差本质是y=x+f(x),激活后f(x)>=0,在求梯度时为1+f’(x),保证梯度始终在1附近,这样两式一起就保证了“就算没学到有用的东西,也不会产生不利的信息,避免了梯度消失”

在这里插入图片描述

明神把这个过程抽象化之后-----最常见状态:y = F(x,{Wi}) + x,前项就是残差,后向就是恒等映射,在反向传播求导的时候,很漂亮1+f’(x)
当传递的时候需要改变大小的时候,因为特征图谱在传递的时候缩放了,所以残差也要跟着做改变:y = F(x,{Wi}) + Wx.下图的实线就是第一个映射,虚线就是第二个映射,作者把这种映射叫做shoutcut
当维度增加(图3中的虚线快捷连接)时,我们考虑两个选项:(A)快捷连接仍然执行恒等映射,额外填充零输入以增加维度。此选项不会引入额外的参数;(B)方程(2)中的投影快捷连接用于匹配维度(由1×1卷积完成)。对于这两个选项,当快捷连接跨越两种尺寸的特征图时,它们执行时步长为2。
左边的是vgg。中间的是何凯明模仿vgg的结构做的34层vgg,最后是在残差模块增幅下的vgg–resnet
在这里插入图片描述

在这里插入图片描述

本文中的实验包括有两层或三层的残差模块/映射模块,也可以是很深层的。但如果映射只有一层,类似于线性层:y=Wx+x,在实验中没有任何优势。何凯明把2层卷积的叫做BasicBlock,三层的叫做Bottleneck

2.代码学习

def resnet18(pretrained=False, hr_pretrained=False, **kwargs):
    """Constructs a ResNet-18 model.
    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
        *args称之为Non-keyword Variable Arguments,无关键字参数;
		**kwargs称之为keyword Variable Arguments,有关键字参数;
    """
    model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
    if pretrained:
        # strict = False as we don't need fc layer params.
        if hr_pretrained:
            print('Loading the high resolution pretrained model ...')
            model.load_state_dict(torch.load("backbone/weights/resnet18_hr_10.pth"), strict=False)
        else:
            model.load_state_dict(model_zoo.load_url(model_urls['resnet18']), strict=False)
    return model

需要传三组组参数,block:指明残差块是BasicBlock还是bottleblock的,layers:传给_make_layer用的,zero_init_residual是否要用0来初始化残差,不用的就用何凯明正态来初始化

class ResNet(nn.Module):

    def __init__(self, block, layers, zero_init_residual=False):
        super(ResNet, self).__init__()
        self.inplanes = 64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
                               bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)

        for m in self.modules():  #这就话就是把模块的每一层进行判别
            if isinstance(m, nn.Conv2d): #如果这一层是二维卷积层,就用何凯明的正态分布初始化
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):#BatchNorm2d归一化也是针对与relu的,数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定
                nn.init.constant_(m.weight, 1) #对归一化的权重和偏执做初始化
                nn.init.constant_(m.bias, 0)

        # Zero-initialize the last BN in each residual branch,
        # so that the residual branch starts with zeros, and each residual block behaves like an identity.
        # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
        if zero_init_residual:
            for m in self.modules():
                if isinstance(m, Bottleneck):
                    nn.init.constant_(m.bn3.weight, 0)
                elif isinstance(m, BasicBlock):
                    nn.init.constant_(m.bn2.weight, 0)

        # torch.nn.init.constant_(tensor, val)[source] 用值val填充向量。



    def forward(self, x):
        C_1 = self.conv1(x)
        C_1 = self.bn1(C_1)
        C_1 = self.relu(C_1)
        C_1 = self.maxpool(C_1)

        C_2 = self.layer1(C_1)
        C_3 = self.layer2(C_2)
        C_4 = self.layer3(C_3)
        C_5 = self.layer4(C_4)

        return C_3, C_4, C_5

凯明正态化: 凯明正态化

def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
 # a这个函数就是分化残差模块用的,有的残差要提前进行下采样,然后再插进去       
 # a即如果该层的输入的通道数inplanes和其输出的通道数的数planes * block.expansion不同,
 # a那要使用1*1的卷积核将输入x低维转成高维进行下采样,相当于之前函数里的w,然后才能进行相加
        
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes, planes * block.expansion, stride),
                nn.BatchNorm2d(planes * block.expansion),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        for _ in range(1, blocks):
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)  

block参数指定是两层残差块或三层残差块,planes参数为输入的channel数,blocks说明该卷积有几个残差块

在这里插入图片描述

《Resnet v2:Identity Mappings in Deep Residual Networks》阅读笔记

作者认为在主路传递的时候尽量要简单一点(keeping a “clean” information path ),因为这样有利于反向传播,通过实验不同的残差模块发现,在浅层网络中并没什么多大卵用,但在深层网络中由于原始的resnet有些拉跨,而v2没有发散收敛的很好,所以残差的计算和传递还是有很大的改进空间的,不能拍脑袋想,多看看数值分析看看能不能算出数值解来。

1.恒等映射

这篇论文的思路就是要把灰色通道弄得整洁明了,所以何凯明想要用恒等映射来取代relu函数(这里的恒等映射,何凯明直接当f(x)=x来用了,因为在传线性参数时候可能会导致梯度爆炸/小时),而且恒等映射后产生的信息可以从一个单元传播到任何其他单元(没有证明,也没法证明)。

在这里插入图片描述

1.1反向传播

恒等变换的优势就是反向传播比较简洁,原始的传递是:
y l = h ( x l ) + F ( x l , W l ) , x l + 1 = f ( y l ) . {y}_{l} = h({x}_{l}) + \mathcal{F}({x}_{l}, \mathcal{W}_l), \\ {x}_{l+1} = f({y}_{l}) . yl=h(xl)+F(xl,Wl),xl+1=f(yl).
在其中的 x l {x}_{l} xl是输入 x l + 1 {x}_{l+1} xl+1是输出,所以而不是 y l {y}_{l} yl,要加一层relu f ( x ) f(x) f(x),而relu函数是一个非线性函数,在求反向传播的时候不是很方便,尽管在 x > x ’ x>x’ x>x时候是原函数。
现在把relu直接换成恒等映射 h ( x l ) = x l h({x}_{l}) = {x}_{l} h(xl)=xl,就有了新的残差传递函数了:
x l + 1 = x l + F ( x l , W l ) {x}_{l+1} = {x}_{l} + \mathcal{F}({x}_{l}, \mathcal{W}_{l}) xl+1=xl+F(xl,Wl)
可以递归,往深层传播,这就契合了之前说的一层可以传播的任意一层:
x L = x l + ∑ i = l L − 1 F ( x i , W i ) {x}_{L} = {x}_{l} + \sum_{i=l}^{L-1}\mathcal{F}({x}_{i}, \mathcal{W}_{i}) xL=xl+i=lL1F(xi,Wi)
这个函数展现了一些很有意思的事情:任意单元之间都可以算残差;然后对于任意深的单元他的计算都是分两部分–x和一个残差和。

求导得:
∂ E ∂ x l = ∂ E ∂ x L ∂ x L ∂ x l = ∂ E ∂ x L ( 1 + ∂ ∑ i = l L − 1 F ( x i , W i ) ∂ x l ) . \frac{\partial E}{\partial {{x}_{l}}}=\frac{\partial E}{\partial {{x}_{L}}}\frac{\partial {{x}_{L}}}{\partial {{x}_{l}}}=\frac{\partial E}{\partial {{x}_{L}}}\left(1+\frac{\partial {\sum_{i=l}^{L-1}\mathcal{F}({x}_{i}, \mathcal{W}_{i})}}{\partial {{x}_{l}}}\right). xlE=xLExlxL=xLE(1+xli=lL1F(xi,Wi)).
这个有意思的事情在求导后更有意思了:求导部分也是分成两部分 ∂ E ∂ x L \frac{\partial E}{\partial {{x}_{L}}} xLE直接传递信息而不涉及任何权重层,保证了信息能够直接传回任意浅层 .而另一部分 ∂ E ∂ x L ( ∂ ∑ i = l L − 1 F ∂ x l ) \frac{\partial E}{\partial {{x}_{L}}}\left(\frac{\partial {\sum_{i=l}^{L-1}\mathcal{F}}}{\partial {{x}_{l}}}\right) xLE(xli=lL1F)表示通过权重层的传递。
第一部分保证了梯度最起码是1,第二部分何凯明在实验的过程中几乎没遇到是-1的情况

1.2反证法

之前已知在强调灰色通道要尽量的干净一些,那么不干净会发生什么?
证明:
给输如加个权重:
x l + 1 = λ l x l + F ( x l , W l ) {x}_{l+1} = \lambda_l{x}_{l} + \mathcal{F}({x}_{l}, \mathcal{W}_{l}) xl+1=λlxl+F(xl,Wl)
再求和求导后得到:
∂ E ∂ x l = ∂ E ∂ x L ( ( ∏ i = l L − 1 λ i ) + ∂ ∑ i = l L − 1 F ^ ( x i , W i ) ∂ x l ) . \frac{\partial E}{\partial {{x}_{l}}}=\frac{\partial E}{\partial {{x}_{L}}}\left((\prod_{i=l}^{L-1}\lambda_{i})+\frac{\partial {\sum_{i=l}^{L-1}\mathcal{\hat{F}}({x}_{i}, \mathcal{W}_{i})}}{\partial {{x}_{l}}}\right). xlE=xLE((i=lL1λi)+xli=lL1F^(xi,Wi)).
这是这时候有意思的1被 ∏ i = l L − 1 λ i . \prod_{i=l}^{L-1}\lambda_{i}. i=lL1λi.替代之后就就极大的提高了梯度消失/爆炸的概率。如果对于所有的i都有 λi>1,那么这个因子将会是指数型的放大;如果 λi<1 ,那么这个因子将会是指数型的缩小或者是消失,从而阻断从捷径反向传来的信号,并迫使它流向权重层。
证毕

2.实验

何凯明基于clear的灰色通道,猜测了很多shotcut模块,几乎没有一个能打的。就之前说的简单的去掉relu的在深层中表现的比较好一点

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.分析

开始炼丹
对于每个图右侧部分我们称作“residual”分支,左侧部分我们称作“identity”分支,如果ReLU作为“residual”分支的结尾,我们不难发现“residual”分支的结果永远非负,这样前向的时候输入会单调递增,从而会影响特征的表达能力,所以我们希望“residual”分支的结果应该在(-∞, +∞);这点也是我们以后设计网络时所要注意的
ReLU before addition :这导致了 F的输出为非负
BN after addition:在将f调整至恒等映射之前,我们先反其道而行之,在加法后添加一个BN(Fig.4(b))。这样f 就包含了BN和ReLU。这样的结果比基本结构的结果要差很多(Table2)。不像原始的设计,目前的BN层改变了流经捷径连接的信号,并阻碍了信息的传递,这从训练一开始降低训练误差的困难就可以看出
Post-activation or pre-activation:他们的导数都是 x l + 1 = x l + F ( f ^ ( x l ) , W l ) {x}_{l+1} = {x}_{l} + \mathcal{F}(\hat{f}({x}_{l}), \mathcal{W}_{l}) xl+1=xl+F(f^(xl),Wl),和之前的一样,所以结果接近,轻微差距可能的这炉没练好,下一炉子可能好一点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值