0%

ResNet | ResNet-18

现在很多网络结构都是一个命名+数字,比如(ResNet18),数字代表的是网络的深度,也就是说ResNet18 网络就是18层的吗?其实这里的18指定的是带有权重的 18层,包括卷积层和全连接层,不包括池化层和BN层。下面先贴出ResNet论文中给出的结构列表。

ps: 看这篇博文之前,一定要看


参考资料


我们这里对 resnet-18 进行讲解。

还是要明确一点,18 包括

  • 卷积层
  • 全连接层

不包括

  • 池化层

  • BN 层

  • BN 就是批量归一化

  • RELU 就是激活函数

  • lambda x:x 这个函数的意思是输出等于输入

  • identity 就是残差

  • 1resnet block 包含2basic block

  • 1resnet block 需要添加2个残差

  • resnet block之间残差形式是1*1conv,在resnet block内部残差形式是lambda x:x

  • resnet block之间的残差用粗箭头表示,resnet block内部的残差用细箭头表示

  • 3*3conv s=2,p=1 特征图尺寸会缩小

  • 3*3conv s=1,p=1 特征图尺寸不变

来看一下 restnet-18 的结构图

红框的一共是 18 层。

我们直接贴一下代码。

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
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np


# 定义残差块ResBlock
class ResBlock(nn.Module):
def __init__(self, inchannel, outchannel, stride=1):
super(ResBlock, self).__init__()
# 这里定义了残差块内连续的2个卷积层
self.left = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=(1, 3), stride=stride, padding=(0, 1), bias=False),
nn.BatchNorm2d(outchannel),
nn.ReLU(inplace=True),
nn.Conv2d(outchannel, outchannel, kernel_size=(1, 3), stride=1, padding=(0, 1), bias=False),
nn.BatchNorm2d(outchannel)
)
self.shortcut = nn.Sequential()
if stride != 1 or inchannel != outchannel:
# shortcut,这里为了跟2个卷积层的结果结构一致,要做处理
self.shortcut = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(outchannel)
)

def forward(self, x):
out = self.left(x)
# 将2个卷积层的输出跟处理过的x相加,实现ResNet的基本结构
out = out + self.shortcut(x)
out = F.relu(out)

return out


class ResNet(nn.Module):
def __init__(self, ResBlock, num_classes=1000):
super(ResNet, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=(1, 3), stride=1, padding=(0, 1), bias=False),
nn.BatchNorm2d(64),
nn.ReLU()
)
self.layer1 = self.make_layer(ResBlock, 64, 2, stride=1)
self.layer2 = self.make_layer(ResBlock, 128, 2, stride=2)
self.layer3 = self.make_layer(ResBlock, 256, 2, stride=2)
self.layer4 = self.make_layer(ResBlock, 512, 2, stride=2)
self.fc = nn.Linear(15872, num_classes)

# 这个函数主要是用来,重复同一个残差块
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1)
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)

def forward(self, x):
# 在这里,整个ResNet18的结构就很清晰了
out = self.conv1(x)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = F.avg_pool2d(out, kernel_size=(1, 4))
out = out.view(out.size(0), -1)
out = self.fc(out).reshape((out.shape[0], 1, 1, 1000))
return out

里面具体多少层是 make_layer 来决定的。

1
2
3
4
5
6
7
8
# 这个函数主要是用来,重复同一个残差块
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1)
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)

这里面的图大致为「网上找的图,所以参数不匹配,结构是相通的」

请我喝杯咖啡吧~