深度学习⑦| ResNet
一、前言即背景
在深度学习的发展历程中,人们不断追求卷积神经网络(CNN)的深度。然而,在这期间的发展中,有一个棘手的问题摆在大家面前:当层数超过一定时,模型的性能反而下降。
这种情况不是因为过拟合,而是在方向传播时出现了梯度消失或梯度爆炸现象。
1.1 梯度消失 Gradient Vanishing
什么是梯度消失?
我们指导,深度神经网络训练靠的是反向传播,而方向传播靠的是 链式求导
所谓链式求导,其实就是反向的 一层一层梯度相乘
grad = (w1 * f1') *( w2 * f2')* ......
w 是第 i 层的权重, fi' 是第 i 层激活函数的导数
根据以上公式,我们可以知道,如果求出来的导数一直小于1,那么一直乘下去,要不了多久,grad 会无限小,就会被解释成0
如果你已经知道梯度消失的原理,那么梯度爆炸便容易理解,和上面相反,乘出来的结果越来越大,造成 grad 大小溢出。
我曾经介绍过的 AlexNet 和 VGG 结构都无法解决这一问题。
AlexNet博客:
https://blog.csdn.net/Wu_Deng_Sheng/article/details/157254935?spm=1001.2014.3001.5501
VGG博客:
https://blog.csdn.net/Wu_Deng_Sheng/article/details/157257594?spm=1001.2014.3001.5501
为了解决这一问题,2015年,何凯明等人提出了 ResNet(残差网络),改变了这一局面。
本文基于 PyTorch 手动实现 ResNet-18,以介绍它的整体框架。
二、实现步骤
2.1 环境配置
import torch
import torch.nn as nn
2.2 残差块
核心就是在输出时,再加上原来的 x,这样就可以有效缓解梯度消失现象。
![]()
根据上面的公式, y 对 x 求导,就会得到 F' + 1 ,这里的 +1 就很巧妙了,如果 F' 很小,接近0,要造成梯度消失的时候,这个 +1 便可以把梯度救回来,使其约等于1
class Residual_block(nn.Module):
def __init__(self, input_channels, out_channels, down_sample=False, strides=1):
super().__init__()
# 第一个卷积层:3x3卷积,保持或改变空间尺寸(通过strides)
self.conv1 = nn.Conv2d(input_channels, out_channels,
kernel_size=3, padding=1, stride=strides)
# 第二个卷积层:3x3卷积,保持空间尺寸
self.conv2 = nn.Conv2d(out_channels, out_channels,
kernel_size=3, padding=1, stride=1)
# 维度匹配,让输入输出的维度一样,这样才可以在后面相加
if input_channels != out_channels:
# 用1x1卷积调整x的通道数和空间尺寸
self.conv3 = nn.Conv2d(input_channels, out_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
# 批量归一化
self.bn1 = nn.BatchNorm2d(out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU()
def forward(self, X):
# 前向传播:卷积 -> BN -> ReLU -> 卷积 -> BN
out = self.relu(self.bn1(self.conv1(X)))
out = self.bn2(self.conv2(out))
# 如果需要维度匹配,对X做1x1卷积
if self.conv3:
X = self.conv3(X)
# 残差连接:F(x) + x,再通过ReLU
out += X
return self.relu(out)
2.3 ResNet-18 框架
ResNet-18 由 初始卷积层+4个残差块+平均池化+全连接 组成
class MyResNet18(nn.Module):
def __init__(self):
super(MyResNet18, self).__init__()
# 初始卷积层:224x224 → 112x112
self.conv1 = nn.Conv2d(3, 64, 7, 2, 3)
self.bn1 = nn.BatchNorm2d(64)
self.pool1 = nn.MaxPool2d(3, stride=2, padding=1) # 最大池化,进一步缩小尺寸
self.relu = nn.ReLU()
# 4个残差层r包含2个残差块
self.layer1 = nn.Sequential(
Residual_block(64, 64), # 不改变通道和尺寸
Residual_block(64, 64)
)
self.layer2 = nn.Sequential(
Residual_block(64, 128, strides=2), # 第一个块下采样(通道+128,尺寸/2)
Residual_block(128, 128)
)
self.layer3 = nn.Sequential(
Residual_block(128, 256, strides=2), # 下采样
Residual_block(256, 256)
)
self.layer4 = nn.Sequential(
Residual_block(256, 512, strides=2), # 下采样
Residual_block(512, 512)
)
# 平均池化:512x7x7 → 512x1x1
self.adv_pool = nn.AdaptiveAvgPool2d(1)
self.flatten = nn.Flatten() # 展平为向量
self.fc = nn.Linear(512, 1000)
def forward(self, x):
# 初始部分
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool1(x)
# 4个残差层
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
# 分类头
x = self.adv_pool(x)
x = self.flatten(x)
x = self.fc(x)
return x
三、总结
ResNet 的提出,对于模型深度的提升有了巨大帮助。
对 ResNet 的学习,我主要学习到了:
① 残差连接
② 如何实现维度匹配:通过1*1的卷积调整输入 x 的维度,以确保残差连接可以正常相加。
更多推荐
所有评论(0)