ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [PyTorchZerotoAll 내용정리] 11-1: GoogleNet(Inception Moudle) [Going deeper with Convolution] ILSVRC14, CVPR 2015)
    ML&DL 이야기/Pytroch 2023. 10. 10. 02:49

    CNN에 관한 몇몇 부분을 더 추가적으로 다루는 파트이다. 

    Inception Module

    (https://gaussian37.github.io/dl-concept-inception/ 를 추가로 참고하여 작성했습니다)

    논문 링크: https://arxiv.org/abs/1409.4842


    2014년 ILSVRC에서 우승한 모델인 Inception Model, 혹은 GoogLeNet 모델에 관해 알아보자(좋은 모델이지만 이후에 나온 ResNet에 밀려 잘 안보이는 슬픈 모델...)

     

    기본적인 아이디어는 CNN의 네트워크를 더 깊게 만드는 것이다. 

    CNN의 네트워크가 깊어지면 깊어질수록 더 다양한 필터를 사용하기에 성능이 일반적으로 좋아지지만...! 

    Overfitting, Vanishing Gradient Problem, Computation Cost 등 여러 문제들도 존재한다. 

    따라서 기존에는 ReLU와 Dropout을 이용하여 이를 해결하고자 하였으나, Dropout의 경우 컴퓨터의 연산에 대해서는 효율적인 접근방법이라고 할 수는 없었다(네트워크와 matrix가 모두 Sparse해지기에 계산 상에서는 비효율적이다, 이는 GPU에서의 행렬연산을 생각하면 쉽게 알 수 있다)

    과연 Deeper해지면 성능이 점점 더 좋아질까??

    Inception 모듈에서는 feature를 효율적으로 추출하기 위해 1*1, 3*3, 5*5 필터를 이용한 convolution 연산을 각각 수행한다. 

    3*3 max pooling도 수행하는데, 이들 모두의 width와 height가 유지되어야함으로 Pooling 과정에서 Padding을 추가해 준다. 

     

    Inception Module

    여기서 여러 필터를 사용한 이유는 input feature에서 의미있는 feature를 뽑아내기 위해서는 다양한 representation을 받아들일 수 있는 필터들이 필요하기 때문이다.

    • input feature의 어떤 특징이 있다고 할 때, 그 특징들과 필터 간의 correlation이 어떻게 분포되어 있는지 모르기 때문에 다양한 필터를 사용했다고 이해하면 된다
    • 이런 이유로 Inception을 개발할 당시에는 다양한 필터를 병렬적으로 사용하는 것이 좋다고 판단하여 위 그림 처럼 사용하게 되었다.
    •  convolution filter의 경우 이미지 내의 detail한 특성을 잡아낼 수 있고 MaxPooling 같은 경우 invariant(불변)한 특성을 잡아낼 수 있기 때문에, 이 두 종류를 같이 사용함(앞선 글의 Pooling 관련 글 참고)
    • 최종적으로 Convolution 연산과 Maxpooling을 concatenation으로 합하며 dense matrix 연산으로 표현하여 더 효율적인 연산을 이끌어냄
    • 합성에 있어서 padding을 추가하여 input과 output의 width와 height를 고정시킨다
    • 왼쪽을 1 x 1 x 3 크기의 feature라고 하고 오른쪽을 1 x 1 x 5 크기의 feature라고 한다면 1 x 1 x 3 feature의 width와 height가 같은 위치의 (e.g. (1, 1)) feature들을 이용하여 같은 위치의 1 x 1 x 5 feature를 만들어 낸다, 즉,  width, height 위치의 채널들이 서로 대응되므로 Fully Connected Layer의 성격도 띄게 됩니다. 물론 FC layer처럼 filter의 위치 속성을 없애지 않고 계속 유지한다는 장점이 있다

    CNN filter output에 관한 공식

     

    하지만 이렇게 계산하면 당연히 연산량이 많아지는데, 이 때문에 naive version은 잘 작동하지 않는다.

    그 이유는 convolution 결과와 max pooling한 결과를 concatenation하면 기존 보다 차원이 늘어나는 문제점이 발생했기 때문이다. 보다 자세히는 max pooling 연산의 경우 channel 수가 유지되지만 convolution 연산에 의해 더해진 channel로 인해 전체적인 computation 부하가 늘어나게 된다.(pooling에서는 width, height의 resolution은 감소하지만, concat하기 위해 크기를 다시 맞춤으로 채널은 감소하지 않는다)

    이를 해결하기 위해 1*1 Conv를 추가적으로 이용하여 채널의 수에 따른 파라미터 계산의 수를 더 줄이는 방향으로 설계하였다. 

     

    1X1 Conv layer의 필요성

    1*1 계산시 계산양이 매우매우 많이 감소함을 한눈에 볼수 있다

    1X1 Conv Layer를 사용하면 해당하는 이미지의 depth를 1로 줄인 것을 그대로 가져오는 계산임을 알 수 있다. 

    이를 이용해 해당 1X1 Conv Layer의 개수만큼 channel의 개수를 원하는 만큼 조절할 수 있다. 

     

    동시에 Computation cost가 매우 감소하는 것을 볼 수 있다.

    위의 그림을 보면 동일하게 192 depth를 가진 28*28 이미지를 32 depth로 줄이는 과정이다. 

    첫번째 과정에서는 5X5 kernel을 이용했고, 

    두번째 과정에서는 1x1 kernel을 한번 거친 후에 계산을 했다. 

    그 결과 operation cost가 10배이상 차이남을 확인 가능하다. 

     

    추가적으로 1x1 Conv 사용 후, 활성화 함수로 ReLU를 사용하여 모델에 비선형성을 증가시켜줄 수 있다. 이에 따라 더 복잡한 패턴을 잘 인식할 수 있게 만들 수 있다한다. 

    Final Form of Inception Module

    최종적으로 앞의 incpetion 모듈을 layer로 적용하여 완성시킨 것이 Inception V1(google net) 모델이다.

    Auxiliary Loss(보조 손실)

    Auxiliary Loss

    Auxiliary loss는 깊어진 Layer에서 발생하는 vanishing gradient problem을 해결하기 위해 적용된 트릭으로 중간 중간에 loss를 계산할 수 있는 layer을 추가적으로 만들어서 최종 loss에 반영한다.(이는 네트워크의 중간 층에서도 클래스 분류를 실시하여 직접 오차를 전달하기 때문에 가능하다)

    즉, label이 있으면 최종 output loss에서 이를 반영하여 계산한다. 논문에서는 해당 loss에 0.3의 가중을 주어 이용한다. 여기서 사용된 auxiliary loss는 training 때만 이용된다는 것도 주요한 특징이다.

    학습 시 신경망에서는 최종 단의 error을 역전파(back-propagation)을 시키면서 파라미터 값을 갱신한다.
    그런데 gradient 값들이 0 근처로 가게 되면, 학습 속도가 아주 느려지거나 파라미터의 변화가 별로 없어 학습 결과가 더 나빠지는 현상이 발생할 수 있다.
    활성함수로 sigmoid 함수를 쓰는 경우는 sigmoid 함수의 특성상 일부 구간의 제외하면, 미분값이 거의 0으로 수렴하기 때문에 출력 에러의 크기와 상관없이 학습 속도가 느려진다.
    Cross-entropy 함수를 사용하면 좀 더 개선은 되지만 본질적인 문제가 해결이 되는 것은 아니다.
    대세인 ReLU를 사용하면 Sigmoid나 cross-entropy를 사용할 때보다 많은 이점이 있지만, 여러 layer를 거치면서 작은 값들이 계속 곱해지다 보면, 0 근처로 수렴되면서 역시 vanishing gradient 문제에 빠질 수 있고, 망이 깊어질수록 이 가능성이 커진다.
    GoogLeNet에서는 이 문제를 극복하기 위해 Auxiliary classifier를 중간 2곳에 두었다. 학습을 할 때는 이 Auxiliary classifier를 이용하여 vanishing gradient 문제를 피하고, 수렴을 더 좋게 해주면서 학습 결과가 좋게 된다.
    이석중의 알기쉬운 인공지능, YTIMES청년신문

     

    추가적으로 2015년에 첫번째 저자가 발표한 논문에도 해당하는 얘기가 잠깐 나온다. 해당 논문에서는 Auxiliary classifier가 “Regularizer”와 같은 역할을 하며, 최종 단의 main classifier가 중간의 side branch가 batch-normalize 되었거나 drop-out layer를 갖고 있으면 결과가 더 좋아진다는 언급이 있다.

    ReThinking the Inception Architecture for Computer Vision, Christian Szergedy, Google Inc, 2015 (https://arxiv.org/pdf/1512.00567.pdf)

     

    Tensor Factorization 

    Tensor factorization은 행렬곱 연산 전 상태의 파라미터를 저장함으로써 행렬의 곱 이후에 늘어나는 파라미터의 갯수에 대비하여 파라미터 수를 적게 저장하는 방법이다. 

    간단히 행렬을 통해 예시를 들면 

    6개에서 9개로 늘어났다..!

    위 식을 보면 계산 전에는 파라미터가 6개였지만 계산 후에는 9개로 늘어났다. 이렇게 matrix 곲을 factorization하면 파라미터의 수를 줄일 수 있기에 이를 이용해 파라미터를 줄일 수 있다. 

    tensor factorization

    그림을 통해 보면 가장 아래 이미지는 5X5인데, 5X5 필터를 한번 거친다면 그 영역에 해당하는 ouput은 1X1로 감소하게 된다. 동시에 파라미터는 25개가 필요하다(5*5 연산).

     하지만 같은 연산을 위 그림처럼 5x5 영역에 3x3 convolution 필터를 stride = 1로 이동하면서 9번(width, height 방향으로 3칸씩 이동)에 convolution 연산을 하고 그 연산의 output인 3x3 영역의 feature를 다시 3x3 convolution 하게 되면 그 결과 또한 1x1 feature가 된다.

     

    이번에는 3x3 convolution을 2번 사용하게 되면 18개의 파라미터만을 사용하게 되어, 파라미터의 수가 줄어든다. 물론 연산량은 더 증가한다. 즉, 이런 트릭을 이용해 같은 크기의 output feature를 만드는데 필요한 파라미터의 수를 줄인다(1*1 kernel 사용 예시와 비슷한 느낌인듯 하다)

    Inception v2, v3 

    이런 tensor factorization 등의 최적화 기법들을 incpetion module을 이용한 Inception v1(Google Net)에 적용한 방법이 v2, v3이다.

    inception v2에 사용된 Inception module A(좌), inception v3에 나타나는 inception module B(우)

    v2에서 사용한 Inception module A는 위의 그림처럼 5*5연산 대신에 3X3을 두번 적용한 것이고, v3에서는 이를 더 최적화하여 1Xn, nX1 convolution을 적용한 moudle B도 사용된다(n=3을 넣어보자). 

     

    Resolution(해상도, 이미지의 데이터 차원수)을 줄이기 위한 방법에는 이전 장에서 작성했듯이 크게 2가지 방법이 있다. Convolution 연산 시 stride를 2 이상으로 가져가거나, Pooling을 하는 것이다.

    이 때, Convolution 연산의 stride를 적용하여 resolution을 줄이게 되면 연산량이 다소 많아지게 되고 반면 Pooling을 이용하여 resolution을 줄이면 Representational Bottleneck 이라는 문제가 발생하는데 이는 말 그대로 resolution이 갑자기 확 줄어들어서 정보를 잃게 되는 것이다(이에 따라 세부적인 detail 판명이 어려움).

     

    이런 이유로 Convolution 연산의 stride 적용과 MaxPooling을 병렬적으로 하는 방법을 적용하는 Grid Size Reduction 방법이 Inception Module C부터 전체적으로 반영이 되어고, Inception Module C는 더 깊게 가 아닌 더 넓게 concatenation하는 방법을 사용했다고 보면 된다. 

    모듈 내부에서 Convolution + stride나 Pooling이 깊게 적용되면 Representational Bottleneck문제가 더 악화되니 옆으로 쌓아보려는 시도라고 이해할 수 있다.

    해당 모듈은 Inception v3에서 output 단에 사용되었다. 

    Inception V3 아키택쳐

    앞에서 설명한 Inception Module A, B, C 그리고 Grid Size Reduction이 적용된 형태가 바로 Inception v3이다. 

     

    처음에 살펴본 Inception v1 GoogLeNet에서 적용된 Auxiliary Loss는 2개였는데 1개로 축소되었는데 사실상 효과가 크게 좋지 않았기 때문이라고 유추할 수 있으며, 현재 이런 Loss를 쓰는 네트워크는 거의 없다고 한다. 

    Code Implementation(Inception Module)

    코드 구현에 참고한 자료는 아래와 같다

    https://velog.io/@euisuk-chung/파이토치-파이토치로-CNN-모델을-구현해보자-GoogleNet편)

    ( https://roytravel.tistory.com/338 )

    • 처음 입력은 컬러 이미지라면  3채널을 입력받고,  흑백이미지라면 1을 받습니다

    각 파트별 코드는 다음과 같다. 

    1. Conv block

    convolution 연산이 지속적으로 사용됨으로 해당하는 conv block에 대한 코드를 class로 만든다. 

    이때 해당 구조는 convolution layer, batch normalization, ReLU가 뒤따르는 구조이다.

    (논문에서는  batch normalization이 사용되지 않았지만 이 부분을 추가했다)

    class ConvBlock(nn.Module):
        def __init__(self, in_channels, out_channels, **kwargs):
            super(ConvBlock, self).__init__()
            self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
            self.batchnorm = nn.BatchNorm2d(out_channels)
            self.relu = nn.ReLU()
    
        def forward(self, x):
            x = self.conv(x)
            x = self.batchnorm(x)
            x = self.relu(x)
            return x

    2. Inception Module A

    inception module

    inception module은 위의 그림에서 알수 있듯이 4갈래로 분리되어 계산된다. 

    따라서 이에 맞춘 branch 변수를 만들어 계산하였다. 

     

    각 branch에 해당하는 변수는 아래와 같다.

    1x1 convolution의 경우 kernel_size=1, stride= 1, padding=0
    3x3 convolution의 경우 kernel_size=3, stride= 1, padding=1
    5x5 convolution의 경우 kernel_size=5, stride= 1, pading=2
    max-pooling의 경우 kernel_size=3, stride=1, padding=1

    kernel_size는 아키텍처에 보이는 그대로와 같고, GoogLeNet에서는 stride를 공통적으로 1로 사용했다. 각각의 padding은 추후 네 개의 branch가 합쳐졌을 때의 크기를 고려하여 맞춰준다.

    class Inception(nn.Module):
        def __init__(self, in_channels, out1x1, mid3x3, out3x3, mid5x5, out5x5, pool_proj):
            super(Inception, self).__init__()
            self.branch1 = ConvBlock(in_channels, out1x1, kernel_size=1, stride=1, padding=0)
    
            self.branch2 = nn.Sequential(
                ConvBlock(in_channels, mid3x3, kernel_size=1, stride=1, padding=0),
                ConvBlock(mid3x3, out3x3, kernel_size=3, stride=1, padding=1))
    
            self.branch3 = nn.Sequential(
                ConvBlock(in_channels, mid5x5, kernel_size=1, stride=1, padding=0),
                # Here, kernel_size=3 instead of kernel_size=5 is a known bug.
                # Please see https://github.com/pytorch/vision/issues/906 for details.
                # ConvBlock(mid5x5, out5x5, kernel_size=3, stride=1, padding=1)
                ConvBlock(mid5x5, out5x5, kernel_size=5, stride=1, padding=2))
    
            self.branch4 = nn.Sequential(
                nn.MaxPool2d(kernel_size=3, stride=1, padding=1, ceil_mode=True),
                ConvBlock(in_channels, pool_proj, kernel_size=1))
    
        def forward(self, x):
            x1 = self.branch1(x)
            x2 = self.branch2(x)
            x3 = self.branch3(x)
            x4 = self.branch4(x)
            output = [x1, x2, x3, x4]
            return torch.cat(output, 1)

    3. Auxiliary Loss

    Auxiliary Loss

    Auxiliary Loss를 구현하기 위해 아래와 같이 InceptionAux라는 클래스를 구현하였다.  논문에 기술된 대로  1 x1 convolution의 output channel의 개수는128을 적용해주었으며, Fully connected layer의 unit은 1024,  dropout rate 0.7을 적용해주었다.  

    • An average pooling layer with 5×5 filter size and stride 3, resulting in an 4×4×512 output for the (4a), and 4×4×528 for the (4d) stage.
    • A 1×1 convolution with 128 filters for dimension reduction and rectified linear activation.
    • A fully connected layer with 1024 units and rectified linear activation.
    • A dropout layer with 70% ratio of dropped outputs.
    • A linear layer with softmax loss as the classifier (predicting the same 1000 classes as the main classifier, but removed at inference time).
    class InceptionAux(nn.Module):
        def __init__(self, in_channels, num_classes):
            super(InceptionAux, self).__init__()
            self.conv = ConvBlock(in_channels, 128, kernel_size=1)
            self.fc1 = nn.Linear(2048, 1024)
            self.fc2 = nn.Linear(1024, num_classes)
            self.dropout = nn.Dropout(p=0.7)
            self.relu = nn.ReLU(inplace = True)
    
        def forward(self, x):
            x = nn.functional.adaptive_avg_pool2d(x, (4, 4))
            x = self.conv(x)
            x = torch.flatten(x, 1)
            x = self.fc1(x)
            x = self.relu(x)
            x = self.dropout(x)
            x = self.fc2(x)
            return x
            x = self.fc2(x)
            return x

     

    4. GoogLeNet

    ConvBlock, Inception, InceptionAux를 활용하여 GoogLeNet 을 다음과 같이 구현할 수 있다.

    LocalRespNorm이 실제 논문 struture에는 포함되나 이에 관한 파라미터가 주어지지 않아 코드에는 작성하지 않았다. 추가로 다른 구현들에서도 해당 부분은 제거한 것으로 보인다.

    이는 batch norm을 conv net부분에서 하기 때문으로 예측된다. 

    (https://pytorch.org/vision/0.8/_modules/torchvision/models/googlenet.html

    https://github.com/pytorch/vision/blob/main/torchvision/models/googlenet.py를 참고하였다)

     

    input과 output layer

     

    Inception 모듈에 들어갈 모델 구성은 Table 1의 GoogLeNet 아키텍처를 참고하였다(size = (Height,Width, Channel))

    추가로 Auxiliary Classifier의 입력 차원 같은 경우는 논문에서 기술된 대로 512, 528을 적용해주었고, fully-connected layer 또한 1024개의 unit으로 설정해주었다

    밑의 표에서 “#3×3 reduce” and “#5×5 reduce” stands for the number of 1×1 filters in the reduction layer used before the 3×3 and 5×5 convolutions. -> 이부분은 1x1 filter를 적용한 layer를 의미함으로 주의하자.

    추가적으로 코드에서 변경된 부분은 AdaaptiveAvgPooling을 사용한 것이다. 

    데이터의 사이즈 변환에 따른 padding의 변경을 하지 않기 위해서 사용하였다. 

    또한 auxiliary loss를 train시에만 사용하기 위해 if문에 해당 조건을 추가하였다. 이는 계산과 반환 모두에서 적용된다.

    import torch
    import torch.nn as nn
    from torch.nn.modules.normalization import LocalResponseNorm
    from torch import Tensor
    from typing import Optional
    
    class GoogLeNet(nn.Module):
        def __init__(self, aux_logits=True, num_classes=1000):
            super(GoogLeNet, self).__init__()
            assert aux_logits == True or aux_logits == False
            self.aux_logits = aux_logits
            self.maxpool = nn.MaxPool2d(3, stride=2, padding=1)
    
            self.conv1 = ConvBlock(3, 64, kernel_size=7, stride=2, padding=3)
            self.conv2 = ConvBlock(64, 64, kernel_size=1, stride=1, padding=0)
            self.conv3 = ConvBlock(64, 192, kernel_size=3, padding=1)
    
    
    
            self.a3 = Inception(192, 64, 96, 128, 16, 32, 32)
            self.b3 = Inception(256, 128, 128, 192, 32, 96, 64)
    
    
            self.a4 = Inception(480, 192, 96, 208, 16, 48, 64)
            self.b4 = Inception(512, 160, 112, 224, 24, 64, 64)
            self.c4 = Inception(512, 128, 128, 256, 24, 64, 64)
            self.d4 = Inception(512, 112, 144, 288, 32, 64, 64)
            self.e4 = Inception(528, 256, 160, 320, 32, 128, 128)
    
            self.a5 = Inception(832, 256, 160, 320, 32, 128, 128)
            self.b5 = Inception(832, 384, 192, 384, 48, 128, 128)
    
            if aux_logits:
                self.aux1 = InceptionAux(512, num_classes)
                self.aux2 = InceptionAux(528, num_classes)
            else:
                self.aux1 = None
                self.aux2 = None
    
            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
            self.dropout = nn.Dropout(p=0.4)
            self.linear = nn.Linear(1024, num_classes)
    
    
    
        def transform_input(self, x: Tensor) -> Tensor:
            if self.transform_input:
                x_ch0 = torch.unsqueeze(x[:, 0], 1) * (0.229 / 0.5) + (0.485 - 0.5) / 0.5
                x_ch1 = torch.unsqueeze(x[:, 1], 1) * (0.224 / 0.5) + (0.456 - 0.5) / 0.5
                x_ch2 = torch.unsqueeze(x[:, 2], 1) * (0.225 / 0.5) + (0.406 - 0.5) / 0.5
                x = torch.cat((x_ch0, x_ch1, x_ch2), 1)
            return x
    
        def forward(self, x: Tensor):
            x = self.transform_input(x)
            x = self.conv1(x)
            x = self.maxpool(x)
            x = self.conv2(x)
            x = self.conv3(x)
            x = self.maxpool(x)
            x = self.a3(x)
            x = self.b3(x)
            x = self.maxpool(x)
            x = self.a4(x)
            aux1: Optional[Tensor] = None
            if self.aux1 is not None:
              if self.training:
                aux1 = self.aux1(x)
    
            x = self.b4(x)
            x = self.c4(x)
            x = self.d4(x)
    
            aux2: Optional[Tensor] = None
            if self.aux2 is not None:
              if self.training:
                aux2 = self.aux2(x)
    
            x = self.e4(x)
            x = self.maxpool(x)
            x = self.a5(x)
            x = self.b5(x)
    
            x = self.avgpool(x)
            x = x.view(x.size()[0], -1)
            x = self.dropout(x)
            x = self.linear(x)
    
            if self.aux_logits and self.training:
                return x, aux1, aux2
            else:
                return x

     

    최종적으로 학습 데이터는 데이터는 CIFAR10 데이터를 이용하였다. 

    여기서 주의해야할 것이 2가지가 있는데,

    1. 데이터를 resize하는 것이다. 우리는 model에서 224X224 이미지가 들어올 것이라고 가정했기에 꼭..! resize해줘야한다.

    2. train시에는 Auxiliary Loss를 사용한 loss값을 이용해야하기에 aux1 aux2 output x를 모두 이용한 total loss를 다시 계산해줘야한다. 그 식은 total x = 0.3loss(aux1) + 0.3loss(aux2) + loss(output x)이다. 

    또한 test시에는 aux를 사용하지 않고, 최종 output만 사용한다는 것에 주의하자.

    # Training settings
    batch_size = 100
    device = 'cuda' if cuda.is_available() else 'cpu'
    print(f'Training GoogLeNet-CIFAR10 Model on {device}\n{"=" * 44}')
    
    # Transform 정의
    # transform = transforms.Compose(
    #     [transforms.ToTensor(),
    #      transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
    transform = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor()
    ])
    
    # CIFAR10 Dataset
    train_dataset = datasets.CIFAR10(root='./CIFAR10_data/',
                                   train=True,
                                   transform=transform,
                                   download=True)
    
    test_dataset = datasets.CIFAR10(root='./CIFAR10_data/',
                                  train=False,
                                  transform=transform)
    
    # Data Loader (Input Pipeline)
    train_loader = data.DataLoader(dataset=train_dataset,
                                               batch_size=batch_size,
                                               shuffle=True)
    
    test_loader = data.DataLoader(dataset=test_dataset,
                                              batch_size=batch_size,
                                              shuffle=False)
    
    model = GoogLeNet(aux_logits=True, num_classes=10).to(device)
    model.to(device)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.002)
    
    
    def train(epoch):
        loss_arr = []
        model.train()
        for batch_idx, (data, target) in enumerate(train_loader):
            data, target = data.to(device), target.to(device)
            optimizer.zero_grad()
            output, aux1, aux2 = model(data)
            loss = criterion(output, target) + 0.3*criterion(aux1, target) + 0.3*criterion(aux2, target)
            loss.backward()
            optimizer.step()
            if batch_idx % 10 == 0:
                loss_arr.append(loss.cpu().detach().numpy())
                print('Train Epoch: {} | Batch Status: {}/{} ({:.0f}%) | Loss: {:.6f}'.format(
                    epoch, batch_idx * len(data), len(train_loader.dataset),
                    100. * batch_idx / len(train_loader), loss.item()))
    
    
    def test():
        model.eval()
        test_loss = 0
        correct = 0
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            # sum up batch loss
            test_loss += criterion(output, target).item()
            # get the index of the max
            pred = output.data.max(1, keepdim=True)[1]
            correct += pred.eq(target.data.view_as(pred)).cpu().sum()
    
        test_loss /= len(test_loader.dataset)
        print(f'===========================\nTest set: Average loss: {test_loss:.6f}, Accuracy: {correct}/{len(test_loader.dataset)} '
              f'({100. * correct / len(test_loader.dataset):.0f}%)')
    
    
    if __name__ == '__main__':
        since = time.time()
        for epoch in range(1, 10):
            epoch_start = time.time()
            train(epoch)
            m, s = divmod(time.time() - epoch_start, 60)
            print(f'Training time: {m:.0f}m {s:.0f}s')
            test()
            m, s = divmod(time.time() - epoch_start, 60)
            print(f'Testing time: {m:.0f}m {s:.0f}s')
    
        m, s = divmod(time.time() - since, 60)
        print(f'Total Time: {m:.0f}m {s:.0f}s\nModel was trained on {device}!')

     

    전체 코드는 아래 링크에 작성되어있다.

    https://colab.research.google.com/drive/1Xbe5uGLzXvrAs4zZfJqKz3gGldn6Y95-?usp=sharing 

     

    GoogleNet.ipynb

    Colaboratory notebook

    colab.research.google.com

     

    결과는 아래와 같다. 대략 80%의 정확도를 보인다. 

     

     

Designed by Tistory.