본문 바로가기
AI데이터 엔지니어, 새싹

60th_11_29(Wed)_GoogLeNet

by Leetora 2023. 11. 29.

❓CIFAR10 Dataset을 VGG로 학습시키면 성능이 떨어지는 이유

  • VGG19는 depth가 가장 큰 모델, 큰 이미지 데이터에 맞게 설계된 모델
  • CIFAR10은 크기가 작은 데이터기 때문에 VGG19 학습시키기 위한 데이터셋으론 적절하지 않음
  • Pooling Layer를 거칠 시 이미지가 절반으로 줄어들기 때문에 VGG19로 학습시켰을 때 높은 Accuracy를 달성하기 어려움
  • Pooling layer를 줄이거나, LeNet, VGG를 커스텀한다면 개선이 가능

1️⃣GoogLeNet

스크린샷 2023-11-29 오전 10.18.26.jpg

❓GoogLeNet?

  • Sparsely connected sturucture Network Model
  • = 넓게 포진되게 연결된 구조의 네트워크 모델

Layer가 깊어질수록 Backpropagation이 불안정(미분계수 소실)

  • backpropagation이 진행되는 동시에 병렬적으로 발생 유도
    Convolutional Layer를 통과할 때 모든 filter size가 같을 필요가 없다는 아이디어
  • 사이즈가 다른 filter를 동시에 convolution하는 건 어떨까?
  • Inception Model

❗️배경
depth 증가 = 많은 채널 수 사용(width 증가)
이에 따라 데이터 과학습(Overfitting)이 발생하기 쉬움

  • 많은 연산 자원을 필요로 하게 됨

2️⃣ Naive Inception Module

스크린샷 2023-11-29 오전 10.29.59.jpg

❗️Branch Implementation

  • 이전 레이어 Input은 (192, 100, 100)이라고 가정
  • branch를 지날 때 이미지 사이즈는 줄어들지 않는다
  • GoogLeNet에서는 이미지 사이즈가 유지되어야 함

branch1: kernel_size=1, padding=0, stride=1, output_size=(64, 100, 100)
branch2: kernel_size=3, padding=1, stride=1, output_size=(128, 100, 100)
branch3: kernel_size=5, padding=2, stride=1, output_size=(32, 100, 100)
branch3(max pooling): kernel_size=3, padding=1, stride=1, output_size=(192, 100, 100)

유의점

  • branch를 만들고 난 후 torch.concat을 통해 모든 레이어의 output을 더해준다
  • 배치사이즈가 없는 경우 axis = 0 / 있는 경우 axix=1을 기억할 것
    • 없을 시 torch.size는 (CHW) 형태를 취함.
    • axis=0이라면 channel 방향으로 붙이는 것
    • axis=1이라면 output_size는 (416, 100, 100)

✍🏻Branch Implementation#1. batch없는 3차 tensor

import torch import torch.nn as nn  #이전 레이어 input = (192, 100, 100)  class ConvBlock(nn.Module):   def __init__(self, in_channels, out_channels, kernel_size, padding=0, stride=1):     super(ConvBlock, self).__init__()      self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels,                   kernel_size=kernel_size, padding=padding, stride=stride)     self.relu = nn.ReLU()    def forward(self, x):     x = self.conv(x)     x = self.relu(x)     return x 

✍🏻Branch Implementation#2. batch가 있는 4차 tensor

class InceptionNaive(nn.Module):     def __init__(self, in_channels, ch1x1, ch3x3, ch5x5):         super(InceptionNaive, self).__init__()          self.branch1 = ConvBlock(in_channels=in_channels, out_channels=ch1x1, kernel_size=1, padding=0, stride=1)         self.branch2 = ConvBlock(in_channels=in_channels, out_channels=ch3x3, kernel_size=3, padding=1, stride=1)         self.branch3 = ConvBlock(in_channels=in_channels, out_channels=ch5x5, kernel_size=5, padding=2, stride=1)         self.branch4 = nn.MaxPool2d(kernel_size=3, padding=1, stride=1)              def forward(self, x):         out_branch1 = self.branch1(x)         out_branch2 = self.branch2(x)         out_branch3 = self.branch3(x)         out_branch4 = self.branch4(x)          out_inception = torch.concat([out_branch1, out_branch2, out_branch3, out_branch4], axis=1)          return out_inception.shape  BATCH_SIZE = 32 input_tensor = torch.randn(size=(BATCH_SIZE, 192, 100, 100)) model = InceptionNaive(in_channels=192, ch1x1=64, ch3x3=128, ch5x5=32) model.forward(input_tensor) 

3️⃣Inception module with dimension reductions

스크린샷 2023-11-29 오후 12.20.04.jpg


왜 (1, 1) Convolution을 추가했을까?

  1. 채널 수를 조정
    → 효과 : (1, 1) Convolution을 추가해줌으로써 이미지 사이즈는 변하지 않고 채널 수만 조정해줄 수 있음
  2. (1, 1) Convolution은 결국 Fully Connected 와 같은 역할을 함
    → 효과 : Non-Linear 을 추가하는 효과로 복잡도를 증가시킬 수 있음

✍🏻ConvBlock, Inception class 활용 branch shape 출력

import torch import torch.nn as nn  C = 192 H = 100 W = 100 input_tensor = torch.randn(size= (C,H,W))  branch1 = ConvBlock(in_channels=192, out_channels=64, kernel_size=1, padding=0, stride=1) out_branch1 = branch1.forward(input_tensor)  branch2 = ConvBlock(in_channels=192, out_channels=128, kernel_size=3, padding=1, stride=1) out_branch2 = branch2.forward(input_tensor)  branch3 = ConvBlock(in_channels=192, out_channels=32, kernel_size=5, padding=2, stride=1) out_branch3 = branch3.forward(input_tensor)  branch4 = ConvBlock(in_channels=192, out_channels=192, kernel_size=3, padding=1, stride=1) out_branch4 = branch4.forward(input_tensor)  out_inception = torch.concat([out_branch1, out_branch2, out_branch3, out_branch4],axis=0)  print(out_branch1.shape) print(out_branch2.shape) print(out_branch3.shape) print(out_branch4.shape) print(out_inception.shape) 
  • out_branch를 통해 각 branch의 shape를 알아볼 수 있다

✍🏻ConvBlock 활용 branch shape 출력 class

C,H,W  = 192, 100, 100  input_tensor = torch.randn(size= (C,H,W))  class InceptionNaive(nn.Module):   def __init__(self, in_channels, ch1x1, ch3x3, ch5x5):     super(InceptionNaive, self).__init__()      self.branch1 = ConvBlock(in_channels=192, out_channels=ch1x1, kernel_size=1, padding=0, stride=1)     self.branch2 = ConvBlock(in_channels=192, out_channels=ch3x3, kernel_size=3, padding=1, stride=1)     self.branch3 = ConvBlock(in_channels=192, out_channels=ch5x5, kernel_size=5, padding=2, stride=1)     self.branch4 = nn.MaxPool2d(kernel_size=3, padding=1, stride=1)    def forward(self, x):     out_branch1 = self.branch1(x)     out_branch2 = self.branch2(x)     out_branch3 = self.branch3(x)     out_branch4 = self.branch4(x)          x = torch.concat([out_branch1, out_branch1, out_branch3, out_branch4],axis=0)     return x     import torch    BATCH_SIZE = 32   H, W = 100, 100   channels = 192   input_tensor = torch.randn(size=(BATCH_SIZE, channels, H, W))   print("Input: ", input_tensor)    inception = InceptionNaive(in_channels=192, c1x1=64, c3x3=128, ch5x5=32)   output_tensor = inception(input_tensor)   print("Output: ", output_tensor) 
  • ConvBlock을 통해 branch object를 제작할 수 있음
  • out_channel을 개별로 선언해주어 코드의 효율성을 높인다

✍🏻GoogLeNet class 제작(ConvBlock, Inception class 활용)

class GoogLeNet(nn.Module):   def __init__(self):     super(GoogLeNet, self).__init__()      self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=7, stride=2, padding=3)     self.conv1_pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)          self.conv2 = nn.Conv2d(in_channels=64, out_channels=192, kernel_size=3, padding=1, stride=1)     self.conv2_pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)      self.inception3a = InceptionModule(in_channels=192, ch1x1=64, ch3x3red=96, ch3x3=128, ch5x5red=16, ch5x5=32, pool_proj=32)     self.inception3b = InceptionModule(in_channels=256, ch1x1=128, ch3x3red=128, ch3x3=192, ch5x5red=32, ch5x5=96, pool_proj=64)     self.inception3_pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)      self.inception4a = InceptionModule(in_channels=480, ch1x1=192, ch3x3red=96, ch3x3=208, ch5x5red=16, ch5x5=48, pool_proj=64)     self.inception4b = InceptionModule(in_channels=512, ch1x1=160, ch3x3red=112, ch3x3=224, ch5x5red=24, ch5x5=64, pool_proj=64)     self.inception4c = InceptionModule(in_channels=512, ch1x1=128, ch3x3red=128, ch3x3=256, ch5x5red=24, ch5x5=64, pool_proj=64)     self.inception4d = InceptionModule(in_channels=512, ch1x1=112, ch3x3red=144, ch3x3=288, ch5x5red=32, ch5x5=64, pool_proj=64)     self.inception4e = InceptionModule(in_channels=528, ch1x1=256, ch3x3red=160, ch3x3=320, ch5x5red=32, ch5x5=128, pool_proj=128)     self.inception4_pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)      self.inception5a = InceptionModule(in_channels=832, ch1x1=256, ch3x3red=160, ch3x3=320, ch5x5red=32, ch5x5=128, pool_proj=128)     self.inception5b = InceptionModule(in_channels=832, ch1x1=384, ch3x3red=192, ch3x3=384, ch5x5red=48, ch5x5=128, pool_proj=128)     self.inception5_pool = nn.AvgPool2d(kernel_size=7, stride=1, padding=0)      self.fc1 = nn.Linear(in_features=1024 , out_features=1000)     self.fc2 = nn.Linear(in_features=1000 , out_features=1000)           def forward(self, x):     x = self.conv1(x)     print(f"conv1: {x.shape}")     x = self.conv1_pool(x)     print(f"conv1_pool: {x.shape}")          x = self.conv2(x)     print(f"conv2: {x.shape}")     x = self.conv2_pool(x)     print(f"conv2_pool: {x.shape}")      x = self.inception3a(x)     print(f"inception3a: {x.shape}")     x = self.inception3b(x)     print(f"inception3b: {x.shape}")     x = self.inception3_pool(x)      x = self.inception4a(x)     print(f"inception4a: {x.shape}")     x = self.inception4b(x)     print(f"inception4b: {x.shape}")     x = self.inception4c(x)     print(f"inception4c: {x.shape}")     x = self.inception4d(x)     print(f"inception4d: {x.shape}")     x = self.inception4e(x)     print(f"inception4e: {x.shape}")     x = self.inception4_pool(x)     print(f"inception4_pool: {x.shape}")      x = self.inception5a(x)     print(f"inception5a: {x.shape}")     x = self.inception5b(x)     print(f"inception5b: {x.shape}")     x = self.inception5_pool(x)     print(f"inception5_pool: {x.shape}")      x = x.view(x.shape[0],-1)     x = self.fc1(x)     print(f"fc1: {x.shape}")     x = self.fc2(x)     print(f"fc2: {x.shape}")      return x  BATCH_SIZE = 32 H, W = 224, 224 channels = 3  input_tensor = torch.randn(size=(BATCH_SIZE, channels, H, W))  model = GoogLeNet() model(input_tensor) 

스크린샷 2023-11-29 오후 6.45.20.jpg

  • channel로 받아들이는 Branch -> features로 받아들이는 Linear이기 때문에 x = x.view(x.shape[0],-1) 로 데이터 형식을 바꾸어준다