当前位置:网站首页>ResNet 原理与代码复现
ResNet 原理与代码复现
2022-08-08 06:23:00 【悬鱼铭】
关注【CV算法恩仇录】
ResNet 模型原理
VGG 网络在特征表示上有极大的优势,但深度网络训练起来非常困难。为了解决这个问题,研究者提出了一系列的训练技巧,如 Dropout、归一化(批量正则化,Batch Normalization)。
2015年,何凯明为了降低网络训练难度,解决梯度消失的问题,提出了残差网络(Residual Network,ResNet)。

图1 梯度消失
ResNet 通过引入跳跃结构(skip connection),让 CNN 学习残差映射。残差结构(Bottleneck)如图 2 所示。

图2 残差结构
图2 的残差结构中,输入 x ,先是 1 x 1 卷积核,64 卷积层,最后是 1 x 1 卷积核,256 卷积层,维度先变小再变大。网络的输出为 H(x),如果没有引入跳跃结构分支, H(x) = F(x),根据链式法则对 x 求导,梯度变得越来越小。引入分支之后,H(x) = F(x) + x,对 x 求导,得到的局部梯度为 1,且当梯度进行反向传播时,梯度也不会消失。
图 3 是 ResNet 的结构,图中展示了 18 层、34 层、50 层、101 层、152 层框架细节,图中 “ x 2” 和 “ x 23 ” 表示该卷积层重复 2 次或 23 次。我们可以发现所有的网络都分成 5 部分,分别是 conv1、conv2_x、conv3_x、conv4_x、conv5_x。

图3 ResNet的结构
图 3 中 conv1 使用的是 7 x 7 的卷积核。当通道数一致时,卷积参数的计算量是 7 x 7 的卷积核 大于 3 x 3 的卷积核 ;当通道数不一致时,若通道数小,则可以采用大的卷积核。
对于第一个卷积层的通道数为 3 时,3 个 3 x 3 卷积核与 1 个 7 x 7 卷积核的感受野效果一样,但 1 个 7 x 7 却比 3 个 3 x 3 的参数多。在 VGG 19 层和 ResNet 34 层里,参数的计算量如图 4 所示,ResNet 34 层采用 1 个 7 x 7 的卷积核的计算量远小于 VGG 19 层采用 3 个 3 x 3 的卷积核。

图4 参数的计算量
图 3 中卷积层 conv2_x 和 conv3_x 的输出(output size)的大小分别为56 x 56 和28 x 28,如果卷积层 conv2_x 采用跳跃结构到 conv3_x,由于特征图的维度不一致,不能直接相加,此时的跳跃结构可采用卷积,以保证特征图的维度一致,特征图可以进行相加操作。
图 3 中最后一行的 FLOPs (floating-point operations) 指的是浮点运算次数,可以衡量框架的复杂度。框架的复杂度与权重和偏差(bias)有关。输入图像的高、宽、通道数分别用 H i n 、 W i n 、 D i n H_{in}、 W_{in}、D_{in} Hin、Win、Din表示;输出的特征图的高、宽、通道数分别用 H o u t 、 W o u t 、 D o u t H_{out}、 W_{out}、D_{out} Hout、Wout、Dout 表示;卷积核的宽和高分别用 F w 、 F h F_w、F_h Fw、Fh表示; N p N_p Np表示特征图一个点的计算量,其计算公式如下:
N p = F w × F h × D i n × D o u t + D o u t N_p = F_w \times F_h \times D_{in} \times D_{out} + D_{out} Np=Fw×Fh×Din×Dout+Dout
一次卷积的 FLOPs 的计算公式如下:
F L O P s : N p × H o u t × W o u t FLOPs: N_p \times H_{out} \times W_{out} FLOPs:Np×Hout×Wout
对于全连接层,输入的特征图会拉伸为1 x N i n N_in Nin 的向量,输出的向量维度为 1 x N o u t N_out Nout,则一次全连接层的 FLOPs 计算公式如下:
F L O P s : N i n × H o u t + W o u t FLOPs: N_{in} \times H_{out} + W_{out} FLOPs:Nin×Hout+Wout
可以使用工具包 Flops 在 PyTorch 中计算网络的复杂度。

图5 ResNet 34 与 VGG 16 网络的 FLOPs
ResNet 代码复现
ResNet 网络参考了 VGG 19 网络,在其基础上进行了修改,变化主要体现在 ResNet 直接使用 stride=2 的卷积做下采样,并且用 Global Average Pool 层替换了全连接层。
ResNet 使用两种残差结构,如下图 5 所示。左图对应的是浅层网络,当输入和输出维度一致时,可以直接将输入加到输出上。右图对应的是深层网络。对于维度不一致时(对应的是维度增加一倍),采用 1 x 1 的卷积,先降维再升维。
图5 残差结构
两种残差结构的代码实现如下,class BasicBlock(nn.Module) 指的是浅层网络 ResNet 18/34 的残差单元:
import torch
import torch.nn as nn
class BasicBlock(nn.Module):
# ResNet 18/34
expansion = 1
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.residual_function = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels * BasicBlock.expansion) # 相加之后再激活
)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != BasicBlock.expansion * out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_channels * BasicBlock.expansion)
)
def forward(self, x):
return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x)) # 此处是相加 和 ReLU 激活
class BottleNeck(nn.Module)指的是深层网络 ResNet 50/101/152 的残差单元:
class BottleNeck(nn.Module):
# ResNet 50/101/152
expansion = 4
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.residual_function = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, stride=stride, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels * BottleNeck.expansion, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels * BottleNeck.expansion)
)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels * BottleNeck.expansion:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels * BottleNeck.expansion, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels * BottleNeck.expansion)
)
def forward(self, x):
return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x))
ResNet 的整体结构如下:
from torch.nn.modules import padding
from torch.nn.modules.batchnorm import BatchNorm2d
class ResNet(nn.Module):
def __init__(self, in_chans, block, num_block, num_classes=100) -> None:
super().__init__()
self.block = block
self.in_channels = 64 # 输入通道
self.conv1 = nn.Sequential(
# nn.Conv2d(in_chans, 64, kernel_size=3, stride=1, padding=1,bias=False)
# 大卷积核,效果不好,用小的,第一层
nn.Conv2d(in_chans, 64, kernel_size=3, stride=1, padding=1,bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True)
)
self.pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.conv2_x = self._make_layers(block, 64, num_block[0], 1) # 最后一个是stride
self.conv3_x = self._make_layers(block, 128, num_block[1], 2)
self.conv4_x = self._make_layers(block, 256, num_block[2], 2)
self.conv5_x = self._make_layers(block, 512, num_block[3], 2)
self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
def _make_layers(self, block, out_channels, num_blocks, stride):
# 函数名前面带着下划线,是被保护的名字,不会通过【from module import *】导入该函数
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):
f1 = self.conv1(x)
f2 = self.conv2_x(self.pool(f1))
f3 = self.conv3_x(f2)
f4 = self.conv4_x(f3)
f5 = self.conv5_x(f4)
output = self.avg_pool(f5)
output = output.view(output.size(0), -1)
output = self.fc(output)
return f1, f2, f3, f4, f5, output
在 ResNet 类中的 forward( )函数规定了网络数据的流向:
(1)数据进入网络后先经过卷积(conv1),再进行下采样pool(f1);
(2)然后进入中间卷积部分(conv2_x, conv3_x, conv4_x, conv5_x);
(3)最后数据经过一个平均池化(avgpool)和全连接层(fc)输出得到结果;
中间卷积部分主要是下图中的蓝框部分,红框部分中的 [2, 2, 2, 2] 和 [3, 4, 6, 3] 等则代表了 bolck 的重复次数。

ResNet18和其他Res系列网络的差异主要在于 conv2_x ~conv5_x,其他的部件都是相似的。
def resnet18(in_chans):
return ResNet(in_chans, BasicBlock,[2, 2, 2, 2])
def resnet34(in_chans):
return ResNet(in_chans, BasicBlock,[3, 4, 6, 3])
def resnet50(in_chans):
return ResNet(in_chans, BottleNeck,[3, 4, 6, 3])
def resnet101(in_chans):
return ResNet(in_chans, BottleNeck,[3, 4, 23, 3])
def resnet152(in_chans):
return ResNet(in_chans, BottleNeck,[3, 8, 36, 3])

参考资料:
https://zhuanlan.zhihu.com/p/54289848
边栏推荐
- [BSidesCF 2020] Had a bad day1
- MongoDB自带的监控工具mongostat与mongotop
- 1.Mysql索引的原理
- Yii2使用composer安装MongoDB扩展
- 3.多线程两种实现方式的区别
- Chemical Industry Research: Current Situation and Scale Analysis of Organic Silica Gel Market
- Distributed voltage regulation using permissioned blockchains and extended contract net protocol to optimize efficiency
- @Autowired和@Resource区别
- 三、MATPLOTLIB数据可视化分析工具
- 2.Explain详解与索引优化原则
猜你喜欢

demo:数组方法-商品查询

轮播图-js

闪电十六鞭

方便面行业调研:预计2028年将达到436亿美元

Mac下按装php的MongoDB扩展

Chemical Industry Research: Current Situation and Scale Analysis of Organic Silica Gel Market

九.Redis 集群(cluster 模式)

简悦音乐播放器用到的相关技术点都在这里了(一)

Distributed voltage regulation using permissioned blockchains and extended contract net protocol to optimize efficiency

2022年天然橡胶市场供需与价格走势
随机推荐
课堂作业--黑客语解密
C语言详解童年游戏“9*9扫雷”
MySQL表的增删改查
微信记账小程序(附源码),你值得拥有!
PHP操作MongoDB的原生CURD方法
我的第一篇博客
cybox target machine wp
电动剃须刀市场现状研究分析与发展前景预测
玫瑰精油市场研究:目前市场产值超过23亿元,市场需求缺口约10%
MongoDB常用命令整理
Analysis of the status quo of the chemical industry: the polyolefin market consumption is nearly 200 million tons
九.Redis 集群(cluster 模式)
五.Redis_事务秒杀案例
Shell(二)
4.MySQL索引优化实战
课堂作业--验证码较验
Scrapy_Redis distributed processing
In 2022 China children's food market scale and development trend
MongoDB的备份与恢复
市场调研报告-食品添加剂行业产量为974万吨