文献阅读ResNet

本文介绍了ResNet如何通过引入残差学习和跳跃连接解决深度神经网络的梯度消失和网络退化问题,从而实现深度网络性能的显著提升。ResNet的创新设计,特别是残差块和跳跃连接,对后续深度学习模型的发展产生了重大影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘要

本周学习ResNet论文。随着网络深度的增加,梯度消失和网络退化问题限制了神经网络的进一步发展。ResNet的研究旨在解决这些问题,通过引入残差学习和跳跃连接的概念,构建了一种新的神经网络架构。ResNet的关键创新在于通过残差块让层学习输入和输出之间的残差,使网络更易训练。跳跃连接直接将输入传递到深层网络,有助于梯度传播和信息保留。这一设计允许网络深度显著增加,提高了性能,同时解决了深层网络的训练问题。ResNet的贡献在于引入了残差学习,跳跃连接和深度增加的概念,为深度学习模型的设计提供了重要启发,影响了后续网络架构的发展。最后进行了代码实践。

Abstract

This week, I studied the ResNet paper. As the depth of networks increases, problems such as gradient vanishing and network degradation have limited the further development of neural networks. The research on ResNet aims to solve these issues by introducing the concepts of residual learning and skip connections, creating a new neural network architecture. The key innovation of ResNet is in using residual blocks that allow layers to learn the residuals between inputs and outputs, making the network easier to train. Skip connections directly transmit inputs to deeper layers of the network, facilitating gradient propagation and information retention. This design has allowed for a significant increase in network depth, improved performance, and resolved the training issues associated with deep networks. The contribution of ResNet lies in introducing concepts like residual learning, skip connections, and increased depth, providing significant insights for the design of deep learning models and influencing the development of subsequent network architectures. Finally, practical coding exercises were conducted.

1、研究背景和目的

在深度学习复兴的背景下,神经网络的深度逐渐增加,从AlexNet开始,研究者们尝试构建更深的卷积神经网络以提高计算机视觉任务的性能。然而,随着网络深度的增加,出现了梯度消失和网络退化等问题,导致训练非常深的神经网络变得困难。这些问题限制了神经网络在更深层次上的表现,因此需要一种新的架构来解决这些问题。ResNet的研究目的是构建一种新的神经网络架构,解决深度神经网络中的梯度消失和网络退化问题,从而推动计算机视觉领域的性能提升和深度学习的发展。通过引入残差学习的概念,ResNet取得了巨大的成功,并成为深度学习中的重要里程碑之一。

2.研究内容

2.1 主要内容

研究者致力于解决深度神经网络中的梯度消失和网络退化问题。为了实现这一目标,他们提出了一种新的神经网络架构,即残差神经网络(ResNet),并使用该架构进行图像分类任务。ResNet的关键创新在于引入了残差块(residual blocks)和跳跃连接(shortcut connections)的概念,这些组件使得网络更容易训练并且可以构建更深的网络。具体而言,ResNet包括了多个残差块,每个块都包含了若干卷积层和跳跃连接,这使得梯度可以更有效地传播,减轻了网络退化问题。作者通过在大规模图像分类任务上的实验验证了ResNet的性能,展示了其在图像分类的准确性上的显著提高。这一创新架构的成功应用不仅是为了推动图像分类性能的提升,还为深度神经网络的更广泛应用提供了有力的基础。

2.2 ResNet模型结构

网络结构

ResNet网络是参考了VGG19网络(最左边),在这基础上进行修改,并通过短路机制加入了残差单元,如下图。变化主要体现在ResNet直接使用stride=2的卷积做下采样,并且用global average pool层替换了全连接层。ResNet的一个重要设计原则是:当feature map大小降低一半时,feature map的数量增加一倍,这保持了网络层的复杂度。从图5中可以看到,ResNet相比普通网络每两层间增加了短路机制,这就形成了残差学习,其中虚线表示feature map数量发生了改变。图中最右边展示的34-layer的ResNet。

截图-1700367188351

ResNet的前两层为输出通道数为64、步幅为2的7×7卷积层,后接步幅为2的3×3的最大池化层。 不同于GoogLeNet,ResNet在每个卷积层后增加了批量规一化层。接着, ResNet使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。 由于之前已经使用了步幅为2的最大池化层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。ResNet的一个重要设计原则是:当feature map大小降低一半时,feature map的数量增加一倍,这保持了网络层的复杂度。最后,输入全局平均汇聚层,以及全连接层输出。通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。34层ResNet如下图所示:

img

残差单元(残差块)

ResNet团队分别构建了带有“直连边(Shortcut Connection)”的ResNet残差块、以及降采样的ResNet残差块,区别是降采样残差块的直连边增加了一个1×1的卷积操作。对于直连边,当输入和输出维度一致时,可以直接将输入加到输出上,这相当于简单执行了同等映射,不会产生额外的参数,也不会增加计算复杂度。但是当维度不一致时,这就不能直接相加,通过添加1×1卷积调整通道数。这种残差学习结构可以通过前向神经网络+直连边实现, 而且整个网络依旧可以通过端到端的反向传播训练。结构如下图所示:
img

模型特点
1.超深的网络结构(突破1000层)

网络深度为什么重要?因为CNN能够提取low/mid/high-level的特征,网络的层数越多,意味着能够提取到不同level的特征越丰富。并且,越深的网络提取的特征越抽象,越具有语义信息。

2.使用Batch Normalization

为什么不能简单地增加网络层数?对于原来的网络,如果简单地增加深度,会导致梯度弥散或梯度爆炸。Batch Normalization可以解决该问题的,因此可以训练到几十层的网络。

3.残差块

随着网络层数增加,出现了新的问题:退化问题,在训练集上准确率饱和甚至下降了。这个不能解释为过拟合,因为过拟合表现为在训练集上表现更好才对。退化问题说明了深度网络不能很简单地被很好地优化。作者通过实验说明:通过浅层网络y=x 等同映射构造深层模型,结果深层模型并没有比浅层网络有更低甚至等同的错误率,推断退化问题可能是因为深层的网络很那难通过训练利用多层网络拟合同等函数。

residual结构
residual的计算方式

residual结构使用了一种shortcut的连接方式,也可理解为捷径。让特征矩阵隔层相加,注意F(X)和X形状要相同,所谓相加是特征矩阵相同位置上的数字进行相加。

截图-1700368127436

ResNet中两种不同的residual

img

CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度

1.左侧残差结构称为 BasicBlock

2.右侧残差结构称为 Bottleneck

(1)其中第一层的1× 1的卷积核的作用是对特征矩阵进行降维操作,将特征矩阵的深度由256降为64;
第三层的1× 1的卷积核是对特征矩阵进行升维操作,将特征矩阵的深度由64升成256。
降低特征矩阵的深度主要是为了减少参数的个数。
如果采用BasicBlock,参数的个数应该是:256×256×3×3×2=1179648
采用Bottleneck,参数的个数是:1×1×256×64+3×3×64×64+1×1×256×64=69632
(2)先降后升为了主分支上输出的特征矩阵和捷径分支上输出的特征矩阵形状相同,以便进行加法操作。

降维时的 short cut

观察下图的 ResNet18层网络,可以发现有些残差块的 short cut 是实线的,而有些则是虚线的。

这些虚线的 short cut 上通过1×1的卷积核进行了维度处理(特征矩阵在长宽方向降采样,深度方向调整成下一层残差结构所需要的channel)。

img

权重更新

image-20231119160749188

3、ResNet网络结构的主要贡献

3.1 残差学习(Residual Learning)

ResNet的核心创新是引入了残差学习。在传统的深度网络中,层与层之间直接学习目标映射。而ResNet通过残差块让这些层学习输入和输出之间的残差(差异),这意味着网络层需要学习的内容更简单。残差学习有效地解决了更深网络中的梯度消失问题,并使网络训练更加高效。

3.2 跳跃连接(Skip Connections)

ResNet通过在其残差块中加入跳跃连接,使得输入可以跳过一个或多个层次。这些连接直接将输入传递到更深层的网络中,有助于梯度在训练过程中更有效地传播,解决了深层网络中梯度消失的问题。这种设计同时也帮助了信息的保留,确保了网络在深层中仍能学习有效信息。

3.3 网络深度的显著增加

与AlexNet相比,ResNet实现了网络深度的显著增加。ResNet提出了多种不同深度的版本,如ResNet-50、ResNet-101和ResNet-152等。这种深度上的增加在保持网络训练效率的同时,大幅提升了模型的性能,尤其是在复杂的视觉任务中。

3.4 提升深层网络的训练效果

在ResNet之前,更深的网络往往会遇到性能下降的问题,但ResNet通过其创新的架构设计,不仅避免了这一问题,还显示了网络深度与性能之间的正相关关系。这一发现对后续深度学习模型的设计产生了深远影响。ResNet的设计哲学和技术已经被广泛应用于后续的网络架构设计中,如DenseNet、Inception网络等。

4.代码实现:

import torch.nn as nn
import torch
 
class BasicBlock(nn.Module):
    # 基本残差块的扩展倍数,对于BasicBlock,这个值是1
    expansion = 1
 
    def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
        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)
        # ReLU激活函数
        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 not None:
            identity = self.downsample(x)
 
        # 应用第一个卷积层、批量归一化和ReLU激活
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
 
        # 应用第二个卷积层和批量归一化
        out = self.conv2(out)
        out = self.bn2(out)
 
        # 将输入添加到卷积层输出
        out += identity
        # 再次应用ReLU激活
        out = self.relu(out)
 
        return out

 
class Bottleneck(nn.Module):
    # 对于Bottleneck,扩展倍数是4
    expansion = 4
 
    def __init__(self, in_channel, out_channel, stride=1, downsample=None,
                 groups=1, width_per_group=64):
        super(Bottleneck, self).__init__()
 
        width = int(out_channel * (width_per_group / 64.)) * groups
 
        # 第一个卷积层,减少通道数
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,
                               kernel_size=1, stride=1, bias=False)
        self.bn1 = nn.BatchNorm2d(width)
        # 第二个卷积层,保持通道数不变
        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,
                               kernel_size=3, stride=stride, bias=False, padding=1)
        self.bn2 = nn.BatchNorm2d(width)
        # 第三个卷积层,增加通道数
        self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion,
                               kernel_size=1, stride=1, bias=False)
        self.bn3 = 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 not None:
            identity = self.downsample(x)
 
        # 依次通过三个卷积层和批量归一化,最后应用ReLU激活
        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,
                 groups=1,
                 width_per_group=64):
        super(ResNet, self).__init__()
        self.include_top = include_top  # 是否包含顶层的全连接层
        self.in_channel = 64  # 初始通道数
 
        self.groups = groups
        self.width_per_group = width_per_group
 
        # 初始卷积层和批量归一化
        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.MaxPool2d(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.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)
        self.layer4 = 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, block_num, stride=1):
        downsample = None
        # 如果步长不为1或通道数需要改变,则创建下采样层
        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,
                            groups=self.groups,
                            width_per_group=self.width_per_group))
        self.in_channel = channel * block.expansion
 
        # 添加剩余的块
        for _ in range(1, block_num):
            layers.append(block(self.in_channel,
                                channel,
                                groups=self.groups,
                                width_per_group=self.width_per_group))
 
        return nn.Sequential(*layers)
 
    def forward(self, x):
        # 依次通过初始卷积层、批量归一化、ReLU激活、最大池化和四个残差层
        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

 
# ResNet各个版本的定义函数
def resnet34(num_classes=1000, include_top=True):
    # 返回34层的ResNet模型
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)

def resnet50(num_classes=1000, include_top=True):
    # 返回50层的ResNet模型
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)

def resnet101(num_classes=1000, include_top=True):
    # 返回101层的ResNet模型
    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)
 
def resnext50_32x4d(num_classes=1000, include_top=True):
     # 返回具有特定组数和宽度的ResNeXt-50模型
    # https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth
    groups = 32
    width_per_group = 4
    return ResNet(Bottleneck, [3, 4, 6, 3],
                  num_classes=num_classes,
                  include_top=include_top,
                  groups=groups,
                  width_per_group=width_per_group)
 
def resnext101_32x8d(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth
    groups = 32
    width_per_group = 8
    return ResNet(Bottleneck, [3, 4, 23, 3],
                  num_classes=num_classes,
                  include_top=include_top,
                  groups=groups,
                  width_per_group=width_per_group)

5.总结

在本周学习中,我学习了ResNet在解决深层神经网络训练中遇到的关键问题上的贡献。通过引入残差学习,ResNet允许更深层次的网络训练,同时避免了梯度消失和网络退化问题,从而实现了在图像分类等任务上的性能提升。这一架构的成功不仅推动了计算机视觉性能的提升,还为深度学习技术的未来研究和应用奠定了基础。

### 关于 ResNet 的学术论文和文献 ResNet 是由何凯明等人在 2015 年提出的深度残差网络,其核心思想是通过引入残差连接来缓解深层神经网络中的梯度消失问题以及退化问题。以下是与 ResNet 相关的重要学术论文和文献: #### 原始论文 - **《Deep Residual Learning for Image Recognition》** 这篇论文首次提出了 ResNet 架构,并展示了如何通过残差块使网络能够训练至非常深的层次[^5]。该研究不仅赢得了当年的 ILSVRC 图像分类竞赛冠军,还对后续的目标检测和语义分割等领域产生了深远的影响。 #### 后续扩展与改进 - **《Identity Mappings in Deep Residual Networks》 (ResNet-v2)** 此论文进一步探讨了 ResNet 中的设计细节,并提出了一种新的残差单元形式,称为预激活单元(pre-activation unit),这显著提高了模型的表现并简化了训练过程[^4]。 - **《Wide Residual Networks》** 提出了 Wide Residual Networks(WideResNets),它证明增加宽度而非单纯增加深度同样能有效提升性能,同时减少了计算资源的需求。 #### 应用领域拓展 - **目标检测与实例分割** - **《Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks》** 和 **《Mask R-CNN》** 都采用了 ResNet 作为骨干网络来进行特征提取[^2]。 - **医学影像分析** - 许多基于 ResNet 的变体被广泛应用于医疗图像处理任务中,比如 X 光片诊断、肿瘤分割等。 #### 综述文章 - 对于希望全面了解 ResNet 及其演变历史的研究者来说,可以查阅一些综述性质的文章或书籍章节,它们通常会总结不同版本 ResNet 的特点及其应用场景。 ```python import torch.nn as nn class BasicBlock(nn.Module): expansion = 1 def __init__(self, inplanes, planes, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d(inplanes, 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(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) 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) if self.downsample is not None: identity = self.downsample(x) out += identity out = self.relu(out) return out ``` 上述代码片段展示了一个简单的 ResNet 残差块实现方式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值