今天介绍的主干网络模型叫ResNet(残差网络),是一个在计算机视觉领域划时代的一个产物,它的诞生解决了深层卷积网络梯度消失的问题,使得卷积神经网络的网络深度得到了大幅度的提升,包括ResNet-50、ResNet-101等等;由微软研究院提出,ResNet通过引入残差块(residual blocks),使得网络能够更容易地学习到恒等映射,从而有效地加深网络层数。
一、模型介绍
ResNet 是 "Residual Network" 的缩写,由微软研究院的 Kaiming He、Xiangyu Zhang、Shaoqing Ren 和 Jian Sun 在 2015 年的论文《Deep Residual Learning for Image Recognition》中首次提出。该论文的主要贡献是通过引入残差块(residual blocks)来解决深层网络中的梯度消失问题,从而使得训练非常深的网络成为可能。
二、拓扑结构
ResNet网络最大的特点就是引入了一个叫做残差模块的结构,每个残差块包含两个或多个卷积层,并通过一个跳过连接将输入直接传递到输出,从而形成一个残差学习单元;通过堆叠这样的残差块,实现深层的卷积神经网络;
上图是一个ResNet网络的拓扑图,看图中的右边模型的整体架构就是很多个卷积块的堆叠,但是普通的卷积网络如果只是一味的堆叠卷积层,由于网络深度较大,卷积层在一层一层进行特征提取并向下传递的过程中会因为提取损失的原因,导致传递下去的特征信息会越来越少,就是在模型训练过程中的梯度消失现象;
基于此问题,ResNet模型创造性的提出了上图左边模型的跳跃连接的模型结构,俗称残差结构,通过跳跃的连接方式,将当前卷积前的特征与卷积后的特征进行融合,不仅学习到了新的特征信息,还有效的保留了原先特征张量内的特征信息,从而有效的降低了特征丢失的问题,实现了卷积层的深层堆叠;
在左边的残差模块中,包含了两个卷积层和激活层,当一个特征张量X输入残差块时,会分两个方向进行运算,第一个方向会经过两个串联的卷积层+BN层+ReLU激活层得到F(x),第二个方向会通过一个下采样层得到与F(x)维度相同的张量x,然后将F(x)与张量x的对应元素相加,之后再次经过BN层+ReLU激活层就完成了一次残差运算,多次残差块的堆叠就形成了ResNet网络,最后拼接FC全链接层+Softmax函数就可以实现模型的分类功能;
三、代码实现
根据上面的模型拓扑介绍,我们大概了解了ResNet模型的整体架构,在实际的应用中,ResNet则经常作为Backbone部分进行特征提取,下面我们就试着用pytorch实现一下去除FC层的ResNet-50的代码;
# Backbone-ResNet50
import torch
import torch.nn as nn
import torch.nn.functional as F
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels * 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 += identity
out = self.relu(out)
return out
class ResNet50Features(nn.Module):
def __init__(self, block, layers):
super(ResNet50Features, self).__init__()
self.in_channels = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
def _make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if stride != 1 or self.in_channels != out_channels * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_channels, out_channels * block.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_channels * block.expansion),
)
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels * block.expansion
for _ in range(1, blocks):
layers.append(block(self.in_channels, out_channels))
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)
return x
# 创建 ResNet-50 特征提取模块
def resnet50_features():
return ResNet50Features(Bottleneck, [3, 4, 6, 3])
# 示例用法
if __name__ == "__main__":
model = resnet50_features()
input_tensor = torch.randn(1, 3, 224, 224) # 假设输入图像大小为224x224
output = model(input_tensor)
print(output.shape) # 输出形状应为 (1, 2048, 7, 7)
代码解释:
-
Bottleneck 类:
- 定义了一个瓶颈块(Bottleneck Block),这是 ResNet-50 中使用的残差块。
expansion
设置为4,表示输出通道数是中间层通道数的4倍。downsample
是一个可选的下采样层,用于调整输入的维度。
-
ResNet50Features 类:
- 初始化方法中定义了初始的卷积层、批量归一化层、ReLU激活函数和最大池化层。
_make_layer
方法用于创建多个相同的残差块。forward
方法定义了前向传播的过程,输入图像依次通过各个层。
-
resnet50_features 函数:
- 创建并返回一个 ResNet-50 特征提取模块。
-
示例用法:
- 创建一个
ResNet50Features
实例,并传入一个随机生成的输入张量,输出形状为(1, 2048, 7, 7)
,这符合 ResNet-50 的特征提取部分的输出。
- 创建一个
四、模型优缺点
优点:
- 缓解梯度消失问题:通过跳过连接,残差块有效地解决了深层网络中的梯度消失问题,使得训练非常深的网络成为可能。
- 更好的收敛性能:ResNet 的残差学习机制使得网络能够更快地收敛,提高训练效率。
- 优秀的性能:ResNet 在多个基准数据集上表现出色,特别是在 ImageNet 数据集上的表现尤为突出。
- 广泛的适用性:ResNet 不仅适用于图像分类任务,还可以用于目标检测、语义分割等多种视觉任务。
缺点:
- 计算成本较高:虽然 ResNet 的结构设计使得训练变得更容易,但随着网络深度的增加,计算成本也会相应增加。
- 内存占用较大:由于网络深度较大,ResNet 在训练和推理过程中需要更多的内存资源。
- 超参数调优:虽然 ResNet 的结构相对简单,但在实际应用中仍然需要对超参数进行仔细调优,以获得最佳性能。
ResNet 通过引入残差块解决了深层网络中的梯度消失问题,使得训练非常深的网络成为可能。它的模块化设计和多尺度特征提取能力使其在多个视觉任务中表现出色。尽管计算成本较高,但 ResNet 仍然是计算机视觉领域中非常重要的模型之一。