Python之ResNet结构的解决办法

内容摘要
这篇文章主要为大家详细介绍了Python之ResNet结构的简单示例,具有一定的参考价值,可以用来参考一下。

感兴趣的小伙伴,下面一起跟随php教程的雯雯来看看吧!
1.ResNet的创新
文章正文

这篇文章主要为大家详细介绍了Python之ResNet结构的简单示例,具有一定的参考价值,可以用来参考一下。

感兴趣的小伙伴,下面一起跟随php教程的雯雯来看看吧!

1.ResNet的创新

现在重新稍微系统的介绍一下ResNet网络结构。 ResNet结构首先通过一个卷积层然后有一个池化层,然后通过一系列的残差结构,最后再通过一个平均池化下采样操作,以及一个全连接层的得到了一个输出。ResNet网络可以达到很深的层数的原因就是不断的堆叠残差结构而来的。

1)亮点

网络中的亮点 :

  • 超深的网络结构( 突破1000 层)
  • 提出residual 模块
  • 使用Batch Normalization 加速训练( 丢弃dropout)

但是,一般来说,并不是一直的加深神经网络的结构就会得到一个更好的结果,一般太深的网络会出现过拟合的现象严重,可能还没有一些浅层网络要好。

2)原因

其中有两个原因:

  • 梯度消失或梯度爆炸

当层数过多的时候,假设每一层的误差梯度都是一个小于1的数值,当进行方向传播的过程中,每向前传播一层,都要乘以一个小于1的误差梯度,当网络越来越深时,所成的小于1的系数也就越来越多,此时梯度便越趋近于0,这样梯度便会越来越小。这便会造成梯度消失的现象。

而当所成的误差梯度是一个大于1的系数,而随着网络层数的加深,梯度便会越来越大,这便会造成梯度爆炸的现象。

  • 退化问题(degradation problem)

当解决了梯度消失或者梯度爆炸的问题之后,其实网络的效果可能还是不尽如意,还可能有退化问题。为此,ResNet提出了残差结构来解决这个退化问题。 也正是因为有这个残差的结构,所以才可以搭建这么深的网络。

2.ResNet的结构

残差结构如图所示

作图是针对ResNet-18/34层浅层网络的结构,右图是ResNet-50/101/152层深层网络的结构,其中注意:主分支与shortcut 的输出特征矩阵shape。

一下表格为网络的一些主要参数

可以看见,不同层数的网络结构其实框架是类似的,不同的至少堆叠的残差结构的数量。

1)浅层的残差结构

需要注意,有些残差结构的ShortCut是实线,而有的是虚线,这两者是不同的。对于左图来说,ShortCut是实线,这表明输入与输出的shape是一样的,所以可以直接的进行相加。而对于右图来说,其输入的shape与输出的shape是不一样的,这时候需要调整步长stribe与kernel size来使得两条路(主分支与捷径分支)所处理好的shape是一模一样的。

2)深层的残差结构

同样的,需要注意,主分支与shortcut 的输出特征矩阵shape必须相同,同样的通过步长来调整。

但是注意原论文中:

右侧虚线残差结构的主分支上、第一个1x1卷积层的步距是2,第二个3x3卷积层的步距是1.

而在pytorch官方实现的过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,这样能够在ImageNet的top1上提升大概0.5%的准确率。

所以在conv3_x,conv4_x,conv5_x中所对应的残差结构的第一层,都是指虚线的残差结构,其他的残差结构是实线的残差结构。

3)总结

对于每个大模块中的第一个残差结构,需要通过虚线分支来调整残差结构的输入与输出是同一个shape。此时使用了下采样的操作函数。对于每个大模块中的其他剩余的残差结构,只需要通过实线分支来调整残差网络结构,因为其输出和输入本身就是同一个shape的。

对于第一个大模块的第一个残差结构,其第二个3x3的卷积中,步长是1的,而其他的三个大模块的步长均为2.在每一个大模块的维度变换中,主要是第一个残差结构使得shape减半,而模块中其他的残差结构都是没有改变shape的。也真因为没有改变shape,所以这些残差结构才可以直接的通过实线进行相加。

3.Batch Normalization

Batch Normalization的目的是使我们的一批(Batch)特征矩阵feature map满足均值为0,方差为1的分布规律。

其中:μ,σ_2在正向传播过程中统计得到γ,β在反向传播过程中训练得到

Batch Normalization是google团队在2015年论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。通过该方法能够加速网络的收敛并提升准确率。

具体的相关原理见:Batch Normalization详解以及pytorch实验

4.参考代码

代码如下:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<code>
import torch
import torch.nn as nn
 
# 分类数目
num_class = 5
# 各层数目
resnet18_params = [2, 2, 2, 2]
resnet34_params = [3, 4, 6, 3]
resnet50_params = [3, 4, 6, 3]
resnet101_params = [3, 4, 23, 3]
resnet152_params = [3, 8, 36, 3]
 
 
# 定义Conv1层
def Conv1(in_planes, places, stride=2):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_planes,out_channels=places,kernel_size=7,stride=stride,padding=3, bias=False),
        nn.BatchNorm2d(places),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
    )
 
 
# 浅层的残差结构
class BasicBlock(nn.Module):
    def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 1):
        super(BasicBlock,self).__init__()
        self.expansion = expansion
        self.downsampling = downsampling
 
        # torch.Size([1, 64, 56, 56]), stride = 1
        # torch.Size([1, 128, 28, 28]), stride = 2
        # torch.Size([1, 256, 14, 14]), stride = 2
        # torch.Size([1, 512, 7, 7]), stride = 2
        self.basicblock = nn.Sequential(
            nn.Conv2d(in_channels=in_places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(places * self.expansion),
        )
 
        # torch.Size([1, 64, 56, 56])
        # torch.Size([1, 128, 28, 28])
        # torch.Size([1, 256, 14, 14])
        # torch.Size([1, 512, 7, 7])
        # 每个大模块的第一个残差结构需要改变步长
        if self.downsampling:
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(places*self.expansion)
            )
        self.relu = nn.ReLU(inplace=True)
 
    def forward(self, x):
        # 实线分支
        residual = x
        out = self.basicblock(x)
 
        # 虚线分支
        if self.downsampling:
            residual = self.downsample(x)
 
        out += residual
        out = self.relu(out)
        return out
 
 
# 深层的残差结构
class Bottleneck(nn.Module):
 
    # 注意:默认 downsampling=False
    def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 4):
        super(Bottleneck,self).__init__()
        self.expansion = expansion
        self.downsampling = downsampling
 
        self.bottleneck = nn.Sequential(
            # torch.Size([1, 64, 56, 56]),stride=1
            # torch.Size([1, 128, 56, 56]),stride=1
            # torch.Size([1, 256, 28, 28]), stride=1
            # torch.Size([1, 512, 14, 14]), stride=1
            nn.Conv2d(in_channels=in_places,out_channels=places,kernel_size=1,stride=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            # torch.Size([1, 64, 56, 56]),stride=1
            # torch.Size([1, 128, 28, 28]), stride=2
            # torch.Size([1, 256, 14, 14]), stride=2
            # torch.Size([1, 512, 7, 7]), stride=2
            nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            # torch.Size([1, 256, 56, 56]),stride=1
            # torch.Size([1, 512, 28, 28]), stride=1
            # torch.Size([1, 1024, 14, 14]), stride=1
            # torch.Size([1, 2048, 7, 7]), stride=1
            nn.Conv2d(in_channels=places, out_channels=places * self.expansion, kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(places * self.expansion),
        )
 
        # torch.Size([1, 256, 56, 56])
        # torch.Size([1, 512, 28, 28])
        # torch.Size([1, 1024, 14, 14])
        # torch.Size([1, 2048, 7, 7])
        if self.downsampling:
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(places*self.expansion)
            )
        self.relu = nn.ReLU(inplace=True)
 
    def forward(self, x):
        # 实线分支
        residual = x
        out = self.bottleneck(x)
 
        # 虚线分支
        if self.downsampling:
            residual = self.downsample(x)
 
        out += residual
        out = self.relu(out)
 
        return out
 
 
class ResNet(nn.Module):
    def __init__(self,blocks, blockkinds, num_classes=num_class):
        super(ResNet,self).__init__()
 
        self.blockkinds = blockkinds
        self.conv1 = Conv1(in_planes = 3, places= 64)
 
        # 对应浅层网络结构
        if self.blockkinds == BasicBlock:
            self.expansion = 1
            # 64 -> 64
            self.layer1 = self.make_layer(in_places=64, places=64, block=blocks[0], stride=1)
            # 64 -> 128
            self.layer2 = self.make_layer(in_places=64, places=128, block=blocks[1], stride=2)
            # 128 -> 256
            self.layer3 = self.make_layer(in_places=128, places=256, block=blocks[2], stride=2)
            # 256 -> 512
            self.layer4 = self.make_layer(in_places=256, places=512, block=blocks[3], stride=2)
 
            self.fc = nn.Linear(512, num_classes)
 
        # 对应深层网络结构
        if self.blockkinds == Bottleneck:
            self.expansion = 4
            # 64 -> 64
            self.layer1 = self.make_layer(in_places = 64, places= 64, block=blocks[0], stride=1)
            # 256 -> 128
            self.layer2 = self.make_layer(in_places = 256,places=128, block=blocks[1], stride=2)
            # 512 -> 256
            self.layer3 = self.make_layer(in_places=512,places=256, block=blocks[2], stride=2)
            # 1024 -> 512
            self.layer4 = self.make_layer(in_places=1024,places=512, block=blocks[3], stride=2)
 
            self.fc = nn.Linear(2048, num_classes)
 
        self.avgpool = nn.AvgPool2d(7, stride=1)
 
        # 初始化网络结构
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # 采用了何凯明的初始化方法
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
 
    def make_layer(self, in_places, places, block, stride):
 
        layers = []
 
        # torch.Size([1, 64, 56, 56])  -> torch.Size([1, 256, 56, 56]), stride=1 故w,h不变
        # torch.Size([1, 256, 56, 56]) -> torch.Size([1, 512, 28, 28]), stride=2 故w,h变
        # torch.Size([1, 512, 28, 28]) -> torch.Size([1, 1024, 14, 14]),stride=2 故w,h变
        # torch.Size([1, 1024, 14, 14]) -> torch.Size([1, 2048, 7, 7]), stride=2 故w,h变
        # 此步需要通过虚线分支,downsampling=True
        layers.append(self.blockkinds(in_places, places, stride, downsampling =True))
 
        # torch.Size([1, 256, 56, 56]) -> torch.Size([1, 256, 56, 56])
        # torch.Size([1, 512, 28, 28]) -> torch.Size([1, 512, 28, 28])
        # torch.Size([1, 1024, 14, 14]) -> torch.Size([1, 1024, 14, 14])
        # torch.Size([1, 2048, 7, 7]) -> torch.Size([1, 2048, 7, 7])
        # print("places*self.expansion:", places*self.expansion)
        # print("block:", block)
        # 此步需要通过实线分支,downsampling=False, 每个大模块的第一个残差结构需要改变步长
        for i in range(1, block):
            layers.append(self.blockkinds(places*self.expansion, places))
 
        return nn.Sequential(*layers)
 
 
    def forward(self, x):
 
        # conv1层
        x = self.conv1(x)   # torch.Size([1, 64, 56, 56])
 
        # conv2_x层
        x = self.layer1(x)  # torch.Size([1, 256, 56, 56])
        # conv3_x层
        x = self.layer2(x)  # torch.Size([1, 512, 28, 28])
        # conv4_x层
        x = self.layer3(x)  # torch.Size([1, 1024, 14, 14])
        # conv5_x层
        x = self.layer4(x)  # torch.Size([1, 2048, 7, 7])
 
        x = self.avgpool(x) # torch.Size([1, 2048, 1, 1]) / torch.Size([1, 512])
        x = x.view(x.size(0), -1)   # torch.Size([1, 2048]) / torch.Size([1, 512])
        x = self.fc(x)      # torch.Size([1, 5])
 
        return x
 
def ResNet18():
    return ResNet(resnet18_params, BasicBlock)
 
def ResNet34():
    return ResNet(resnet34_params, BasicBlock)
 
def ResNet50():
    return ResNet(resnet50_params, Bottleneck)
 
def ResNet101():
    return ResNet(resnet101_params, Bottleneck)
 
def ResNet152():
    return ResNet(resnet152_params, Bottleneck)
 
 
if __name__=='__main__':
    # model = torchvision.models.resnet50()
 
    # 模型测试
    # model = ResNet18()
    # model = ResNet34()
    # model = ResNet50()
    # model = ResNet101()
    model = ResNet152()
    # print(model)
 
    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)</code>

以上就是pytorch实现ResNet结构的实例代码的详细内容,更多关于pytorch ResNet结构的资料请关注php教程其它相关文章!

注:关于Python之ResNet结构的简单示例的内容就先介绍到这里,更多相关文章的可以留意

代码注释

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!