神经网络-pytorch实现resnet网络

一、Resnet简述

1.1 出现背景

深度学习网络的深度对最后的分类和识别的效果有着很大的影响,所以正常想法就是能把网络设计的越深越好,但是事实上却不是这样,常规的网络的堆叠(plain network)在网络很深的时候,效果却越来越差了。其中原因之一即是网络越深,梯度消失的现象就越来越明显,网络的训练效果也不会很好。 但是现在浅层的网络(shallower network)又无法明显提升网络的识别效果了,所以现在要解决的问题就是怎样在加深网络的情况下又解决梯度消失的问题1。(具体细节,查看参考文献)

1.2 网络结构

Resnet网络由一个一个块(block)组成,简图如下所示1

1560084326963

对于不同深度的残差网络,每个块的内部结构大致相似,但是卷积的数量不同,网络结构也有所不同,如下图所示。但是不管是多少层的网络,大致可以分为6个小部分:conv1、conv2_x、conv3_x、conv4_x、conv5_x、Flops。在使用的过程中,通常只需要对conv1和Flops进行修改即可。

1560084037551

1.3 块结构

根据上面的结构,大致分为以下两种块结构,可以看到少于50层的时候左边的结构,大于等于50层的使用右边的结构。注意左边输入64通道,中间是64通道,输出也是64通道;右边输入是256通道,中间是64通道,输出是256通道,存在四倍的规律。

1560158436129

1.4 残差网络

块与块之间的结构如下图所示

1560163485192

实线的计算公式为:

虚线的计算公式为:(虚线内部由于有个下采样,所以特殊)

其中$w$是卷积操作,由于$f(x)$是下采样过的,$w$对$x$也进行一次下采样。

1.5 输入输出size

  • 输入:224*224 图片
  • 输出:根据上述图片,不同网络输出不一样。

二、代码设计思想

2.1 环境介绍

框架:pythorch

环境:python

系统:win10

2.2 设计思想

为了代码利用率高,观察各个深度的resnet组成,conv1、FLOPs结构不变,conv2_x、conv3_x、conv4_x、conv5_x中每个小块的结构类似。其中18-layer、34-layer的维度都为2,只是每个块的数量不同。50、101、152-layer的维度都为3,之间的不同同样再与不同块的数量。所以在设计上,设计两个基础网络(BaseNet1,BaseNet2),以实现不同层数的resnet实现。

通过查看参考文献1可知,例如conv2_x中一小块之间存在残差设计(3个网络即存在3个残差块),但是conv2_x和conv3_x之间由于存在池化网络(缩小输出为一半),所以这两个之间没有残差设计(代码实现过程需要注意)。

三、代码实现

3.1 基础网络的实现

使用率最多的3x3网络实现(用函数写着方便),其中in_planes是输入通道数,out_planes输出通道数,stride是卷积块每次移动的位置,kernel_size卷积块的大小,padding图片边缘补0(当卷即块大小为3,padding=1时,输出的图片size不变,因为方便做残差)

1
2
3
4
def conv3x3(in_planes, out_planes, stride=1):
"3x3 convolution with padding"
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)

基础网络BasicBlock实现,实现块如下图所示(输入通道不一定是64):

1560165985456

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class BasicBlock(nn.Module):
expansion = 1

def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)

self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.downsample = downsample
self.stride = stride

def forward(self, x):
residual = x

out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)

out = self.conv2(out)
out = self.bn2(out)

# 下采样的残差算法有单独的设计,前面有介绍
if self.downsample is not None:
residual = self.downsample(x)

out += residual
out = self.relu(out)

return out

基础网络Bottleneck实现,其结构如下图所示:

1560166157377

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Bottleneck(nn.Module):
expansion = 4 # 表示输出会是中间层的4倍

def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride

def forward(self, x):
residual = x

out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)

out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)

out = self.conv3(out)
out = self.bn3(out)

if self.downsample is not None:
residual = self.downsample(x)

out += residual
out = self.relu(out)

return out

resnet类的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000):
self.inplanes = 64
super(ResNet, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
self.avgpool = nn.AvgPool2d(7, stride=1)
self.fc = nn.Linear(512 * block.expansion, num_classes)

for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()

def _make_layer(self, block, planes, blocks, stride=1):
#downsample 主要用来处理H(x)=F(x)+x中F(x)和xchannel维度不匹配问题
downsample = None
#self.inplanes为上个box_block的输出channel,planes为当前box_block块的输入channel
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),
nn.BatchNorm2d(planes * block.expansion),
)

layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes))

return nn.Sequential(*layers)

def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)

x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)

x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)

return x

3.2 resnet 50的实现

1
2
3
4
5
6
7
8
9
10
def resnet50(pretrained=False, **kwargs):
"""Constructs a ResNet-50 model.

Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
"""
model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
if pretrained:
model.load_state_dict(model_zoo.load_url(model_urls['resnet50']))
return model

3.3 resnet 18的实现

1
2
3
4
5
6
7
8
9
10
def resnet18(pretrained=False, **kwargs):
"""Constructs a ResNet-18 model.

Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
"""
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
if pretrained:
model.load_state_dict(model_zoo.load_url(model_urls['resnet18']))
return model

3.4 说明

resnet 50和resnet 18除了基础Block不同,其他的完全一样。对于不同层数的网络,只需要根据最前的那张图修改ResNet的第二个参数即可。

参考文献

1. 常用网络结构:深度残差理解Resnet
-------------本文结束感谢您的阅读-------------
0%