1.理解resnet
图1 残差块结构
图2 网络结构
根据图1和图2来理解一下resnet。
ResNet是用来解决什么问题的?:ResNet 的提出主要是为了解决深度神经网络训练中出现的退化问题。
退化问题?:当网络层数加深时,模型的性能并不一定会提高,甚至会因为梯度消失、 梯度爆炸等问题导致模型性能下降。这种现象不仅仅是过拟合的问题,而是随着网络 加深,优化变得更加困难。传统的深层网络(如 VGG 或 AlexNet)在超过一定深度(例 如 20 层以上)时,训练过程可能会使模型的表现变得更差。
为什么ResNet可以解决这个问题?:归功于残差块(ResNet中的核心模块),使得神经网络能够在更深的层数下保持良好的性能。
在传统卷积网络中,梯度消失是因为梯度在反向传播时不断被链式法则中的小数值缩小。网络的每一层学习的是直接从输入映射到输出的函数 H(x),但H(x)的表达可能过于复杂,使得网络难以找到合适的权重。
残差块引入了一条快捷连接(Shortcut Connection),它使得梯度可以绕过中间的非线性层直接传递回前面层。分析如下:
残差块的输出为:
假设损失函数为L,则:
根据
,有:
这里的“1”就是捷径连接,确保
很小(梯度可能消失),梯度仍然能通过“1” 被有效传递回去。即使残差块中的 F(x)表现不好,梯度也可以通过直接连接的路径流 回到前面的层
残差块的结构:包括两个卷积层,卷积核大小为3*3。
输入首先通过第一个卷积层、批归一化层、激活函数,然后经过第二个卷积层和批归一化层。之后输入与输出经过残差连接,最终ReLU激活得到输出。
快捷连接(Shortcut Connection)分为两种类型:
恒等连接(Identity Mapping):输入和输出形状相同,无需额外调整。
投影连接(Projection Shortcut):通过 1×1卷积调整通道数或特征图大小(当输入输出通道数不匹配,或特征图被下采样时)。
代码实现残差块,就是定义了一个ResidualBlock块,继承自nn.Module类,里面包括了init函数和forward函数。
init函数:一个残差块包含了两个卷积层,所以初始化参数是输入通道数in_channles和输出通道数的列表channels_list(里面分别是第一个卷积层的输出通道和第二个卷积层的输出通道),以及步长stride。接下来就是定义需要用的函数,利用nn.Conv2d定义卷积层conv1,卷积核大小设置为3,填充为1保证特征图大小变化是因为步长stride的变化。利用函数nn.BatchNorm2d定义批归一化层bn1,定义激活函数relu。接下来定义第二个卷积层conv2,这个卷积层保持特征图大小和通道数不变,所以填充为1,步长也设置为1(保证了特征图大小不变)。定义批归一化层bn2。最后利用nn.Sequential()定义函数shortcut,用于对输入x进行调整大小和通道数。如果输入x和F(x)和通道数不一样,或者第一个卷积层的stride不为1(特征图大小发生了变化),那么就需要在nn.Sequential()中定义一个卷积层和批归一化层,其中卷积层的目的就是用来将输入x和f(x)的通道和大小一样,所以卷积函数的参数为 nn.Conv2d(in_channles,channels_list[-1],1,stride,0,bias=False),设置卷积核大小为1以及输出通道数量channels_list[-1],确保输入x的通道是和f(x)的通道数量一样;因为卷积核是1*1,填充为0,设置步长为stride就保证x经过卷积得到的特征图大小和f(x)一样。
forward函数:对于传进来的参数x,先通过in_x=x保留一个备份,之后x依次通过conv1,bn1,relu,conv2,bn2得到out,之后in_x传入shortcut函数,对其特征图大小和通道数量进行规整,最后就是out加上in_x,对其那行relu激活,将结果返回。
残差块代码:
class ResidualBlock(nn.Module):
def __init__(self, in_channles, channels_list,stride):
super(Residual