ResNet(Residual Networks,残差网络)是由 Microsoft Research 提出的深度卷积神经网络架构,首次在 2015 年的 ImageNet 竞赛中提出,并取得了惊人的成功。ResNet 以其 残差学习 的思想为核心,成功地解决了随着网络深度增加,训练变得越来越困难的问题。它的提出解决了“加深网络”时出现的退化问题,开创了更加高效和深度的神经网络架构。
ResNet 的核心思想:残差学习
在传统的深层神经网络中,随着网络层数的增加,模型的训练和优化变得越来越困难。为了应对这个问题,ResNet 引入了 残差学习(Residual Learning) 的思想,通过引入跳跃连接(skip connection) 或 捷径连接(shortcut connection) 来帮助信息流在网络中更容易传播。
残差块(Residual Block)
ResNet 的基本单元是 残差块,它由两层或更多层卷积组成,并且在这些层之间加入了“跳跃连接”。这些跳跃连接直接将输入加到输出上,从而避免了深层网络训练中的梯度消失问题。
数学上,残差块的输出是:
y=F(x,{Wi})+x
其中:
- x 是输入
- F(x,{Wi}) 是通过卷积等操作计算得到的特征
- x 被直接加到 F(x,{Wi}) 上,作为网络的输出。
通过这种结构,网络学到的目标变成了学习输入和输出之间的“残差”(即差异),而不是直接学习输出。这种结构能够大大加速训练过程,并且使得更深的网络能够更容易地训练。
残差块的优点:
-
避免梯度消失问题: 跳跃连接允许梯度直接从更深的层反向传播到更浅的层,使得即使在非常深的网络中,梯度也不会迅速消失。
-
加深网络而不增加训练难度: 残差连接使得网络能够非常容易地加深,且不会因为层数增加而导致训练困难。这使得 ResNet 可以训练上百甚至上千层的深度网络。
-
提高性能: 由于有效地利用了更深的网络,ResNet 在许多计算机视觉任务中,尤其是图像分类、目标检测和图像分割等任务中,表现得非常优异。
ResNet 架构
ResNet 的基本结构由多个 残差块 堆叠而成,通常每个阶段的残差块数目会有所不同。
ResNet 网络结构示意图:
- 输入图像通常为 224×224×3。
- 初始阶段:一个大的卷积层,大小为 7×7,步长为 2,后跟一个最大池化层。
- 多个阶段:每个阶段由多个残差块组成,每个残差块由两个或三个卷积层组成(卷积、批归一化、ReLU 激活)。
- 输出阶段:全局平均池化层(Global Average Pooling),输出一个固定长度的向量。
- 全连接层:最后经过全连接层进行分类。
ResNet-50、ResNet-101 和 ResNet-152
ResNet 有不同的变种,其中最常见的是 ResNet-50、ResNet-101 和 ResNet-152。这些数字表示网络中的残差块数量。
- ResNet-50:由 50 层组成,使用了一个名为 Bottleneck 的残差块,它比传统的残差块更高效,适用于更深层的网络。
- ResNet-101:由 101 层组成,适用于更复杂的任务,能够提取更深层次的特征。
- ResNet-152:由 152 层组成,具有更深的层次,可以捕捉到更多的细节信息。
ResNet 中的 Bottleneck 结构
为了提高效率,ResNet-50 和更深的 ResNet 网络使用了 Bottleneck 结构,它通过减少中间层的通道数来减少计算量。传统的残差块通过两层卷积(3×3)进行特征提取,而 Bottleneck 结构将每个残差块分解成三部分:
- 一个 1×1 卷积用于降维
- 一个 3×3 卷积用于特征提取
- 一个 1×1 卷积用于恢复通道数。
这种结构通过减少计算量而不牺牲网络的表现力,使得 ResNet 在加深网络层数的同时不会导致计算开销过大。
ResNet 的应用场景
-
图像分类: ResNet 在 ImageNet 数据集上取得了突破性的成绩,成为图像分类任务中的标准架构。它能够处理非常复杂的图像分类任务,并且在大规模数据集上表现优异。
-
目标检测: ResNet 可以作为目标检测框架(如 Faster R-CNN、Mask R-CNN)的基础网络。由于其深层次特征提取的能力,ResNet 在检测小物体和复杂场景时表现更好。
-
图像分割: 由于 ResNet 能够提取丰富的图像特征,它也被广泛应用于图像分割任务中(如 FCN、U-Net),尤其是在医学图像分析中取得了显著成果。
-
迁移学习: ResNet 由于其良好的泛化能力,通常作为迁移学习的基础模型,在许多其他领域(如文本分类、语音识别等)中得到应用。
ResNet 的优缺点
优点:
-
深层网络的训练变得可行:
通过引入残差连接,ResNet 可以训练数百层的深度网络,极大提高了模型的表达能力。 -
减少梯度消失问题:
残差连接允许梯度直接传播,使得深度网络在训练时更加稳定。 -
高效且易于扩展:
ResNet 使用了高效的 Bottleneck 结构,可以在不增加计算复杂度的情况下加深网络。
缺点:
-
计算复杂性较高:
尽管 ResNet 的参数较少,但随着网络的加深,计算复杂度仍然较高,特别是在更深的网络(如 ResNet-152)上。 -
内存消耗较大:
深层的网络会导致更多的内存消耗,尤其是在训练阶段。
ResNet 的实现代码示例
以下是使用 PyTorch 加载和使用 ResNet-34 模型的代码示例:
import torch
import torch.nn as nn
class Conv(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, padding=0, groups=1, stride=1):
super(Conv, self).__init__()
self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, groups=groups)
self.bn = nn.BatchNorm2d(num_features=out_channels)
self.relu = nn.ReLU6()
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
x = self.relu(x)
return x
class ResNet_Block(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super(ResNet_Block, self).__init__()
if in_channels == out_channels and stride == 1:
self.fage = False
else:
self.fage = True
self.conv = Conv(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride)
self.conv1 = Conv(in_channels=in_channels, out_channels=out_channels, kernel_size=3, padding=1, stride=stride)
self.conv2 = Conv(in_channels=out_channels, out_channels=out_channels, kernel_size=3, padding=1)
def forward(self, x):
x1= self.conv1(x)
x1 = self.conv2(x1)
if self.fage:
x = self.conv(x)
return x+x1
class ResNet_mod(nn.Module):
def __init__(self, in_channels,out_channels,num,stride=1):
super(ResNet_mod, self).__init__()
self.num = num
self.resnet_block1 = ResNet_Block(in_channels=in_channels, out_channels=out_channels, stride=stride)
self.resnet_block2 = ResNet_Block(in_channels=out_channels, out_channels=out_channels, stride=1)
def forward(self, x):
x = self.resnet_block1(x)
for i in range(self.num-1):
x = self.resnet_block2(x)
return x
class MyNetwork(nn.Module):
'''一般在init中来构建网络算子层的初始属性'''
def __init__(self, num_class):
# 继承父类中的初始构造函数
super(MyNetwork, self).__init__()
self.conv = nn.Sequential(
Conv(in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
ResNet_mod(in_channels=64, out_channels=64,num=3, stride=1),
ResNet_mod(in_channels=64, out_channels=128,num=4, stride=2),
ResNet_mod(in_channels=128, out_channels=256,num=6, stride=2),
ResNet_mod(in_channels=256, out_channels=512,num=3, stride=2),
nn.AdaptiveAvgPool2d(output_size=1),
Conv(in_channels=512, out_channels=num_class, kernel_size=1)
)
self.flat = nn.Flatten()
self.softmax = nn.Softmax(dim=1) # 分类层
''' forward,必须叫这个名称。但是输入的参数,可以有多少。至少有1个input_X'''
def forward(self, input_X):
x = self.conv(input_X)
x = self.flat(x)
out = self.softmax(x)
return out
总结
ResNet 的创新之处在于引入了残差学习,解决了深层网络训练中的退化问题。它使得训练非常深的神经网络成为可能,同时通过跳跃连接保持了良好的训练稳定性。ResNet 作为一种高效且深度可扩展的架构,已经成为计算机视觉领域的重要模型,并广泛应用于图像分类、目标检测、图像分割等任务中。