ResNet详解

关键要点

  • ResNet(残差神经网络)是一种深度学习架构,研究表明它通过残差学习解决了深度网络的“退化”问题。
  • 它似乎通过残差块设计支持训练非常深的网络(如152层),在图像分类任务中表现优异。
  • 证据倾向于表明ResNet通过捷径连接缓解了梯度消失问题,影响了后续模型如DenseNet。

ResNet简介

ResNet(Residual Neural Network,残差神经网络)是一种深度学习模型,旨在解决深度神经网络中随着层数增加,训练和测试误差反而上升的“退化”问题。它通过引入残差学习和残差块设计,成功训练了非常深的网络,并在2015年ImageNet竞赛中取得了突破性成果。

问题与解决方案

研究表明,深度网络的“退化”问题不是由过拟合或梯度消失/爆炸引起的,而是优化难度增加导致的。ResNet通过学习残差映射 (F(x) := H(x) – x),而不是直接学习映射 (H(x)),使得网络更容易学习恒等映射,从而支持更深的网络结构。

网络结构

ResNet的网络结构包括基本块和瓶颈块,通过堆叠残差块实现深度可调。不同变体如ResNet-18、ResNet-50等,适用于不同复杂度的任务。

应用与影响

ResNet不仅在图像分类中表现优异,还被广泛应用于目标检测和语义分割等领域,深刻影响了深度学习的发展。



详细报告

以下是对ResNet(残差神经网络)的全面分析,基于网络搜索和页面浏览获取的信息,旨在为用户提供完整的讲解。

引言

ResNet(Residual Neural Network,残差神经网络)是一种深度学习架构,由何凯明等人于2015年提出,发表于论文《Deep Residual Learning for Image Recognition》(https://arxiv.org/abs/1512.03385)。它通过引入残差学习和残差块设计,解决了深度神经网络中随着层数增加,训练和测试误差反而上升的“退化”问题。ResNet在2015年ImageNet Large Scale Visual Recognition Challenge(ILSVRC)中获得第一名,Top-5错误率为3.57%,并在2016年获得CVPR最佳论文奖。其设计深刻影响了后续深度学习模型,如DenseNet、ResNeXt等。

背景:深度神经网络的“退化”问题

在深度学习中,通常认为增加网络深度可以提高模型的表现力,因为更深的网络可以提取更复杂的特征。然而,研究表明,当网络层数超过一定深度时,模型的训练和测试误差反而会增加,这种现象被称为“退化”问题。例如,CSDN博客(https://blog.csdn.net/sunny_yeah_/article/details/89430124)提到,34层普通卷积网络(plain network)的训练误差高于20层网络,这表明深度增加并不总是带来性能提升。

这种退化问题不是由过拟合或梯度消失/爆炸引起的,而是因为更深的网络更难优化,其误差曲面更复杂。知乎文章(https://zhuanlan.zhihu.com/p/31852747)指出,梯度消失/爆炸问题可以通过Batch Normalization(BN)层解决,但退化问题仍存在,原因在于优化难度增加。

解决方案:残差学习

ResNet通过引入残差学习(Residual Learning)来解决退化问题。传统的深度网络试图直接学习从输入到输出的映射函数 (H(x)),但ResNet改为学习残差映射 (F(x) := H(x) – x)。这样,网络只需要学习输入和输出的差值,而不是整个映射关系。

这种设计使得网络更容易学习恒等映射(即 (F(x) = 0)),这对于很深的网络至关重要。CSDN博客(https://www.cnblogs.com/shine-lee/p/12363488.html)解释道,通过残差学习,网络可以轻松地训练非常深的网络,而不会出现退化问题。这是因为,当网络无法学习到更好的映射时,它可以简单地学习恒等映射,从而保持性能不下降。

残差块设计

残差块(Residual Block)是ResNet的核心组成部分,它由两个路径组成:

  • 残差路径(Residual Path):通过卷积层计算残差函数 (F(x))。
  • 捷径路径(Shortcut Path):直接将输入 (x)传递到输出。

残差块的输出为 (F(x) + x),即残差函数与输入的和。知乎文章(https://zhuanlan.zhihu.com/p/54289848)提到,这种设计通过捷径连接将反向传播(backpropagation)转化为加法操作,避免了梯度消失或爆炸问题。

残差块的类型

  • 基本块(Basic Block):由两个 (3 \times 3) 卷积层组成,通常用于较浅的变体如ResNet-18和ResNet-34。
  • 瓶颈块(Bottleneck Block):由 (1 \times 1)、(3 \times 3) 和 (1 \times 1) 卷积层组成,用于减少计算量,尤其在ResNet-50及更深的变体中使用。瓶颈块通过 (1 \times 1) 卷积降低维度,减少参数量。

捷径路径的处理

  • 如果 (F(x)) 和 (x) 的维度相同,直接使用恒等映射。
  • 如果维度不同(如降采样时),使用 (1 \times 1) 卷积调整维度。例如,在ResNet-50中,当通道数翻倍时,捷径路径也会通过 (1 \times 1) 卷积匹配维度。

网络结构

ResNet的网络结构可以通过堆叠多个残差块来实现深度可调。不同变体包括:

  • ResNet-18:18层,仅使用基本块。
  • ResNet-34:34层,仅使用基本块。
  • ResNet-50:50层,使用瓶颈块。
  • ResNet-101:101层,使用瓶颈块。
  • ResNet-152:152层,使用瓶颈块。

网络结构特点(基于https://www.cnblogs.com/shine-lee/p/12363488.html):

  • 初始卷积层:通常为 (7 \times 7) 卷积,步长为2,输出通道数为64。
  • 残差块组:网络分为多个阶段,每个阶段包含多个残差块。
  • 每个阶段的输出通道数翻倍(如从64到128、256等)。
  • 降采样(downsampling)通过卷积层的步长实现,而不是池化层。
  • 全局平均池化:代替全连接层,减少参数量。
  • 分类层:Softmax分类器。

以下是ResNet各变体的层数和块数对比表:

模型层数基本块数瓶颈块数参数量(百万)
ResNet-1818160~11.7
ResNet-3434330~21.8
ResNet-5050016~25.6
ResNet-101101033~44.5
ResNet-152152050~60.2

误差曲面与优化

ResNet的误差曲面比普通网络更平滑,优化更容易。CSDN博客(https://www.cnblogs.com/shine-lee/p/12363488.html)提到,通过捷径连接,网络可以更容易地学习恒等映射,使得训练更稳定。研究表明,残差块的引入使得网络的损失函数更加平滑,避免了深度网络中常见的优化困难。

改进与扩展

后续工作对ResNet进行了改进,例如:

  • 预激活(Pre-activation):将BN(Batch Normalization)和ReLU激活函数放在卷积层之前,而不是之后。这进一步提高了优化效率并减少了过拟合(基于https://arxiv.org/abs/1603.05027)。
  • 保持捷径路径“纯净”:尽可能避免在捷径路径上使用 (1 \times 1) 卷积,除非必要(如维度不匹配)。这使得网络更易于优化。

这些改进使得ResNet可以训练超过1000层的网络,而性能仍能持续提升。例如,改进后的ResNet可以在CIFAR-10上训练1000层网络,性能优于浅层网络。

理论见解

捷径连接将反向传播(backpropagation)转化为加法操作,避免了梯度消失或爆炸问题。具体来说,在反向传播中,捷径连接确保了梯度可以通过 (F(x) + x) 的形式直接传递,而不会被稀释或放大。知乎文章(https://zhuanlan.zhihu.com/p/42706477)提到,这使得ResNet能够无损地传播损失信号(loss),从而支持非常深的网络结构。

应用与影响

ResNet不仅在图像分类任务中表现出色,还被广泛应用于目标检测、语义分割等任务。例如,Faster R-CNN中使用ResNet-101替换VGG-16,观察到28%的相对性能提升(基于https://zhuanlan.zhihu.com/p/31852747)。其残差块设计已成为深度学习架构的标准组件,影响了后续模型如DenseNet、ResNeXt等。

代码示例

以下是一个简化的ResNet-18代码示例,使用PyTorch实现,展示其基本结构(基于https://www.cnblogs.com/xfuture/p/17795430.html):

import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out

class ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes=10):
        super(ResNet, self).__init__()
        self.in_channels = 64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
        self.linear = nn.Linear(512, num_classes)

    def _make_layer(self, block, out_channels, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_channels, out_channels, stride))
            self.in_channels = out_channels
        return nn.Sequential(*layers)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = F.avg_pool2d(out, 4)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        return out

def ResNet18():
    return ResNet(BasicBlock, [2, 2, 2, 2])

# 示例使用
model = ResNet18()
print(model)

总结

ResNet通过残差学习和残差块设计成功地训练了非常深的网络(如152层),在ImageNet竞赛中取得了突破性成果,并深刻影响了深度学习领域。其关键在于深度可调性和优化效率,适合图像分类、目标检测等计算机视觉任务。

参考资料

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注