AI/컴퓨터 비전

GoogleNet (InceptionNet) 정리해보기

HHB 2023. 10. 10. 23:22

Lenet-5를 이어 일반적인 CNN 구조가 정착됐다.

일반적인 CNN 구조라 하면 Conv-Layer > Normalication, Pooling > FC 구조이다.

 

ImageNet과 같은 데이터 크기가 큰 경우 모델을 더 Deep하게 Layer 수를 늘리고 Dropout을 이용해 overfitting을 피하도록 했다.

 

점점 AI 기술이 모바일이나 임베디드 상에서 효율적으로 활용하기 위해 연산량을 줄이도록 하는 알고리즘을 개발하게 됐다.

 

GoogleNet은 기존 AlexNet 보다 1/12 배의 연산량을 가진다.

Inception Module을 사용하는 것이 그 이유인데, 모듈 구조를 살펴보자.

 

대표사진 삭제

Inception module

일단 여기서 가장 중요한 것은 1x1 Convolution 이다.

 

 

 

1x1 Conv

보통 3x3 conv나 5x5의 필터를 사용하게 되면 갈 수록 위치 정보가 줄어든다.

1x1 필터는 이미지 픽셀 전체에 적용하여 공간을 뭉게는 것이 없기 때문에 위치 정보를 해치지 않는다.

 

게다가 다른 필터보다 연산량이 현격히 적다. 이게 1x1 conv를 사용하는 큰 이유

 

 

 
 

(왼) 1x1 conv(16channel) > 5x5 conv(32channel) / (오른) 5x5 conv(32channel)

이 그림을 보면

(왼) 1x1 으로 16 채널로 줄이고 5x5 필터로 32 채널로 증가시킨 모습이고

(오른) 5 x 5 필터로 32채널을 만든 모습이다.

 

연산량을 구해보면

(왼) 28 x 28 x 192 x 1 x 1 x 16 = 약 240만번 (1x1 conv로 채널 수 줄이고)

(왼) 28 x 28 x 16 x 5 x 5 x 32 = 약 1000만번 (5x5 conv로 32채널로 다시 증가)

(왼) 약 1240번 연산

 

이렇게 1x1으로 채널 수를 줄였다가 다시 키우는 것을 Bottleneck 구조라고 한다.

 

(오른) 28 x 28 x 32 x 5 x 5 x 192 = 약 1.2억번 (5x5 conv로 32채널)

 

10배 가량 연산량이 차이난다.

 

 

 

다시 Inception Module로 돌아와서

max-pool을 하면 이미지 크기가 줄어들 수 있는데 stride를 1로 주고 padding='same'을 했을 때 크기는 줄어들지 않고 유지된다.

 

1 x 1 Conv로 연산량을 크게 줄이고 padding과 stride를 1로 사용함으로써 크기를 맞춰주는 것이 중요하다.

마지막에 concatenation을 이용해 각 output을 합쳐줘야 하기 때문이다.

 

 
사진 삭제

사진 설명을 입력하세요.

Inception 모듈을 9번 사용한 모습이다.

각 모듈마다 채널수는 층이 깊어지면 깊어질 수록 많아진다.

 

각 블럭이 가진 색상의 의미는

파란색은 Convolution Layer, 빨간색은 Max-Pooling, 노란색은 Softmax, 초록색은 기타 함수이다.

 

 

 

 

Auxiliary Classifier

근데 중간 중간 따로 빠진 layer 들이 존재한다.

 

보조 분류기를 이용해 Vanishing Gradient 문제를 해결했다.

 

각 분류기에서 나온 loss 값을 최종 loss 값에 반영을 하는 것인데,

각 분류기에 0.3의 가중치를 더해 사용한다.

loss = output_loss + 0.3 * aux_loss_1 + 0.3 * aux_loss_2

 

 

Global Average Pooling (GAP)

이는 각 채널을 평균을 내어 하나의 벡터로 만들어 준다.

 

 
사진 삭제

GAP

기존 CNN 구조의 마지막의 FC 방식이 아닌 GAP 방식을 사용함으로써 파라미터 수를 크게 줄일 수 있다.

기존 CNN의 대부분의 파라미터 수는 FC 레이어에서 많이 발생한다.

 

 
사진 삭제
 
사진 삭제

오른쪽 표를 보고 채널 수 시각화

위 표를 보고 googlenet을 구현이 가능하다.

 

CIFAR10 데이터를 활용한 googlenet 실습.

from tensorflow.keras.layers import Conv2D, MaxPool2D, Concatenate, GlobalAveragePooling2D, Dropout, Dense, Input, Activation, BatchNormalization                                                                                                                                                                                                   
from tensorflow.keras.models import Model
from tensorflow.keras.datasets.cifar10 import load_data
from tensorflow.keras.callbacks import ReduceLROnPlateau
 
(x_train, y_train), (x_test, y_test) = load_data()

print(x_train.shape, y_train.shape)
print(x_test.shape, y_test.shape)

x_train, x_test = x_train / 255. , x_test / 255. #cifar10 데이터를 불러온 후 0~1사이 값으로 변환해줍니다.
 

cifar10 데이터를 불러온 후 0~1사이 값으로 변환해줍니다.

def inception_block(input_layer, n1x1, n3x3r, n3x3, n5x5r, n5x5, npool, maxpool=False):
  block_1x1 = Conv2D(n1x1, (1, 1), padding='same', activation='relu')(input_layer)

  block_3x3 = Conv2D(n3x3r, (1, 1), padding='same', activation='relu')(input_layer)
  block_3x3 = Conv2D(n3x3, (3, 3), padding='same', activation='relu')(block_3x3)

  block_5x5 = Conv2D(n5x5r, (1, 1), padding='same', activation='relu')(input_layer)
  block_5x5 = Conv2D(n5x5, (5, 5), padding='same', activation='relu')(block_5x5)

  block_pool = MaxPool2D((3, 3), padding='same', strides=1)(input_layer)
  block_pool = Conv2D(npool, (1, 1), padding='same', activation='relu')(block_pool)

  block_concat = Concatenate()([block_1x1, block_3x3, block_5x5, block_pool])

  if maxpool:
    block_concat = MaxPool2D((3, 3), strides=2, padding='same')(block_concat)

  return block_concat
 

위 함수는 inception module을 함수화 한 것이다.

googlenet은 inception module이 반복적으로 들어가 있기 때문에 함수를 이용했더니 편리했다.

 

def create_model():
  input = Input(shape=(32, 32, 3), name="Input_layer")

  x = Conv2D(32, kernel_size=(3, 3), padding='same', strides=1, activation='relu')(input)
  x = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')(x)

  x = Conv2D(96, kernel_size=(3, 3), padding='same', strides=1, activation='relu')(x)
  x = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')(x)

  inception3a = inception_block(x, 32, 48, 64, 8, 16, 16, False)
  inception3b = inception_block(inception3a, 64, 64, 96, 16, 48, 32, True)

  inception4a = inception_block(inception3b, 96, 48, 104, 16, 24, 32, False)
  inception4b = inception_block(inception4a, 80, 106, 112, 12, 32, 32, False)
  inception4c = inception_block(inception4b, 64, 64, 128, 12, 32, 32, False)
  inception4d = inception_block(inception4c, 56, 72, 144, 16, 32, 32, False)
  inception4e = inception_block(inception4d, 128, 80, 160, 16, 64, 64, True)

  inception5a = inception_block(inception4e, 128, 80, 160, 16, 64, 64, False)
  inception5b = inception_block(inception5a, 192, 96, 192, 24, 64, 64, False)

  x = GlobalAveragePooling2D()(inception5b)
  x = Dropout(0.4)(x)
  
  output = Dense(10, activation='softmax')(x)

  model = Model(inputs=input, outputs=output)

  return model
 

위 함수는 모델을 최종적으로 구성한 모습이다. inception_block에 각 인자들은 논문에 나온 표의 절반 값을 사용했다.

애초에 이 모델이 imagenet을 기준으로 만들었기 때문에 똑같이 하이퍼파라미터를 정할경우에 성능이 잘 안 나올 것으로 예상된다. 그리고 지금 정한 파라미터로 했을 때 결과도 잘 나오지 않았다. 추후 확인

model = create_model()

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.summary()
 

optimizer는 adam을 사용했고 loss는 one-hot-encoding을 하지 않아 sparse_categorical_crossentropy를 사용했다.

 

rlr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=5, mode='min', verbose=1)
 

만약 val_loss가 5번동안 줄지 않을 때 learning-rate에 0.2를 곱해 학습을 시킨다.

 

history = model.fit(x=x_train, y=y_train, batch_size=128, epochs=30, validation_split=0.15, callbacks=[rlr])
 

배치 크기는 128로 정했다. 왜냐하면 결과를 빠르게 보고 싶었기 때문.

Epoch 30: ReduceLROnPlateau reducing learning rate to 4.0000001899898055e-05.
333/333 [==============================] - 10s 30ms/step - loss: 0.0244 - accuracy: 0.9918 - val_loss: 2.0375 - val_accuracy: 0.7569 - lr: 2.0000e-04
 

결론은 과적합이 아주 잘 됐다.

추가적으로 BatchNormalization, Dropout을 사용해 과적합을 피할 수 있도록 구성할 수 있을 것이다.

또한 배치 크기도 줄여보고 데이터 증강이나

 

지금까지 구현한 내용을 보면 뭔가 빠진 것이 있다.

바로 Auxiliary Classifier이다.

 

어떻게 구현해야 할지 아직 공부하지 못 했다.

추후 더 학습해볼 필요성이 있다.

 

이번엔 직접 구현했지만 전이 학습으로 여러 모델을 사용해 볼 예정이다.

728x90
반응형
LIST