小杰深度学习(fourteen)——视觉-经典神经网络——ResNet

5.ResNet

ResNet的主要特点是采用了残差学习机制。在传统的神经网络中,每一层的输出都是直接通过一个非线性激活函数得到的。但在ResNet中,每一层的输出是通过一个"残差块"得到的,该残差块包含了一个快捷连接(shortcut)和几个卷积层。这样,在训练过程中,每一层只需要学习残差(即输入与输出之间的差异),而不是所有的信息。这有助于防止梯度消失和梯度爆炸的问题,从而使得网络能够训练得更深。

ResNet的网络结构相对简单,并且它的训练速度也比GoogLeNet快。这使得ResNet成为了在许多计算机视觉任务中的首选模型。

ResNet的主要优点是具有非常深的层数,可以达到1000多层,但仍然能够高效地训练。这是通过使用残差连接来实现的,这种连接允许模型学习跨越多个层的残差,而不是直接学习每一层的输出。这使得ResNet能够更快地收敛,并且能够更好地泛化到新的数据集,ResNet论文中共提出了五种结构,分别是ResNet-18,ResNet-34,ResNet-50,ResNet-101,ResNet-152。

论文名称:Deep Residual Learning for Image Recognition

论文地址:https://arxiv.org/abs/1512.03385

ResNet_Paper.pdf

  1. 网络的结构

在paper中给出了网络结构的表,如下图所示:

残差连接的34 层网络结构图

  1. 网络的创新

2.1 残差结构解决更深的网络层数带来的问题

2.1.1更深的网络层数带来的问题?

如果想要搭建一个更深的网络,是不是可以类似于它们那样直接进行卷积和池化的堆叠呢?

答案是否定的,直接的堆叠网络错误率如下图所示:

上图都是直接堆叠神经网络的结果,在左侧图中,黄色线是训练过程中20层网络的训练损失曲线,红色线是训练过程中56层网络的训练损失曲线,理论上讲,网络深可以带来更小的损失,但是实时恰恰相反,56层的错误率要高于20层的错误率。

发生这种情况的原因是什么呢?

1.梯度消失或梯度爆炸:

梯度消失:例如在一个网络中,每一层的损失梯度的值都小于1,那么连续的链式法则之下,每向前传播一次,都要乘以一个小于1的误差梯度,那么如果网络越深,在经过非常多的前向传播次数之后,那么梯度越来越小,直到接近于0,这就是梯度消失。

梯度爆炸:如果每一层的损失梯度的值都大于1,那么网络越深,在经过非常多的前向传播次数之后,那么梯度越来越大,导致梯度爆炸。

误差梯度不会始终为 1 或接近 1,所以一般通过数据标准化处理,权重初始化等操作进行抑制,但网络太深依然很难很好的抑制,当然Relu也可以抑制梯度消失问题,但是Relu可能会导致原始特征不可逆损失,引出另一个问题,即网络退化问题。

2.degradation problem:直译就是退化问题:随着网络层数的增多,训练集loss逐渐下降,然后趋于饱和,当再增加网络深度,训练集loss反而会增大。注意这并不是过拟合,因为在过拟合中训练loss是一直减小的。

2.1.2 Residual结构(残差结构)

2.1.2.1残差结构效果

当时用残差结构进行网络组合时,可以很明显的解决这个问题,如下图所示:

根据上图可以看出,在使用残差结构后,从20层,到110层,错误率都是逐步在降低,残差网络对degradation problem是有抑制作用的。在使用残差网络之后,模型内部的复杂度降低,所以抑制了退化问题。

2.1.2.2 残差结构

ResNet网络两种不同的残差结构

Residual结构是残差结构,有两种不同的残差结构,在ResNet-18和ResNet-34中,用的是如下图中左侧图的结构,在ResNet-50、ResNet-101和ResNet-152中,用的是下图中右侧图的结构。

在上图左侧图可以看到输入特征矩阵的channels是64,经过一个3x3的卷积核卷积之后,再进行Relu激活函数的激活,再经过一个3x3的卷积核进行卷积,但是在这之后并没有直接经过激活函数进行激活。并且可以看到,在主分支上有一个圆弧的线从输入特征矩阵直接连到了一个加号,这个圆弧的线是shortcut(捷径分支),它直接将输入特征矩阵加到经过第二次3x3的卷积核卷积之后的输出特征矩阵,注意,这里描述的是加,而不是叠加或者拼接,也就是说是矩阵对应维度位置进行一个和法运算,意味着主分支的输出矩阵和shortcut的输出矩阵的shape必须相同,这里包括宽、高、channels,在相加之后,再经过Relu激活函数进行激活。

在上图右侧图可以看到输入特征矩阵的channels是256,要先经过一个1x1的卷积,之前在GoogLeNet提到过,1x1的卷积是为了维度变换,所以这里也是先用1x1的卷积进行降维到64,然后再使用3x3的卷积进行特征提取,提取完成后,在通过1x1的卷积进行升维到256,之后得到的输出矩阵再和经过shortcut的输入矩阵进行对应维度位置的加法运算,在相加之后,再经过Relu激活函数进行激活。

34层的网络残差结构图如下所示:

上图中残差网络部分也就是shortcut有实线和虚线之分。

实线部分就是之前讲的普通的shortcut,如下图左侧图,从左侧图可以看到,当主分支的输入特征矩阵和输出特征矩阵的shape一致时,输入特征矩阵可以经过shortcut得到输出特征矩阵直接与主分支的输出特征矩阵进行加法运算.

虚线部分不仅仅有channels变化,还有特征矩阵的宽和高变化,虚线部分有一个处理(需要进行下采样 (downsampling),所谓下采样,也就是通过卷积,缩小图片)来让主分支的输出特征矩阵和shortcut的输出特征矩阵保持一致。如下图右侧图。

右侧图主分支上由于步长=2,导致矩阵的宽和高都减半了,同时由于第一个卷积核的个数是128,导致channels从64升到了128,从而channels也不一样了,所以主分支的输出特征矩阵是[28,28,128],那么如果将shortcut分支上加一个卷积运算,卷积核个数为128,步长为2,那么经过shortcut分支的输出矩阵也同样为[28,28,128],那么两个输出矩阵又可以进行相加了。

为什么残差结构能够抑制degradation problem问题呢?

导数很小的情况下,那么会对网络起不到更新的作用,但是由于加入了输入矩阵的导数,也就保证了网络导数会一直存在,而不会出现导数消失的情况

简而言之,ResNet网络中的残差结构的堆叠有点这类似下面这个过程:

刚开始的前几个残差结构提取了很重要的一些特征,后几个残差结构负责把一些特征进行细化,如果后几个残差结构学不到东西,但是并不会影响前面的梯度从而造成degradation problem退化问题。

2.2 Batch Normalization

2.2.1概念:

对一个batch 内的数据在通道 尺度上计算均值方差,将同批次同通道的数据归一化为均值为0、方差为1的正态分布。

2.2.2 Batch Normalization 为什么要这样做?

一个神经网络在输入图像之前,会将图像进行预处理,这个预处理可能是标准化处理等手段,由于输入数据满足某一分布规律,所以会加速网络的收敛。

虽然在输入第一次卷积的时候满足某一分布规律,但是在输入第二次卷积时,就不一定满足某一分布规律了,再往后的卷积的输入就更不满足了,那么就需要一个中间商,让上一层的输出经过它之后能够某一分布规律,Batch Normalization就是这个中间商,它可以让输入的特征矩阵的每一个channels满足均值为0,方差为1的分布规律。

举例:

上图展示了大小为[3,4,2,2]的tensor(批次大小为3,通道数为4,高为2,宽为2)的BatchNorm过程,该过程是针对训练数据的且无缩放和平移。可以看出,BatchNorm是对同一批次内同一通道的所有数据进行归一化。

2.2.3 Batch Normalization 的好处

Batch Normalization对解决梯度消失或者梯度爆炸的抑制起到了作用。

归一化也可以理解成规则化,让数据符合某些特征,这样模型训练时更容易掌握规律

3. 网络的问题

由于ResNet的结构非常复杂,所以它的训练时间比较长。此外,由于它具有非常深的层数,因此它需要大量的数据来进行训练。

4 结构组件介绍

下面结合虚拟仿真的组件,搭建ResNet的网络结构。

4.1 卷积

输入特征矩阵是(224 x 224 x 3),本层卷积核的宽、高、通道、个数是(7 x 7 x 3 x 64),步长为2,padding方式为SAME,经过计算可知,输出特征矩阵为(112 x 112 x 64)。

本层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.2 池化-降采样

池化方式为MaxPool,输入特征矩阵是(112 x 112 x 64),池化核大小为3,步长为2,经过计算可知,输出特征矩阵为(56 x 56 x 64),如下图:

4.3 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(56 x 56 x 64),第一层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 64),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 64),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(56 x 56 x 64)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.4 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(56 x 56 x 64),第一层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 64),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 64),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(56 x 56 x 64)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.5 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(56 x 56 x 64),第一层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 64),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 64),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(56 x 56 x 64)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.6 残差结构

shortcut类型是dotted line shortcuts。

输入特征矩阵是(56 x 56 x 64),第一层卷积核的宽、高、通道、个数是(3 x 3 x 64 x 128),步长为2,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

shortcut的宽、高、通道、个数是(1 x 1 x 64 x 128),步长为2,padding方式为SAME。

经过计算可知,输出特征矩阵为(28 x 28 x 128)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.7 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(28 x 28 x 128),第一层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(28 x 28 x 128)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.8 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(28 x 28 x 128),第一层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(28 x 28 x 128)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.9 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(28 x 28 x 128),第一层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 128),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(28 x 28 x 128)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.10 残差结构

shortcut类型是dotted line shortcuts。

输入特征矩阵是(28 x 28 x 128),第一层卷积核的宽、高、通道、个数是(3 x 3 x 128 x 256),步长为2,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

shortcut的宽、高、通道、个数是(1 x 1 x 128 x 256),步长为2,padding方式为SAME。

经过计算可知,输出特征矩阵为(14 x 14 x 256)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.11 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(14 x 14 x 256),第一层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(14 x 14 x 256)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.12 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(14 x 14 x 256),第一层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(14 x 14 x 256)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.13 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(14 x 14 x 256),第一层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(14 x 14 x 256)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.14 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(14 x 14 x 256),第一层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(14 x 14 x 256)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.15 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(14 x 14 x 256),第一层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 256),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(14 x 14 x 256)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.16 残差结构

shortcut类型是dotted line shortcuts。

输入特征矩阵是(14 x 14 x 256),第一层卷积核的宽、高、通道、个数是(3 x 3 x 256 x 512),步长为2,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 512 x 512),步长为1,padding方式为SAME。

shortcut的宽、高、通道、个数是(1 x 1 x 256 x 512),步长为2,padding方式为SAME。

经过计算可知,输出特征矩阵为(7 x 7 x 512)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.17 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(7 x 7 x 512),第一层卷积核的宽、高、通道、个数是(3 x 3 x 512 x 512),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 512 x 512),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(7 x 7 x 512)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.18 残差结构

shortcut类型是solid line shortcuts。

输入特征矩阵是(7 x 7 x 512),第一层卷积核的宽、高、通道、个数是(3 x 3 x 512 x 512),步长为1,padding方式为SAME。

第二层卷积核的宽、高、通道、个数是(3 x 3 x 512 x 512),步长为1,padding方式为SAME。

经过计算可知,输出特征矩阵为(7 x 7 x 512)。

每一卷积层之后要经过batchNorm进行归一化,以及ReLU激活函数,如下图:

4.19 池化-降采样

池化方式为AvgPool,输入特征矩阵是(7 x 7 x 512),池化核大小为7,步长为1,经过计算可知,输出特征矩阵为(1 x 1 x 512),如下图:

4.20 全连接

输入节点是512,paper中是按照ImageNet数据集做的,所以分类为1000类,输出节点为1000。

如下图:

4.21 全连接Softmax

最后通过Softmax实现将多分类的输出值转换为范围在[0, 1]和为1的概率分布,如下图:

4.22实验验证

本实验主要学习resnet的网络的相关知识,最后需要进行点击"验证",验证成功即代表网络结构连接是正确的。

5.代码

5.2调用torchvision中的已经写好的resnet代码

python 复制代码
from torchvision.models import resnet34
from torchsummary import summary
import  torch
device =torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model=resnet34().to(device)
print(summary(model,input_size=(3,224,224)))

手写代码实现

python 复制代码
import torch
import torch.nn as nn

#定义BasicBlock
class BasicBlock(nn.Module):
    expansion=1
    def __init__(self,inplanes,planes,stride=1,downsample=None):
        super(BasicBlock,self).__init__()
        self.conv1=nn.Conv2d(in_channels=inplanes,out_channels=planes,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn1=nn.BatchNorm2d(planes)
        self.relu=nn.ReLU(inplace=True)
        self.conv2=nn.Conv2d(in_channels=planes,out_channels=planes,kernel_size=3,stride=1,padding=1,bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
    def forward(self,x):
        identiy=x
        out=self.conv1(x)
        out=self.bn1(out)
        out=self.relu(out)

        out=self.conv2(out)
        out=self.bn2(out)
        if self.downsample is not None:
            identiy=self.downsample(x)
        out=out+identiy
        out=self.relu(out)
        return out


#定义bottleneck
class Bottleneck(nn.Module):
    expansion=4
    def __init__(self,inplanes,planes,stride=1,downsample=None):
        super(Bottleneck,self).__init__()
        self.conv1 = nn.Conv2d(in_channels=inplanes, out_channels=planes, kernel_size=1, stride=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)

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

        self.conv3 = nn.Conv2d(in_channels=planes, out_channels=planes * self.expansion, kernel_size=1, stride=1, bias=False)
        self.bn3 = nn.BatchNorm2d(planes * self.expansion)

        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
    def forward(self,x):
        identity=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)

        #捷径分支有两种
        if self.downsample is not None:
            identity=self.downsample(x)

        out=out+identity
        #激活
        out=self.relu(out)
        return out


#定义resnet类
class ResNet(nn.Module):
    def __init__(self,block,layers,num_classes=1000):
        super(ResNet,self).__init__()
        #定义输出通道数
        self.inplanes=64
        #卷积
        self.conv1=nn.Conv2d(in_channels=3,out_channels=self.inplanes,kernel_size=7,stride=2,padding=3,bias=False)
        self.maxpool=nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
        #使用bn
        self.bn1=nn.BatchNorm2d(self.inplanes)
        #定义激活
        self.relu=nn.ReLU(inplace=True)
        self.layer1=self._make_layer(block,outplanes=64,blocks=layers[0],stride=1)
        self.layer2 = self._make_layer(block, outplanes=128, blocks=layers[1], stride=2)
        self.layer3 = self._make_layer(block, outplanes=256, blocks=layers[2], stride=2)
        self.layer4 = self._make_layer(block, outplanes=512, blocks=layers[3], stride=2)
        #自适应池化
        self.avgpool=nn.AdaptiveAvgPool2d((1,1))
        self.fc=nn.Linear(512*block.expansion,num_classes)

    def _make_layer(self,block,outplanes,blocks,stride=1):
        #考虑捷径分支,有两种 一个是普通的捷径分支,一个是特殊的捷径分支
        downsample=None
        if stride!=1 or self.inplanes!=outplanes*block.expansion:
            downsample=nn.Sequential(nn.Conv2d(in_channels=self.inplanes,out_channels=outplanes*block.expansion,kernel_size=1,stride=stride,bias=False),
                                     nn.BatchNorm2d(outplanes * block.expansion)
                                     )
        layers=[]
        layers.append(block(self.inplanes,outplanes,stride,downsample))
        self.inplanes = outplanes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, outplanes))
        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)
        x = self.avgpool(x)
        # 展平
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x


def resnet18(num_class=1000):
    return ResNet(BasicBlock,layers=[2,2,2,2],num_classes=1000)
def resnet34(num_class=1000):
    return ResNet(BasicBlock,layers=[3,4,6,3],num_classes=1000)
def resnet50(num_class=1000):
    return ResNet(Bottleneck,layers=[3,4,6,3],num_classes=1000)

if __name__ == '__main__':
    device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model=resnet50(1000).to(device)
    from torchsummary import summary
    print(summary(model,input_size=(3,224,224)))
相关推荐
lljss20203 小时前
5. 神经网络的学习
人工智能·神经网络·学习
闲看云起3 小时前
论文阅读《LIMA:Less Is More for Alignment》
论文阅读·人工智能·语言模型·自然语言处理
jie*3 小时前
小杰深度学习(sixteen)——视觉-经典神经网络——MobileNetV2
人工智能·python·深度学习·神经网络·tensorflow·numpy·matplotlib
MYX_3093 小时前
第五章 神经网络的优化
pytorch·深度学习·神经网络·学习
TGITCIC3 小时前
有趣的机器学习-利用神经网络来模拟“古龙”写作风格的输出器
人工智能·深度学习·神经网络·ai大模型·模型训练·训练模型·手搓模型
whltaoin3 小时前
AI 超级智能体全栈项目阶段五:RAG 四大流程详解、最佳实践与调优(基于 Spring AI 实现)
java·人工智能·spring·rag·springai
Piink3 小时前
网络模型训练完整代码
人工智能·深度学习·机器学习
曾经的三心草3 小时前
OpenCV4-直方图与傅里叶变换-项目实战-信用卡数字识别
python·opencv·计算机视觉
luoganttcc3 小时前
在 orin 上 安装了 miniconda 如何使用 orin 内置的 opencv
人工智能·opencv·计算机视觉