Pytorch_About_RESNET

RESNET 모델의 생성과정 살펴보기

모두를 위한 딥러닝 - 파이토치 강의 참고

  • CNN advanced model중 하나인 RESNET의 모델 생성과정을 소스코드를 보며 살펴보고자 한다.

  • RESNET에서 파이토치 소스코드를 볼 수 있으며 이를 기준으로 모델 생성 과정을 따라가보았다.

  • RESNET은 RESNET Class외에 BasicBlock과 Bottleneck class를 통해 모델이 생성된다.

  • RESNET 모델의 생성과정에서 downsample을 이용해 output의 shape을 맞춰주게 된다.

  • 예를 들어, BasicBlock에 stride=2 로 설정해 3x64x64의 이미지가 들어갔을때 아래의 forward함수를 살펴보자.

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

class BasicBlock(nn.Module):
expansion = 1
# stride값으로 2가 들어갔을 때를 살펴보자
def __init__(self, inplanes, planes, stride=2, downsample=None, groups=1,
base_width=64, dilation=1, norm_layer=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = norm_layer(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = norm_layer(planes)
self.downsample = downsample
self.stride = stride

# x로 3x64x64의 input이 들어갔을때 각 output의 shape을 생각해보자.
def forward(self, x):
identity = x
# identity shape = 3x64x64

out = self.conv1(x)
# out shape = 3x32x32
out = self.bn1(out)
out = self.relu(out)

out = self.conv2(out)
#out shape = 3x32x32
out = self.bn2(out)

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

# stride를 2로 적용하니 out의 shape과 identity의 shape이 맞지 않아서
# 둘을 아래와 같이 더해줄 수 없는 문제가 발생한다.
# 이러한 문젤르 해결해 shape를 맞춰주도록 사용하는게 downsample 이다.

out += identity

out = self.relu(out)

return out
  • 파이토치에서는 resnet18~152까지 지원하는데 이번에는 resnet50의 생성과정을 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
model = ResNet(block, layers, **kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls[arch],
progress=progress)
model.load_state_dict(state_dict)
return model

def resnet50(pretrained=False, progress=True, **kwargs):
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
**kwargs)
  • resnet50은 Bottleneck class를 ResNet class가 받으며 만들어지며 layers로 [3,4,6,3]의 리스트를 받는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ResNet(nn.Module):
.
.
.
def _forward_impl(self, x):
# See note [TorchScript super()]
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 = torch.flatten(x, 1)
x = self.fc(x)

return x
  • 기본적으로 앞의 conv1, bn1, relu, maxpool를 지난뒤 layer1~4까지를 통과하게 된다. 이때 각 layer는 VGG와 비슷하게 make_layer함수를 통해 이루어지게 되며 다음과 같은 값을 받게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ResNet(nn.Module):
# _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress, **kwargs)
def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
groups=1, width_per_group=64, replace_stride_with_dilation=None,
norm_layer=None):
.
.
.
# layers값으로 [3,4,6,3]의 리스트를 받았으므로 아래의 layer1~4는 각각 3,4,6,3을 받게된다.
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
# make_layer함수가 block를 Bottleneck으로, planes로 64 그리고 blocks로 3을 입력받게 된다.
  • make_layer에서 layer1이 생성되는 과정은 다음과 같다.
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

def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
norm_layer = self._norm_layer
downsample = None
previous_dilation = self.dilation
if dilate:
self.dilation *= stride
stride = 1
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
conv1x1(self.inplanes, planes * block.expansion, stride),
norm_layer(planes * block.expansion),
)

layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))

return nn.Sequential(*layers)

# layer1 = _make_layer(Bottleneck, 64, 3)
# Bottleneck.expansion = 4

1. (self.inplanes = 64) != (planes = 64) * (Bottleneck.expansion = 4) 이므로 downsample이 실행된다.
이때 downsample은 채널의 수를 맞춰주기 위해서 실행되는 과정이다.

2. 이제 layers 리스트에 block()이 하나씩 추가되게 되는데 처음으로 Bottleneck(64, 64, 1, downsample)이 추가된다.

layers = [
Bottleneck(64, 64, 1, downsample)
]

3. 그리고 self.inplanes값이 planes * block.expansion = 64 * 4 = 256으로 변한다.

4. 다음으로 blocks의 값이 3을 입력받았으므로 두번의 for loop를 돌며 두 개의 Bottleneck()이 추가된다.

layers = [
Bottleneck(64, 64, 1, downsample)
Bottleneck(256, 64)
Bottleneck(256, 64)
]
  • 위와 같은 과정을 통해 layer1이 만들어졌는데 실제 ResNet50을 불러서 위에서 계산한 값과 동일하게 Bottleneck()을 통해 생성되었는지 확인해 볼 수 있다.
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
import torchvision
resnet = torchvision.models.resnet50()
print(resnet)
#>>>
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
(conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(2): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
.
.
.
  • 실제 resnet50에서도 layer1에서 총 3개의 Bottleneck이 생성된것을 확인할 수 있다. 각 Bottleneck의 convolution layer값 또한 확인해 볼 수 있었다.

  • 위와 같은 방식으로 총 4개의 layer가 구성되며 각 layer의 깊이는 입력받는 blocks수로 결정된다.

  • 이와같은 ResNet모델의 기본 입력은 3x224x224이다. 하지만 우리가 입력하고자 하는 이미지의 크기가 다를 수 있는데 이와 같은 상황일 때 어떻게 모델을 수정하고 적용할 수 있는지 다음 포스트에서 살펴보고자 한다.

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×