ResNet(残差网络)是一种深度卷积神经网络结构,通过引入残差块(Residual Block)解决了深度网络中的梯度消失和梯度爆炸问题,使得可以训练非常深的网络。
使用PyTorch实现的ResNet-50模型
FixedBatchNorm类
定义FixedBatchNorm类,继承自nn.BatchNorm2d
,并在forward
方法中使用F.batch_norm
函数,这是为了固定批量归一化的行为。
class FixedBatchNorm(nn.BatchNorm2d):
def forward(self, input):
output = F.batch_norm(input, # 输入数据,即卷积层的输出
self.running_mean, # 训练过程中累积的样本均值,训练过程中被动更新,推理阶段用于标准化数据。
self.running_var, # 训练过程中累积的样本方差,训练过程中被动更新,推理阶段用于标准化数据。
self.weight,
self.bias,
training=False, # 推理阶段进行批量归一化,因此不需要计算新的均值和方差,而是使用之前训练时计算得到的self.running_mean和self.running_var。
eps=self.eps # eps是为了数值稳定性而添加到方差的小常量。这可以防止除以接近于零的方差,避免数值不稳定性的问题。
)
return output
计算标准化的值(normalized value):
F.batch_norm
操作在模型的卷积层之后,常常与激活函数一起使用,以促使模型更快地学习和更好地泛化。
Bottleneck类
定义Bottleneck类,继承自nn.Module
,表示ResNet中的残差块。
ResNet 中的基本构建块,它定义了残差网络中的瓶颈结构。瓶颈结构的主要目标是减少计算成本,同时提高网络的表示能力。
具体而言,Bottleneck
块在每个阶段(stage)的每个残差块中被重复使用。
class Bottleneck(nn.Module):
# ...
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None, dilation=1):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) # 1x1 卷积,用于减少输入特征图的维度(降维),其输出通道数为 planes
self.bn1 = FixedBatchNorm(planes) # Batch Normalization, 对 1x1 卷积的输出进行批量归一化。
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=dilation, bias=False, dilation=dilation) # 3x3 卷积,用于学习特征,而且由于通道数相对较小,计算成本相对较低,这也是为什么称之为“瓶颈”的原因。
self.bn2 = FixedBatchNorm(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) # 1x1 卷积,用于恢复输入特征图的维度(升维),其输出通道数为 planes * 4。
self.bn3 = FixedBatchNorm(planes * 4)
self.relu = nn.ReLU(inplace=True) # ReLU 激活函数:对归一化后的数据进行激活。
self.downsample = downsample #
self.stride = stride
self.dilation = dilation
主要作用是引入残差连接(residual connection),允许梯度更容易地通过网络传播。残差连接通过将输入直接加到块的输出中,有助于缓解梯度消失问题,并允许训练非常深的神经网络。
最终的output是将残差连接的结果传递给 ReLU 激活函数,这是为了引入非线性。在整个 Bottleneck
类中,expansion
参数设置为 4,表示输出通道数是输入通道数的 4 倍。
ResNet类
定义了整个 Residual Network(ResNet)的架构。ResNet 是一种深度神经网络架构,引入了残差块(Residual Block),旨在解决深层网络训练过程中的梯度消失和梯度爆炸问题。
定义ResNet类,继承自nn.Module,包含了ResNet的整体结构,包括多个残差块的堆叠。
在ResNet类的构造函数中,定义了模型的各个组成部分,包括卷积层、批量归一化层、激活函数等。
class ResNet(nn.Module):
def __init__(self, block, layers, strides=(2, 2, 2, 2), dilations=(1, 1, 1, 1)):
self.inplanes = 64 # 初始通道数=64
super(ResNet, self).__init__()
'''初始卷积层'''
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size, stride=strides[0], padding,
bias=False)
self.bn1 = FixedBatchNorm()
self.relu = nn.ReLU(inplace=True)
'''池化层'''
self.maxpool = nn.MaxPool2d(kernel_size, stride=2, padding=1)
'''四个阶段,每个阶段由多个 Bottleneck 块组成。Bottleneck 块的数量由 layers 参数决定。输入通道数和输出通道数随着阶段的增加而增加。'''
# 主要用于捕捉输入数据中的低级特征,例如边缘、颜色等。由于这个阶段位于网络的起始部分,对图像的原始特征进行初步处理和提取。
self.layer1 = self._make_layer(block, planes, layers[0], stride=1, dilation=dilations[0])
# 在第一阶段的基础上,进一步学习中级特征,如纹理、形状等。引入更多的Bottleneck块,逐渐增加网络的深度和复杂性,提高特征表示的抽象程度。
self.layer2 = self._make_layer(block, planes, layers[1], stride=strides[1], dilation=dilations[1])
# 学习更高级的语义特征,例如物体部分和局部特征。增加的深度有助于网络理解图像中更复杂的模式,对物体的更深层次的特征有更好的捕捉。
self.layer3 = self._make_layer(block, planes, layers[2], stride=strides[2], dilation=dilations[2])
# 在网络的最深层次上进行全局特征建模,包括整体物体的语义信息。通过更深的层次,网络能够捕捉图像中更全局、更高级的特征,从而提高对整个图像内容的理解。
self.layer4 = self._make_layer(block, planes, layers[3], stride=strides[3], dilation=dilations[3])
self.inplanes = 1024
定义_make_layer方法,用于构建每个阶段的残差块。
def _make_layer(self, block, planes, blocks, stride=1, dilation=1):
''' 如果stride不为1,或者self.inplanes不等于planes*block.expansion则创建一个下采样'''
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False), # 1*1卷积
FixedBatchNorm(planes * block.expansion), # 批量归一化
)
layers = [block(self.inplanes, planes, stride, downsample, dilation=1)]
'''更新self.inplanes以匹配当前阶段的输出通道数'''
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes, dilation=dilation))
'''将列表中的块组成一个序列,并返回这个序列作为阶段的输出'''
out_layer = nn.Sequential(*layers)
return out_layer
为了保持输入和输出的维度一致,当 stride 不等于 1 或者当前层的输入通道数不等于
planes * block.expansion
时,会引入下采样(downsample)操作。具体来说,当 stride 不为 1 时,卷积操作会导致特征图的尺寸减小,为了保持尺寸一致,需要引入下采样。同时,如果输入通道数与输出通道数不一致,也需要引入下采样,以便将输入的通道数调整到与输出通道数一致,从而能够正确地进行残差连接。
下采样的引入有助于保持网络中特征图尺寸的一致性,使得残差块能够正确地进行残差连接,从而更好地传播梯度并促进模型训练。这种设计也是 ResNet 架构成功的一个关键因素,有助于训练非常深层的神经网络。
定义前向传播方法forward
,描述了数据在模型中的流动。
def forward(self, x):
# ···
resnet50
函数
定义resnet50
函数,用于创建ResNet-50模型实例。如果pretrained
为True,还会加载预训练权重。
def resnet50(pretrained=True, **kwargs):
model = ResNet(Bottleneck, layers, **kwargs)
# 预训练模型
if pretrained
state_dict = torch.load(model_path)
# 删除不需要的全连接层权重
state_dict.pop('fc.weight', None)
state_dict.pop('fc.bias', None)
model.load_state_dict(state_dict)
return model
这样设计的 ResNet-50 模型是由论文"Deep Residual Learning for Image Recognition"中提出的,。这个结构的设计旨在在保持模型深度的同时,有效地捕捉和学习图像中的抽象特征。