Today
Total
Archives
05-09 10:28
관리 메뉴

tony9402

[Pytorch 구현] ResNet : Deep Residual Learning for Image Recognition 본문

개발기록/논문 구현

[Pytorch 구현] ResNet : Deep Residual Learning for Image Recognition

ssu_gongdoli 2021. 11. 18. 01:32
반응형

ResNet 논문 : https://arxiv.org/pdf/1512.03385.pdf

 

(해당 포스터는 논문을 읽고 직접 구현하는 능력을 기르기 위해 기록하는 글입니다. 딥러닝 모델의 특성보다 구현에 초점하여 작성했습니다.)

 

 

논문 파악

1. ResNet은 Residual Block을 사용한다.

 

 

 

2. ResNet 모델에는 두가지 Block을 기반으로 모델을 구축한다.

보통 왼쪽에 있는 block을 Basicblock으로 많이 불리는 것 같다.

2-layer block(왼)과 3-layer bottlenect block

 

 

3 . Convolution과 activation 사이에 Batch Normalization을 항상 적용

 

 

 

 

4. ResNet 18, 34, 50, 101, 152 모델 구조, ResNet-18,34는 Basicblock을, ResNet-50,101,152는 Bottleblock을 사용한다.

 

ResNet-$x$에서 $x$의 값은 다음과 같이 계산이 된다. 

 

$x$ = (conv1 layer) + (conv2_x layer) + (conv3_x layer) + (conv4_x layer) + (conv5_x layer) + (pool layer)

 

ex) ResNet 18 : $x = 1 + 2 \times 2 + 2 \times 2 + 2 \times 2 + 2 \times 2 + 1 = 18$

ex) ResNet 101 : $x = 1 + 3 \times 3 + 3 \times 4 + 3 \times 23 + 3 \times 3 + 1 = 101$

 

 

논문 구현

1. ResNet 18, 34에 쓰이는 Basic Block 구현

class BasicBlock(nn.Module):
    mul = 1
    def __init__(self, in_planes, out_planes, stride = 1):
        super(BasicBlock, self).__init__()

        self.conv1 = conv3x3(in_planes, out_planes, stride)
        self.conv2 = conv3x3(out_planes, out_planes, 1)

        self.bn1   = nn.BatchNorm2d(out_planes)
        self.bn2   = nn.BatchNorm2d(out_planes)

        self.shortcut = nn.Sequential()
        if stride != 1:
            self.shortcut = nn.Sequential(
                conv1x1(in_planes, out_planes, stride),
                nn.BatchNorm2d(out_planes)
            )

    def forward(self, x):
        out  = self.conv1(x)
        out  = self.bn1(out)
        out  = F.relu(out)
        out  = self.conv2(out)
        out  = self.bn2(out)
        out += self.shortcut(x)
        out  = F.relu(out)
        return out

 

 

 

2. ResNet 50, 101, 152에 쓰이는 Bottlenect Block 구현

class BottleNect(nn.Module):
    mul = 4
    def __init__(self, in_planes, out_planes, stride = 1):
        super(BottleNect, self).__init__()

        self.conv1 = conv1x1(in_planes, out_planes, stride)
        self.conv2 = conv3x3(out_planes, out_planes, 1, 1)
        self.conv3 = conv1x1(out_planes, out_planes * self.mul, 1)

        self.bn1   = nn.BatchNorm2d(out_planes)
        self.bn2   = nn.BatchNorm2d(out_planes)
        self.bn3   = nn.BatchNorm2d(out_planes * self.mul)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != out_planes * self.mul:
            self.shortcut = nn.Sequential(
                conv1x1(in_planes, out_planes * self.mul, stride),
                nn.BatchNorm2d(out_planes * self.mul)
            )

    def forward(self, x):
        out  = self.conv1(x)
        out  = self.bn1(out)
        out  = F.relu(out)
        out  = self.conv2(out)
        out  = self.bn2(out)
        out  = F.relu(out)
        out  = self.conv3(out)
        out  = self.bn3(out)
        out += self.shortcut(x)
        out  = F.relu(out)
        return out

 

 

3. ResNet 만들기

class ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes = 10):
        super(ResNet, self).__init__()

        # 7x7, 64 channels, stride 2 in paper
        self.in_planes = 64 

        # RGB channel -> 64 channels
        self.conv    = nn.Conv2d(3, self.in_planes, kernel_size = 7, stride = 2, padding = 3)
        self.bn      = nn.BatchNorm2d(self.in_planes)
        self.maxpool = nn.MaxPool2d(kernel_size = 3, stride = 2, padding = 1)

        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.linear  = nn.Linear(512 * block.mul, num_classes)

    def _make_layer(self, block, out_planes, num_block, stride):
        layers  = [ block(self.in_planes, out_planes, stride) ]
        self.in_planes = block.mul * out_planes
        for i in range(num_block - 1):
            layers.append(block(self.in_planes, out_planes, 1))

        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.conv(x)
        out = self.bn(out)
        out = F.relu(out)
        out = self.maxpool(out)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.avgpool(out)
        out = torch.flatten(out, 1)
        out = self.linear(out)
        return out

 

 

위 코드에서 layer1 ~ layer4 list에 넣어놓고 forward에서 for문으로 돌리면 될꺼 같다라는 생각으로 한번 해봤더니 에러가 났다. (난 최대한 귀찮은걸 피해가려고 하다보니 겪에된 에러였다.) 아래 코드가 내가 하고 싶어했던 코드 형식이다.

class ResNet(nn.Module):
    def __init__(~~~~): # 중요한 부분이 아님
    	# Something Here
        
        self.layers = []
        outputs, strides = [64, 128, 256, 512], [1, 2, 2, 2]
        for i in range(4):
            self.layers.append(self._make_layer(block, outputs[i], num_blocks[i], stride=strides[i]))
 		
        # Something ~
        
    def forward(self, x):
        # Something ~
        out = self.maxpool(out)
        for layer in self.layers:
            out = layer(out)
        # Someting ~

 

 

하지만 내가 생각했던 방식과 동일하게 코딩을 할 수 있다는 것을 갑자기 떠올라 nn.Sequential로 변경했더니 오류 없이 작동했다. 역시 프레임워크를 잘 알고 잘 활용하자... (아직 Pytorch 뉴비여서 나중에 작동방식을 조금 더 분석해봐야겠다.)

class ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes = 10):
        super(ResNet, self).__init__()

        # 7x7, 64 channels, stride 2 in paper
        self.in_planes = 64 

        # RGB channel -> 64 channels
        self.conv    = nn.Conv2d(3, self.in_planes, kernel_size = 7, stride = 2, padding = 3)
        self.bn      = nn.BatchNorm2d(self.in_planes)
        self.maxpool = nn.MaxPool2d(kernel_size = 3, stride = 2, padding = 1)
		
        # 적용한 부분
        ############################################
        _layers = []
        outputs, strides = [64, 128, 256, 512], [1, 2, 2, 2]
        for i in range(4):
            _layers.append(self._make_layer(block, outputs[i], num_blocks[i], stride=strides[i]))
        self.layers = nn.Sequential(*_layers)
        ############################################
        
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.linear  = nn.Linear(512 * block.mul, num_classes)

    def _make_layer(self, block, out_planes, num_block, stride):
        layers  = [ block(self.in_planes, out_planes, stride) ]
        self.in_planes = block.mul * out_planes
        for i in range(num_block - 1):
            layers.append(block(self.in_planes, out_planes, 1))

        return nn.Sequential(*layers)

    def forward(self, x):
        out = self.conv(x)
        out = self.bn(out)
        out = F.relu(out)
        out = self.maxpool(out)
        # 적용한 부분
        out = self.layers(out) # layer 1 -> 2 -> 3 -> 4
        out = self.avgpool(out)
        out = torch.flatten(out, 1)
        out = self.linear(out)
        return out

 

 

 

4. ResNet18부터 ResNet152까지 구축해놓기

def ResNet18():
    return ResNet(BasicBlock, [2, 2, 2, 2])

def ResNet34():
    return ResNet(BasicBlock, [3, 4, 6, 3])

def ResNet50():
    return ResNet(BottleNect, [3, 4, 6, 3])

def ResNet101():
    return ResNet(BottleNect, [3, 4, 23, 3])

def ResNet152():
    return ResNet(BottleNect, [3, 8, 36, 3])

 

 

결론

Pytorch를 이용해서 ResNet 논문을 참고하여 모델을 직접 구현해봤다. 처음이라 다른 블로그들, 공식 Pytorch 문서들을 분석해보면서 파악했다.

모델을 잘 만들었는지 확인하기 위해서는 간단하게 CIFAR10 데이터 셋을 이용해서 학습을 시켜보면 된다. 이 부분은 나중에 포스터로 정리해보겠다.

 

구현한 코드 : https://github.com/tony9402/pytorch_models/blob/main/src/models/resnet.py

 

반응형
Comments