神经网络学习笔记1——ResNet残差网络、Batch Normalization理解与代码


前言

一般印象中,越复杂的特征有着越强的表达特征能力。在深度网络中,各个特征会不断的经过线性非线性的综合计算,越深的网络输出表示能力越强的特征。所以网络的深度对于学习表达能力更强的特征至关重要,即神经网络结构越深(复杂,参数多)越是有着更强的表达能力。这一点在VGGNet中得到很好的体现。

深度模型中,每层的输出特征图的尺寸大都随着网络深度而变化,主要是长和宽越来越小,输出特征图的深度随着网络层数的深度而增加。从另一方面讲,长和宽的减小有助于减小计算量,而特征图深度的增加则使每层输出中可用的特征数量增多。

增加深度带来的首个问题就是 梯度爆炸/梯度消失 的问题。这是由于随着层数的增多,在网络中反向传播的梯度会随着连乘变得不稳定,变得特别大或特别小。这其中经常出现的是梯度消失的问题。在提出残差网络之前,网络结构无法很深,在VGG中,卷积网络达到了19层,在GoogLeNet中,网络达到了22层。随着网络层数的增加,网络发生了退化(degradation)的现象:随着网络层数的增多,训练集loss逐渐下降,然后趋于饱和,当你再增加网络深度的话,训练集loss反而会增大。

ResNet残差网络主要是通过残差块组成的,而引入残差块后,网络可以达到很深,网络的效果也随之变好。总而言之,ResNet网络是为了解决深度网络中的退化问题,即网络层数越深时,在数据集上表现的性能却越差的问题。


一、结构展示

1、ResNet18图形化:

2、ResNet表格化:

在这里插入图片描述

二、结构学习

参考来源

1.整体

根据resnet18的图形与表格中的18layer对比,进行文字解析:

  1. conv1:7x7x64的卷积
  2. conv2:3x3最大池化下采样层,两层两组3x3x64卷积堆叠
  3. conv3:两层两组3x3x128卷积堆叠
  4. conv4:两层两组3x3x256卷积堆叠
  5. conv5:两层两组3x3x512卷积堆叠
  6. 1+1+1:平均池化下采样层,全连接层,归一化处理

2.残差结构

残差结构部分前向传播的路径分为两条路来走:
第一条路卷积递进,是需要经过若干层卷积层
第二条路快捷连接,直接绕过第一条路与第一条路输出的特征图进行相加

扫描二维码关注公众号,回复: 15348147 查看本文章

在这里插入图片描述

不过不管是哪条路,在进入下一个残差结构第一层时,都需要调整尺寸和非线性处理,以实现输入输出的尺寸一致

1.以resnet18为例:

在这里插入图片描述

Conv(n)——>Conv(n+1)时(右边结构):
卷积递进的输入为[56,56,64]——>输出为[28,28,128]:

  1. 通过设置步长stride=2、3x3卷积和卷积核128,使得高宽从56缩减一半到28,卷积核从64个更改为128个。
  2. 激活函数relu处理。
  3. 通过设置步长stride=1、3x3卷积和卷积核128,重复特征提取。

快捷连接的输入为[56,56,64]——>[1x1x128]——>输出为[28,28,128]:

  1. 通过设置步长stride=2、1x1卷积和卷积核更改,使得高宽从56缩减一半到28,卷积核从64个更改为128个。
  2. 与卷积递进的输出值相加

非残差结构第一层时(左边结构):
卷积递进:

  1. 通过设置步长stride=1、3x3卷积和卷积核64,使得高宽与卷积核不变。
  2. 激活函数relu处理。
  3. 通过设置步长stride=1、3x3卷积和卷积核64,重复特征提取。

快捷连接:

  1. 输入值与卷积递进的输出值相加

在这里插入图片描述

2.以resnet50为例:

在这里插入图片描述
Conv(n)——>Conv(n+1)时(右边结构):
卷积递进的输入为[56,56,256]——>输出为[28,28,512]:

  1. 通过设置步长stride=1、1x1卷积和卷积核128,使得高宽保持不变,卷积核从256个更改为128个。
  2. 激活函数relu处理。
  3. 通过设置步长stride=2、3x3卷积和卷积核128,使得高宽从56缩减一半到28,卷积核个数保持不变。
  4. 激活函数relu处理。
  5. 通过设置步长stride=1、1x1卷积和卷积核512,使得高宽保持不变,卷积核从256个更改为512个。

快捷连接的输入为[56,56,256]——>[1x1x512]——>输出为[28,28,512]:

  1. 通过设置步长stride=2、1x1卷积和卷积核更改,使得高宽从56缩减一半到28,卷积核从256个更改为512个。
  2. 与卷积递进的输出值相加

非残差结构第一层时(左边结构):
卷积递进:

  1. 通过设置步长stride=1、1x1卷积和卷积核64,使得高宽不变,卷积核从256个更改为64个。
  2. 激活函数relu处理。
  3. 通过设置步长stride=1、3x3卷积和卷积核64,使得高宽与卷积核个数不变。
  4. 激活函数relu处理。
  5. 通过设置步长stride=1、1x1卷积和卷积核256,使得高宽不变,卷积核从64个更改为256个。

快捷连接:

  1. 输入值与卷积递进的输出值相加

在这里插入图片描述


三、Batch Normalization

1、初步理解

参考博客
所谓Feature Map是卷积核卷出来的,你用各种情况下的卷积核去乘以原图,会得到各种各样的feature map。

在输入一张图片时往往会使用预处理来使得图片满足某一个分布规律,以加快特征提取。然后进行一次卷积得到一个feature map,而这个feature map就不一定还满足之前所要求的分布规律。

而Batch Normalization目的是使得一批Batch数据的feature map,使得满足均值为0,方差为1的分布规律。注意不是对某一张图片的feature map,而是一整批的feature map,因为BN需要计算整个batch的均值和方差。
在这里插入图片描述

通过BN可以的到的值
在这里插入图片描述

2、使用注意

使用BN时需要注意的问题
(1)训练时要将traning参数设置为True进行统计,在验证时将trainning参数设置为False,对统计的值进行验证。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。

(2)batch size尽可能设置大点,设置小后表现可能很糟糕,设置的越大求的均值和方差越接近整个训练集的均值和方差。

(3)建议将bn层放在卷积层(Conv)和激活层(例如Relu)之间,且卷积层不要使用偏置bias,因为没有用,即使使用了偏置bias求出的结果也是一样的。

四、python代码

结合ResNet与Batch Normalization制作一个pytorch版本的网络模型

import torch.nn as nn
import torch



class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self,in_channel,out_channel,stride=1,downsample=None):
        super(BasicBlock,self).__init__()

        self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,
                               kernel_size=3,stride=stride,padding=1,bias=False)

        self.bn1 = nn.BatchNorm2d(out_channel)

        self.relu = nn.ReLU()

        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
                               kernel_size=3, stride=1, padding=1, bias=False)

        self.bn2= nn.BatchNorm2d(out_channel)

        self.downsample = downsample

    def forward(self,x):
        identity = x

        if self.downsample is None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        out += identity
        out = self.relu(out)

        return out



class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self,in_channel,out_channel,stride=1,downsample=None):
        super(Bottleneck,self).__init__()

        self.conv1 = nn.Conv2d(in_channels=in_channel,out_channels=out_channel,
                               kernel_size=1,stride=1,bias=False)

        self.bn1 = nn.BatchNorm2d(out_channel)

        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, bias=False,padding=1)

        self.bn2 = nn.BatchNorm2d(out_channel)

        self.conv3 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel*self.expansion,
                               kernel_size=1, stride=1, bias=False)

        self.bn2 = nn.BatchNorm2d(out_channel*self.expansion)

        self.relu = nn.ReLU(inplace=True)

        self.downsample = downsample

    def forward(self,x):
        identity = x

        if self.downsample is None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        out += identity
        out = self.relu(out)

        return out




class ResNet(nn.Module):

    def __init__(self,block,blocks_num,num_classes=1000,include_top=True):
        super(ResNet,self).__init__()

        self.include_top = include_top

        self.in_channel = 64

        self.conv1 = nn.Conv2d(3,self.in_channel,kernel_size=7,stride=2,
                               padding=3,bias=False)

        self.bn1 = nn.BatchNorm2d(self.in_channel)

        self.relu = nn.ReLU(inplace=True)

        self.maxpool = nn.MzxPool2d(kernel_size=3,stride=2,padding=1)

        self.layer1 = self._make_layer(block, 64, blocks_num[0])
        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)
        self.layer1 = self._make_layer(block, 256, blocks_num[2], stride=2)
        self.layer1 = self._make_layer(block, 512, blocks_num[3], stride=2)

        if self.include_top:
            self.avgpool = nn.AdaptiveAvgPool2d((1,1))
            self.fc = nn.Linear(512*block.expansion,num_classes)

        for m in self.modules():
            if isinstance(m,nn.Conv2d):
                nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')

    def _make_layer(self,block,channel,blocks_num,stride=1):
        downsample = None
        if stride != 1 or self.in_channel != channel*block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channel,channel*block.expansion,kernel_size=1,stride=stride,bias=False),
                nn.BatchNorm2d(channel*block.expansion))

        layers = []
        layers.append(block(self.in_channel,channel,downsample=downsample,stride=stride))

        self.in_channel = channel*block.expansion

        for _ in range(1,blocks_num):
            layers.append(block(self.in_channel,channel))

        return nn.Sequential(*layers)

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

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        if self.include_top:
            x = self.avgpool(x)
            x = torch.flatten(x,1)
            x = self.fc(x)

        return x



def resnet18(num_classes=1000,include_top=True):
    return ResNet(BasicBlock,[2,2,2,2],num_classes=num_classes,include_top=True)



def resnet50(num_classes=1000,include_top=True):
    return ResNet(Bottleneck,[3,4,6,3],num_classes=num_classes,include_top=True)

猜你喜欢

转载自blog.csdn.net/qq_45848817/article/details/127023232