ResNet
ResNet 由何恺明等人在2015年提出,通过引入残差块有效解决深度神经网络的梯度消失和梯度爆炸问题。ResNet50是这系列的具体模型,包含50层。
-
BasicBlock类:简单的残差块,适用于浅层的ResNet模块(18、34)。shortcut:捷径连接,用于将输入直接加到输出上。
forward方法:输入x经过conv1和bn1,通过ReLU激活函数得到中间结果,再经过conv2和bn2。将shortcut连接的结果加到当前输出上。最后通过ReLU激活函数得到最终输出。
-
Bottleneck类:用于较深的模型,如50、101的残差块,通过1*1卷积减少计算量。
结构:conv1 bn1 conv2 bn2 conv3 bn3 shortcut
-
ResNet类:通过堆叠多个残差块来构建网络。
结构:conv1 bn1 四个残差块层 avgpool fc
代码:
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicBlock(nn.Module):
expansion = 1
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 != self.expansion * out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, self.expansion * out_channels, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion * 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 Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_channels, out_channels, stride=1):
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, self.expansion * out_channels, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(self.expansion * out_channels)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != self.expansion * out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, self.expansion * out_channels, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion * out_channels)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(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.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, 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 * block.expansion
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 = self.avgpool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
def ResNet50():
return ResNet(Bottleneck, [3, 4, 6, 3])
ResNet-50常被用在骨干网络(Backbone)中,用于特征提取。除此之外,还包含颈部网络(Neck)和头部网络(Head)。
骨干网络(Backbone)作为基础特征提取器,从输入图像中提取底层到高层的各种特征 。比如通过卷积操作逐步提取图像的边缘、纹理等低级特征,以及物体形状、类别等高级语义特征,为后续的分割任务提供基础特征表示 。
颈部网络(Neck)常用于融合骨干网络不同层级、不同尺度的特征,使模型兼顾细节和语义信息 。或者扩大特征感受野,获取更广泛上下文信息等。
头部网络(Head)对颈部网络输出的特征进行处理,完成最终的像素级分类任务,即预测图像中每个像素所属类别 。
neck模块
neck部分介于骨干网络(backbone)和头部(head)之间,主要作用是:
-
特征融合:将高分辨率但低语义的浅层特征与低分辨率但高语义的深层特征统合,提升特征质量。
-
上下文增强:通过特定结构和操作,neck模块能扩大特征的感受野,捕捉更广泛的上下文信息。例如空洞卷积操作。
-
尺度适应性调整:使模型适应图像中不同尺度的目标。通过对不同尺度特征图的处理和融合,让模型再面对大目标和小目标时能有效提取特征进行分割。如小目标再浅层特征中体现明显,大目标在深层特征中更容易识别。neck模块整合这些信息,提升对不同尺度目标的分割性能。
常用的neck模块
-
特征金字塔网络(FPN) :通过自底向上和自顶向下的路径,将不同尺度的特征图进行融合。自底向上是骨干网络正常的特征提取过程;自顶向下将深度特征上采样后与浅层特征对应相加融合。利用不同尺度特征,提升模型对多尺度目标的分割能力。
-
路径聚合网络(PAN):在FPN基础上增加了自底向上的路径聚合,从低层次到高层次传递定位信息,强化了不同尺度特征之间的信息流动。比如yolov8的PAN-FPN结构融合了骨干网络不同阶段的特征图。进一步增强特征表示能力,让模型能更好聚合和利用不同层级特征。
-
空洞空间金字塔池化(ASPP):使用不同空洞率(采样率)的扩展卷积并行提取特征,每个分支的空洞卷积有不同感受野,能捕捉不同尺度上下文信息,最后将这些特征融合。扩大了感受野,获取多尺度上下文信息。
-
全局上下文模块(GC):通过捕捉全局上下文信息,计算特征图中每个位置与全局特征的关联,对特征进行加权调整,突出重要区域特征。增强模型对全局信息的感知和利用。
head模块
head部分负责将骨干网络提取的特征转换为每个像素的类别预测结果。
卷积层
-
对骨干网络输出的特征图进行进一步的特征提取和转换,调整特征图的通道数以匹配类别数量。
-
在一些简单的语义分割网络中,可能会使用 1x1 卷积层将特征图的通道数调整为类别数,直接用于后续的像素分类
上采样层
-
骨干网络通常会对输入图像进行下采样,导致特征图尺寸变小。上采样层用于将特征图恢复到与输入图像相同的尺寸,以便进行逐像素的分类
-
方法:
-
双线性插值(Bilinear Interpolation):一种简单且常用的上采样方法,通过线性插值的方式增加特征图的尺寸。
-
反卷积(Transposed Convolution):也称为转置卷积,通过学习的方式进行上采样,可以在一定程度上恢复特征图的细节信息
-
空洞卷积
-
在不增加参数数量和计算量的情况下,扩大卷积核的感受野,从而捕捉更大范围的上下文信息,有助于提高语义分割的精度
金字塔池化模块
-
过在不同尺度上进行池化操作,提取不同尺度的上下文信息,然后将这些信息融合在一起,增强特征的表达能力。
-
在 PSPNet 中,使用了 PPM 模块,将特征图分别进行全局平均池化、1x1、2x2、3x3 和 6x6 的池化操作,然后将这些池化结果进行上采样并拼接在一起
-
apc_head:自适应金字塔上下文头部
-
aspp_head:空洞空间金字塔池化头部
-
lraspp_head:轻量化改进版空洞空间金字塔池化头部
-
psa_head: 金字塔场景解析头部,利用金字塔池化融合多尺度特征
注意力模块
-
通过学习不同位置和通道的重要性,增强特征图中重要区域的表达,抑制不重要区域的干扰,从而提高语义分割的性能
-
常见类型
-
通道注意力(Channel Attention):关注特征图中不同通道的重要性,对通道进行加权。
-
空间注意力(Spatial Attention):关注特征图中不同空间位置的重要性,对空间位置进行加权。
-
-
ann_head: 基于注意力机制的头部模块
-
da_head: 双注意力头部,结合通道和空间注意力
-
isa_head: 基于特定注意力或交互机制的头部
-
nl_head: 利用非局部操作捕获长距离依赖关系
全连接层
-
在一些早期的语义分割网络中,会使用全连接层将特征图转换为一维向量,然后进行分类。但由于全连接层会丢失空间信息,现在更多地使用卷积层来替代