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이다.
어떻게 구현해야 할지 아직 공부하지 못 했다.
추후 더 학습해볼 필요성이 있다.
이번엔 직접 구현했지만 전이 학습으로 여러 모델을 사용해 볼 예정이다.
'AI > 컴퓨터 비전' 카테고리의 다른 글
이미지 데이터 증강 (Data augmentation) (0) | 2023.10.10 |
---|---|
IOU : 모델이 예측한 결과와 실측 Box가 얼마나 정확하게 겹치는 가를 나타내는 지표 (0) | 2022.12.04 |
NMS : 여러 Bounding Box 중 확실한 Box만 선택하는 기법 (0) | 2022.12.04 |
[Python, OpenCV 4로 배우는 컴퓨터 비전과 머신러닝] CH03 (0) | 2022.12.04 |
[Python, OpenCV 4로 배우는 컴퓨터 비전과 머신러닝] CH02 (0) | 2022.11.25 |