ResNet

Deep Residual Learning for Image Recognition

微软研究院的Kaiming He,孙剑等四名华人

2015

摘要

  • 网络越深越强,更深的神经网络难训练,提出了一种残差学习框架来减轻网络训练,152层。当时googlenet并行性高,resnet比VGG深8倍,但是resnet深,结构简单,训练起来快
  • 在ImageNet测试集上取得了3.57%的错误率。
  • CIFAR-10上分析了100层和1000层的残差网络
  • 也赢得了ImageNet检测任务,ImageNet定位任务,COCO检测和COCO分割任务的第一名。
  • 网络深了,梯度消失/爆炸,从一开始就阻碍了收敛。然而,这个问题通过标准初始化和中间标准化层在很大程度上已经解决,这使得数十层的网络能通过具有反向传播的随机梯度下降(SGD)开始收敛。
  • 当更深的网络能够开始收敛时,暴露了一个退化问题:随着网络深度的增加,准确率达到饱和然后迅速下降。意外的是,这种退化不是由过拟合引起的,并且在适当的深度模型上添加更多的层会导致更高的训练误差

创新点

  • 提出了退化现象,极深的残差网络易于优化,但当深度增加时,对应的“简单”网络(简单堆叠层)表现出更高的训练误差
  • 提出残差块
  • 提出BN,批归一化,丢弃dropout

创新点讲解

两种残差结构BasicBlock和BottleNeck

  • 左边的残差结构称为BasicBlock是针对层数较少网络,例如ResNet18层和ResNet34层网络。

  • 右边的残差结构BottleNeck是针对网络层数较多的网络,例如ResNet101,ResNet152等。

  • 为什么深层网络要使用右侧的残差结构呢。因为,右侧的残差结构能够减少网络参数与运算量。同样输入一个channel为256的特征矩阵,如果使用左侧的残差结构需要大约1170648个参数,但如果使用右侧的残差结构只需要69632个参数。

  • 1*1卷积核可以做到升降纬度,为了使的残差块可以同通道相加,需要通过1*1卷积把输入升纬度。在BottleNeck中一个256通道的图先通过1*1卷积

image-20250602112533234

BasicBlock

在resnet18和resnet34中使用了BasicBlock,输入输出通道数均为64,不需要1*1卷积

image-20250602113025370

class BasicBlock(nn.Module):
    expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
    super(BasicBlock, self).__init__()
    self.conv1 = conv3x3(inplanes, planes, stride)
    self.bn1 = nn.BatchNorm2d(planes)
    self.relu = nn.ReLU(inplace=True)
    self.conv2 = conv3x3(planes, planes)
    self.bn2 = nn.BatchNorm2d(planes)
    self.downsample = downsample
    self.stride = stride

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

BottleNeck

在resnet50、resnet101、resnet152使用了Bottlenect构造网络。Bottleneck Block中使用了1×1卷积层。如输入通道数为256,1×1卷积层会将通道数先降为64,经过3×3卷积层后,再将通道数升为256。1×1卷积层的优势是在更深的网络中,用较小的参数量处理通道数很大的输入。

image-20250602113116084

class Bottleneck(nn.Module):
    expansion = 4
    def __init__(self, inplanes, planes, stride=1, downsample=None):
    super(Bottleneck, self).__init__()
    self.conv1 = conv1x1(inplanes, planes)
    self.bn1 = nn.BatchNorm2d(planes)
    self.conv2 = conv3x3(planes, planes, stride)
    self.bn2 = nn.BatchNorm2d(planes)
    self.conv3 = conv1x1(planes, planes * self.expansion)
    self.bn3 = nn.BatchNorm2d(planes * self.expansion)
    self.relu = nn.ReLU(inplace=True)
    self.downsample = downsample
    self.stride = stride

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 += identity
    out = self.relu(out)

    return out

Batch Normalization

  • Batch Normalization(批归一化)是深度学习中一种重要的归一化技术,由 Sergey Ioffe 和 Christian Szegedy 在 2015 年提出。

  • **原paper中,BN被建议插入在(每个)ReLU激活层前面,因为ReLU激活函数的输出非负,不能近似为高斯分布。**但是后面实验表明,放在前后的差异似乎不大,甚至放在ReLU后还好一些。

  • 一个batch有几个图,比如batchsize为4就是4个图拼到一起然后计算均值方差然后归一化。

  • 如何做到每个批次的图片组合是随机的,并且同一张图片不会重复出现在同一个批次中:DataLoadershuffle=True 参数会在每个 epoch 开始时打乱数据顺序,从而保证不同 epoch 的批次组合不同。

  • 在推理(Inference)时,如果只有一张图片(无法组成完整的 Batch),可以通过以下方法处理:

    • 大多数现代深度学习框架(如 PyTorch、TensorFlow)允许直接输入单张图片,无需强制补全 Batch。
    • 全局统计量:PyTorch 的 model.eval() 会自动切换 BN 到推理模式。Batch Size=1,BN 层也能正常工作。input.unsqueeze(0)
    • 如果模型强制要求固定 Batch Size(某些老旧框架),可以通过以下方式补全:batch_tensor = input_tensor.repeat(4, 1, 1, 1)
  • image-20250601210943839

网络架构

Table 1

Figure 3