赞
踩
论文地址:
Deep Residual Learning for Image Recognition
参考博客:
ResNet 网络是在 2015年 由微软实验室中的何凯明等几位大神提出,斩获当年ImageNet竞赛中分类任务第一名,目标检测第一名。获得COCO数据集中目标检测第一名,图像分割第一名。
COCO 是一个大规模的目标检测、分割的数据集,具有以下几个特点:
(1)目标分割 ;(2)图像情景识别 ;(3)超像素分割 ;
(4)拥有 330K 张图像(其中 >200K 张有标签);(5)150 万个实例对象 ;(6)80 个目标类别 ;
(7)91个物品类别 ;(8)每张图片有 5 个描述 ;(9)250K 张人的图像有关键点标注 ;
1.超深的网络结构(超过1000层)。
2.提出residual(残差结构)模块。
3.使用Batch Normalization 加速训练(丢弃dropout)。
在ResNet提出之前,所有的神经网络都是通过卷积层和池化层的叠加组成的。人们认为卷积层和池化层的层数越多,获取到的图片特征信息越全,学习效果也就越好。但是在实际的试验中发现,随着卷积层和池化层的叠加,不但没有出现学习效果越来越好的情况,反而两种问题:
(1)梯度消失和梯度爆炸
(2)退化问题
深度网络出现了退化问题:网络深度增加时,网络准确度出现饱和,甚至出现下降。
BN(Batch Normalization)层
来解决。残差网络 (ResNet)
。ResNet论文提出了residual
结构(残差结构)来减轻退化问题,下图是使用residual结构的卷积网络,可以看到随着网络的不断加深,效果并没有变差,而是变的更好了。(虚线是train error,实线是test error) 对于一个网络,如果简单地增加深度,就会导致 梯度弥散
或 梯度爆炸
,我们采取的解决方法是 正则化
;
随着网络层数进一步增加,又会出现模型退化问题,在训练集上的 准确率出现饱和甚至下降 的现象 。
通过利用内部的残差块实现跳跃连接,解决神经网络深度加深带来的模型退化问题:
在传统网络中采用的输入输出函数为:F(x)(output1)
= x(input)
;
在残差网络中利用残差模块使输入输出函数为:F(x)(output2)
= F(x)(output1)
+ x(input)
;
x(input)
直接跳过多层 加入到最后的输出 F(x)(output2)
单元当中,解决 F(x)(output1)
可能带来的 模型退化问题
。
浅层网络: 采用的是左侧的残差结构(18-layer、34-layer),左侧称为BasicBlock
;
深层网络: 采用的是右侧的残差结构(50-layer、101-layer、152-layer),右侧称为Bottleneck
;
(1)通过 1X1卷积 既能够改变通道数,又能大幅减少计算量 和 参数量;可以对比 34-layer
和 50-layer
发现它们的参数量分别为 3.6 X 10^9 和 3.8 X 10^9;
(2)先降后升为了主分支上输出的特征矩阵和捷径分支上输出的特征矩阵形状相同,以便进行加法操作。
注:CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度。
注意:搭建深层次网络时,采用三层的残差结构。
ResNet18
和ResNet34
采用的是BasicBlock,ResNet50
、ResNet101
和ResNet152
采用的是Bottleneck。
以下以 ResNet50
为代表进行介绍:
[3, 4, 6, 3]
个小block 组成 ;每个小block都有三个卷积操作 ;50layer
; 其中每个大的 block 里面都是由两部分组成:Conv Block
和 Identity Block
。
(1)Conv Block
:输入和输出维度不相同,不能串联,主要用于改变网络维度 ;
(2)Identity Block
:输入和输出维度相同,可以串联,主要用于加深网络层数 ;
ResNet50:[3, 4, 6, 3]可以表示为:
conv2_x
:Conv Block
+ Identity Block
+ Identity Block
conv3_x
:Conv Block
+ Identity Block
+ Identity Block
+ Identity Block
conv4_x
:Conv Block
+ Identity Block
+ Identity Block
+ Identity Block
+ Identity Block
+Identity Block
conv5_x
:Conv Block
+ Identity Block
+ Identity Block
ResNet50
结构框图:
参考:
理论+实践 | ResNet、ResNeXt好兄代码复现和解析
class ConvBN(nn.Module):
def __init__(self, in_channels, out_channels, **kwargs):
super(ConvBN, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
self.bn = nn.BatchNorm2d(out_channels)
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
return x
class BasicBlock(nn.Module): # 左侧的 residual block 结构(18-layer、34-layer)
# 如果是BasicBlock,每个小block的输入=输出
expansion = 1
def __init__(self, in_channels, out_channels, stride=1): # 两层卷积 Conv2d + Shutcuts
super(BasicBlock, self).__init__()
# 两个3x3卷积核
self.conv = nn.Sequential(
# 第一个cov是用来改变WxH的(stride可指定)
ConvBN(in_channels=in_channels, out_channels=self.expansion*out_channels,
kernel_size=3, stride=stride, padding=1),
nn.ReLU(inplace=True),
# 第二个conv的stride恒为1,不改变WxH
ConvBN(in_channels=self.expansion*out_channels, out_channels=self.expansion*out_channels,
kernel_size=3, stride=1, padding=1),
)
self.shortcut = nn.Sequential()
# Shortcuts用于构建 Conv Block 和 Identity Block
if stride != 1 or in_channels != self.expansion*out_channels:
self.shortcut = nn.Sequential(
# 卷积+BN,不激活
ConvBN(in_channels=in_channels, out_channels=self.expansion*out_channels,
kernel_size=1, stride=stride)
)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
out = self.conv(x)
out += self.shortcut(x)
return self.relu(out)
class Bottleneck(nn.Module): # 右侧的 residual block 结构(50-layer、101-layer、152-layer)
# 观察50-layer可以发现,各个block内部卷积核通道数是4倍的关系
expansion = 4
def __init__(self, in_channels, out_channels, stride=1): # 三层卷积 Conv2d + Shutcuts
super(Bottleneck, self).__init__()
# 1x1 -> 3x3 -> 1x1
self.conv = nn.Sequential(
# 第一个cov是用来降维的,减少参数量
ConvBN(in_channels=in_channels, out_channels=out_channels,
kernel_size=1),
nn.ReLU(inplace=True),
# 第二个conv是用来改变WxH的(stride可指定)
ConvBN(in_channels=out_channels, out_channels=out_channels,
kernel_size=3, stride=stride, padding=1),
nn.ReLU(inplace=True),
# 第三个conv用来升维
ConvBN(in_channels=out_channels, out_channels=self.expansion*out_channels,
kernel_size=1)
)
self.shortcut = nn.Sequential()
# Shortcuts用于构建 Conv Block 和 Identity Block
if stride != 1 or in_channels != self.expansion*out_channels:
self.shortcut = nn.Sequential(
# 卷积+BN,不激活
ConvBN(in_channels=in_channels, out_channels=self.expansion*out_channels,
kernel_size=1, stride=stride)
)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
out = self.conv(x)
out += self.shortcut(x)
return self.relu(out)
class ResNet(nn.Module):
def __init__(self, block, numlist_blocks, num_classes=2):
"""
Args:
block: 选用BasicBlock还是Bottleneck这两种残差结构
num_blocks: 针对不同数量的layers,有不同的组合,比如ResNet50为[3, 4, 6, 3]
num_classes:最终分类数量
"""
super(ResNet, self).__init__()
self.in_channels = 64
# 原始输入为229x229x3 -> 112x112x64
self.conv1 = ConvBN(in_channels=3, out_channels=64, kernel_size=7, stride=2) # conv1
# 112x112x64 -> 56x56x64
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True) # maxpool
self.layer1 = self._make_layer(block, 64, numlist_blocks[0], stride=1) # conv2_x
self.layer2 = self._make_layer(block, 128, numlist_blocks[1], stride=2) # conv3_x
self.layer3 = self._make_layer(block, 256, numlist_blocks[2], stride=2) # conv4_x
self.layer4 = self._make_layer(block, 512, numlist_blocks[3], stride=2) # conv5_x
self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 平均池化
self.linear = nn.Linear(2048, num_classes) # 线性层
self.relu = nn.ReLU(inplace=True)
def _make_layer(self, block, in_channels, num_blocks, stride):
# 虽然每个convn_x由多个block组成,但是其中只有某个block的stride为2,剩余的为1
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(block(self.in_channels, in_channels, stride))
# 经过某个convn_x之后,in_channels被放大对应expansion倍
self.in_channels = in_channels * block.expansion
return nn.Sequential(*layers)
def forward(self, x):
x = self.relu(self.conv1(x)) # conv1
x = self.maxpool(x) # maxpool
x = self.layer1(x) # conv2_x
x = self.layer2(x) # conv3_x
x = self.layer3(x) # conv4_x
x = self.layer4(x) # conv5_x
x = self.avgpool(x)
x = torch.flatten(x, 1)
out = self.linear(x)
return out
def make_net(net, num_classes):
if net == 'ResNet18':
return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)
if net == 'ResNet34':
return ResNet(BasicBlock, [3, 4, 6, 3], num_classes)
if net == 'ResNet50':
return ResNet(Bottleneck, [3, 4, 6, 3], num_classes)
if net == 'ResNet101':
return ResNet(Bottleneck, [3, 4, 23, 3], num_classes)
if net == 'ResNet152':
return ResNet(Bottleneck, [3, 8, 36, 3], num_classes)
def test():
ResNet50 = make_net('ResNet50', num_classes=2)
#创建模型,部署gpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
ResNet50.to(device)
summary(ResNet50, (3, 229, 229))
# test()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。