합성곱 신경망을 이용한 CIFAR-100데이터셋 이미지 분류

Intro

Google에서 제공하는 Colab환경에서 합성곱 신경망 ML모델을 구현해봤다.
Colab이 무료로 제공하는 12시간의 Tesla-K80 GPU의 성능을 시험해 보려는 목적도 있지만 그냥한번 CNN을 제대로 학습시키기 위한 내부 architecture의 효율적인 배치를 공부 혹은 실험을 해보려는 목적이 더 크다. Dataset은 CIFAR에서 교육용으로 무료로 제공하는 이미지 데이터로 학습시켜 보았다. 프레임워크는 tensorflow 1.6.0부터 제공하는 tf.keras를 이용했다.

Dataset

CIFAR은 CIFAR-10과 CIFAR-100, 총 2개로 나뉜다. 이름 그대로 CIFAR-10은 10개의 class들이 있고 CIFAR-100은 100가지 종류의 class들이 있다. 나는 CIFAR-100를 선택했다. 총 60000개의 이미지 데이터가 있었는데 50000개는 training용, 나머지 10000개는 validation으로 사용.

아래사진은 ‘학습용’, ‘확인용’으로 분류된 데이터 shape들이다.

before_reshape
위사진에서 나왔듯, x_train이라는 변수에 3 컬러 채널을 가진 32X32사이즈의 사진들이 50000개가 있음을 4차원 텐서로 표현되었다.

reshape
위 사진은 y_train과 y_test들을 one_hot벡터로 변환후의 결과들이다.

교육용으로 제공된 이미지셋인만큼 깨끗하게 이미지들이 preprocessing되어있다. 그래서 첫번째 컨볼루션 계층의 input_shape는 (32, 32, 3) 3차원 텐서이다.

Convolutional Neural Network

Kernel은 3X3사이즈로 모든 계층에 통일을 하였고 (사실 귀찮아서), stride도 1칸씩 convolve하도록 하였다 (이것도 귀찮아서). 합성곱 계층의 특성상 이미지가 줄어드는것을 방지하기위해 활성화 함수를 통과를한후 zero padding으로 감쌌다. 그리고 마지막 계층마다 downsampling을 해주는데 자주 쓰이는 maxpooling을 사용하였다. 아래는 코드.

model = tf.keras.models.Sequential()

checkpoint = tf.keras.callbacks.ModelCheckpoint(filepath='./cnn_trained_model.h5', monitor='val_acc')

model.add(Conv2D(128, kernel_size=(3, 3), input_shape=(32, 32, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(Conv2D(20, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(Conv2D(20, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(Conv2D(20, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(Conv2D(20, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(32, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(Conv2D(20, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(Conv2D(20, kernel_size=(3, 3), strides=(1, 1)))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(ZeroPadding2D())
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(BatchNormalization())
model.add(Dense(90))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dense(100))
model.add(BatchNormalization())
model.add(Activation('softmax'))

Overfitting을 방지하기위해 드롭아웃을 매층마다 넣었으며, FCN 레이어에는 internal input distribution을 같게하기위해 배치 정규화 레이어를 틈틈히 넣어줌. 그리고 마지막 출력 레이어에는 당연하게도 multiclass-classification에서 많이 쓰이는 softmax 활성화 함수를통해 아웃풋을 출력했다.

Training

model.compile(loss='categorical_crossentropy', optimizer=Adam(lr=1e-3), metrics=['accuracy'])
trainer = model.fit(x_train, y_train, epochs=2000, batch_size=150, validation_data=(x_test, y_test), callbacks=[checkpoint])

손실함수는 cross entropy를 사용했으며 ADAM optimizer를 이용해 경사 하상법을 구현했다. 학습률은 0.001, 배치 사이즈는 150개씩, epoch은 2000번이다. Keras 콜백에서 제공하는 ModelCheckpoint를 이용해 매 epoch마다 저장하고 ‘val_acc’(validation accuracy)를 모니터링한다.

Training Result

Epoch이 2000번에다 학습용 이미지 데이터가 50000개 이다보니 존나 오래걸린다. 근데 확실히 GPU로 학습하는것이 CPU를 사용했을때보다 steps per epoch이 몇배는 빠르다는것을 알았다. 12시간이면 충분할줄 알았지만 개뿔 그거보다 더 오래걸렸다. 12시간이되면 자동으로 assigned된 instance에서 튕기고 다시 시작해야했다. 하지만 ModelCheckpoint를 이용해 항상 백업을 해두었으므로 model을 다시 load하여 re-training을 하는방식으로 총 이틀을 학습시켰다.

아래는 그래프

training_result

그렇게 학습을 쳐시켜 놨는데 validation accuracy는 52%에서 왔다갔다했고 (slowly pickup양상은 보이긴했음) training accuracy는 꾸준히 오르는 양상을 보여 60%내외로 찍었다. 결국 overfitting 사인일듯하다. 다음번에는 여러 hyperparameter들을 여러번 조정을 해보고 합성곱 계층배치에 좀더 신경을 써야할듯 하다.

Conclusion

CIFAR-100데이터셋을 합성곱 신경망으로 이미지 분류하는 classifer를 구현해 보았다. 정작 꽤 긴 학습시간에도 불구하고 생각보다 accuracy에서 현저히 뒤떨어진 결과를 보고 다소 실망감이 컸지만 이 계기로 내가 구현한 모델의 문제점 몇가지를 추측해봤다.

  1. 합성곱 계층 개수
  2. 잦은 배치정규화
  3. 드롭아웃 prob이 적음
  4. kernel개수의 다양화
  5. 작은 배치사이즈

Convolutaion layer에서 추출된 feature map은 깊이가 더해질수록 선명한 feature들을 잡아낸다. 나는 총 9개의 합성곱 계층을 넣었다. 이것도 나는 충분하다고 생각하지만 (너무 많을수도 있겠다) 문제점 4번의 kernel개수가 문제일수도 있겠다는 느낌이 든다. 여러개의 kernel들이 input을 convolve하면서 특징들을 추출하는데 깊어질수록 좀 적게 세팅하지 않았나 싶다. 하지만 확실한것은 validation accuraccy자체는 굉장히 조금씩 이지만 오르긴 오른다. 아마 epoch을 한 10000정도로 올려버리면 천천히라도 아마 70%까지는 갔을지도 모르겠다. 얼마나 걸릴진 상상하기도 싫다 ㅅㅂ. Out of scratch로 계층들을 배치하지말고 여러 cnn 논문들을 참고를하며 좀더 carefully designed architecture을 고려해 보아야겠다.

Written on April 2, 2018