Buomsoo Kim

케라스와 함께하는 쉬운 딥러닝 (11) - CNN 모델 개선하기 2

|

합성곱 신경망 5 - CNN 모델 개선하기 2

Objective: 케라스로 개선된 CNN 모델을 만들어 본다.

지난 포스팅에서 케라스로 deep CNN 모델을 만들어 보았지만, MNIST 데이터 셋에서 간단한 cnn 모델에 비해 오히려 학습이 잘 되지 않고, 정확도가 떨어지는 경향을 보여주었다.

이번 포스팅에서는 지난 포스팅의 깊은 cnn 모델의 학습 결과를 개선해 보고, 새로운 cnn 모델을 시도해 보자.

Deep CNN - 2

MLP 모델을 개선하기 위해 사용했던 방법들을 Deep CNN 모델을 개선하기 위해서도 적용해 보자.

  • 가중치 초기화(Weight initialization)
  • 배치 정규화(Batch Normalization)
  • 드랍아웃(Dropout)

MNIST 데이터 셋 불러오기

MLP에서도 사용했던 MNIST 데이터 셋을 불러온다. 그때와 다른 점이 있다면, 그때는 MLP 모델에 입력하기 위해 (28, 28, 1) 짜리 사이즈의 데이터를 flatten해 784차원의 1차원 벡터로 만들었다면, 여기에서는 3차원 이미지 데이터를 그대로 사용한다는 것이다.

import numpy as np
import matplotlib.pyplot as plt

from keras.datasets import mnist
from keras.utils.np_utils import to_categorical

(X_train, y_train), (X_test, y_test) = mnist.load_data()
# reshaping X data: (n, 28, 28) => (n, 28, 28, 1)
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], X_train.shape[2], 1))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], X_test.shape[2], 1))
# converting y data into categorical (one-hot encoding)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
(60000, 28, 28, 1), (10000, 28, 28, 1), (60000, 10), (10000, 10)  

모델 생성하기

기존의 deep CNN 모델과 구조는 동일하지만 학습을 개선하기 위해 위에서 제안한 세 가지 방법(가중치 초기화, 배치 정규화, 드랍아웃)이 활용되었다.

from keras.layers import BatchNormalization, Dropout
def deep_cnn_advanced():
    model = Sequential()

    model.add(Conv2D(input_shape = (X_train.shape[1], X_train.shape[2], X_train.shape[3]), filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))

    # prior layer should be flattend to be connected to dense layers
    model.add(Flatten())
    # dense layer with 50 neurons
    model.add(Dense(50, activation = 'relu', kernel_initializer='he_normal'))
    model.add(Dropout(0.5))
    # final layer with 10 neurons to classify the instances
    model.add(Dense(10, activation = 'softmax', kernel_initializer='he_normal'))

    adam = optimizers.Adam(lr = 0.001)
    model.compile(loss = 'categorical_crossentropy', optimizer = adam, metrics = ['accuracy'])

    return model
model = deep_cnn_advanced()
model.summary()

배치 정규화 레이어가 추가되면서 파라미터 개수가 미묘하게 늘었지만 큰 차이는 없다.

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_34 (Conv2D)           (None, 28, 28, 50)        500       
_________________________________________________________________
batch_normalization_7 (Batch (None, 28, 28, 50)        200       
_________________________________________________________________
activation_4145 (Activation) (None, 28, 28, 50)        0         
_________________________________________________________________
conv2d_35 (Conv2D)           (None, 28, 28, 50)        22550     
_________________________________________________________________
batch_normalization_8 (Batch (None, 28, 28, 50)        200       
_________________________________________________________________
activation_4146 (Activation) (None, 28, 28, 50)        0         
_________________________________________________________________
max_pooling2d_19 (MaxPooling (None, 14, 14, 50)        0         
_________________________________________________________________
conv2d_36 (Conv2D)           (None, 14, 14, 50)        22550     
_________________________________________________________________
batch_normalization_9 (Batch (None, 14, 14, 50)        200       
_________________________________________________________________
activation_4147 (Activation) (None, 14, 14, 50)        0         
_________________________________________________________________
conv2d_37 (Conv2D)           (None, 14, 14, 50)        22550     
_________________________________________________________________
batch_normalization_10 (Batc (None, 14, 14, 50)        200       
_________________________________________________________________
activation_4148 (Activation) (None, 14, 14, 50)        0         
_________________________________________________________________
max_pooling2d_20 (MaxPooling (None, 7, 7, 50)          0         
_________________________________________________________________
conv2d_38 (Conv2D)           (None, 7, 7, 50)          22550     
_________________________________________________________________
batch_normalization_11 (Batc (None, 7, 7, 50)          200       
_________________________________________________________________
activation_4149 (Activation) (None, 7, 7, 50)          0         
_________________________________________________________________
conv2d_39 (Conv2D)           (None, 7, 7, 50)          22550     
_________________________________________________________________
batch_normalization_12 (Batc (None, 7, 7, 50)          200       
_________________________________________________________________
activation_4150 (Activation) (None, 7, 7, 50)          0         
_________________________________________________________________
max_pooling2d_21 (MaxPooling (None, 3, 3, 50)          0         
_________________________________________________________________
flatten_11 (Flatten)         (None, 450)               0         
_________________________________________________________________
dense_21 (Dense)             (None, 50)                22550     
_________________________________________________________________
dropout_2 (Dropout)          (None, 50)                0         
_________________________________________________________________
dense_22 (Dense)             (None, 10)                510       
=================================================================
Total params: 137,510
Trainable params: 136,910
Non-trainable params: 600
_________________________________________________________________

모델을 학습시키고 학습 과정을 시각화해 본다. 학습이 훨씬 안정적으로 이루어지는 것을 볼 수 있다.

history = model.fit(X_train, y_train, batch_size = 50, validation_split = 0.2, epochs = 100, verbose = 0)

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.legend(['training', 'validation'], loc = 'upper left')
plt.show()


학습 결과를 검증해 본다.

results = model.evaluate(X_test, y_test)
print('Test accuracy: ', results[1])
Test accuracy:  0.9942

최종 검증 정확도 99.42%로 기존의 모델에 비해 훨씬 개선된 성능을 보여준다.

Deep CNN - 3

방금 생성한 모델로도 99%가 넘는 성능을 보여주며 이미지 데이터를 처리함에 있어 CNN 구조가 얼마나 효과적인지를 입증하였다. 이번에는 색다른 CNN 구조를 시도해 보자.

Network In Network (NIN)

처음에 Deep CNN에 대해서 소개할 때, 모델이 깊어지고 레이어의 수가 많아질수록 추정해야 할 파라미터의 개수가 많아져 연산량이 늘어나고 학습이 오래걸린다고 언급하였다.

바로 위의 모델에서 MLP를 개선하기 위한 방법들을 CNN에도 적용하며 학습 과정을 개선하는 데에는 성공하였다. 이번에는 깊어진 CNN에서 파라미터 개수를 줄이기 위한 방법 중 하나로 “Network In Network (NIN)”을 적용해 보자.

NIN은 1x1 convolution이라고도 하며, Min et al 2013이 제안하였다. 방법은 매우 간단하다. convolution layer 다음에 pooling layer가 아닌 convolution layer를 연속해서 쌓는것이다. 너무 단순해서 비상식적으로 보일 수도 있는데, 직관적으로는 입력 공간의 차원을 축소하여 앞서 얘기했듯이 파라미터 개수를 이전에 비해 상당히 줄일 수 있다.


2015년에 제안된 구글넷(GoogleNet)의 인셉션 구조는 1x1 convolution을 적극 사용해 파라미터 개수를 획기적으로 줄이는 데 성공하였다.


모델 생성하기

모델 생성은 크게 복잡할 것 없다. 기존의 convolution layer에 1x1 convolution layer를 붙이면 된다.

def deep_cnn_advanced_nin():
    model = Sequential()

    model.add(Conv2D(input_shape = (X_train.shape[1], X_train.shape[2], X_train.shape[3]), filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    # 1x1 convolution
    model.add(Conv2D(filters = 25, kernel_size = (1,1), strides = (1,1), padding = 'valid', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    # 1x1 convolution
    model.add(Conv2D(filters = 25, kernel_size = (1,1), strides = (1,1), padding = 'valid', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    # 1x1 convolution
    model.add(Conv2D(filters = 25, kernel_size = (1,1), strides = (1,1), padding = 'valid', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    # 1x1 convolution
    model.add(Conv2D(filters = 25, kernel_size = (1,1), strides = (1,1), padding = 'valid', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_initializer='he_normal'))
    # 1x1 convolution
    model.add(Conv2D(filters = 25, kernel_size = (1,1), strides = (1,1), padding = 'valid', kernel_initializer='he_normal'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))

    # prior layer should be flattend to be connected to dense layers
    model.add(Flatten())
    # dense layer with 50 neurons
    model.add(Dense(50, activation = 'relu', kernel_initializer='he_normal'))
    model.add(Dropout(0.5))
    # final layer with 10 neurons to classify the instances
    model.add(Dense(10, activation = 'softmax', kernel_initializer='he_normal'))

    adam = optimizers.Adam(lr = 0.001)
    model.compile(loss = 'categorical_crossentropy', optimizer = adam, metrics = ['accuracy'])

    return model

model = deep_cnn_advanced_nin()
model.summary()

아래 summary에서 보듯이 학습 가능한 파라미터의 수를 기존의 13만여개에서 8만여개로 60% 수준으로 줄일 수 있었다.

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_45 (Conv2D)           (None, 28, 28, 50)        500       
_________________________________________________________________
batch_normalization_13 (Batc (None, 28, 28, 50)        200       
_________________________________________________________________
activation_4151 (Activation) (None, 28, 28, 50)        0         
_________________________________________________________________
conv2d_46 (Conv2D)           (None, 28, 28, 50)        22550     
_________________________________________________________________
conv2d_47 (Conv2D)           (None, 28, 28, 25)        1275      
_________________________________________________________________
batch_normalization_14 (Batc (None, 28, 28, 25)        100       
_________________________________________________________________
activation_4152 (Activation) (None, 28, 28, 25)        0         
_________________________________________________________________
max_pooling2d_24 (MaxPooling (None, 14, 14, 25)        0         
_________________________________________________________________
conv2d_48 (Conv2D)           (None, 14, 14, 50)        11300     
_________________________________________________________________
conv2d_49 (Conv2D)           (None, 14, 14, 25)        1275      
_________________________________________________________________
batch_normalization_15 (Batc (None, 14, 14, 25)        100       
_________________________________________________________________
activation_4153 (Activation) (None, 14, 14, 25)        0         
_________________________________________________________________
conv2d_50 (Conv2D)           (None, 14, 14, 50)        11300     
_________________________________________________________________
conv2d_51 (Conv2D)           (None, 14, 14, 25)        1275      
_________________________________________________________________
batch_normalization_16 (Batc (None, 14, 14, 25)        100       
_________________________________________________________________
activation_4154 (Activation) (None, 14, 14, 25)        0         
_________________________________________________________________
max_pooling2d_25 (MaxPooling (None, 7, 7, 25)          0         
_________________________________________________________________
conv2d_52 (Conv2D)           (None, 7, 7, 50)          11300     
_________________________________________________________________
conv2d_53 (Conv2D)           (None, 7, 7, 25)          1275      
_________________________________________________________________
batch_normalization_17 (Batc (None, 7, 7, 25)          100       
_________________________________________________________________
activation_4155 (Activation) (None, 7, 7, 25)          0         
_________________________________________________________________
conv2d_54 (Conv2D)           (None, 7, 7, 50)          11300     
_________________________________________________________________
conv2d_55 (Conv2D)           (None, 7, 7, 25)          1275      
_________________________________________________________________
batch_normalization_18 (Batc (None, 7, 7, 25)          100       
_________________________________________________________________
activation_4156 (Activation) (None, 7, 7, 25)          0         
_________________________________________________________________
max_pooling2d_26 (MaxPooling (None, 3, 3, 25)          0         
_________________________________________________________________
flatten_13 (Flatten)         (None, 225)               0         
_________________________________________________________________
dense_25 (Dense)             (None, 50)                11300     
_________________________________________________________________
dropout_3 (Dropout)          (None, 50)                0         
_________________________________________________________________
dense_26 (Dense)             (None, 10)                510       
=================================================================
Total params: 87,135
Trainable params: 86,785
Non-trainable params: 350
_________________________________________________________________
history = model.fit(X_train, y_train, batch_size = 50, validation_split = 0.2, epochs = 100, verbose = 0)

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.legend(['training', 'validation'], loc = 'upper left')
plt.show()

모델의 학습 과정은 deep CNN -2 와 크게 다르지 않아 보인다.


results = model.evaluate(X_test, y_test)
print('Test accuracy: ', results[1])
Test accuracy:  0.9914

최종 검증 정확도도 99.14%로 두 번째 깊은 CNN 모델에 비해 크게 다르지 않다. 하지만 파라미터의 수를 상당히 줄였음에도, 비슷한 정확도가 나온다는 데에서 1x1 convolution을 가치를 찾을 수 있다고 할 수 있다.

많은 딥 러닝 연구가 이미지/영상 분석과 함께 맥을 이어 왔고, 이에 따라 NIN과 같이 기존의 vanilla cnn 구조를 개선하기 위한 연구들이 지금도 수없이 제안되고 있다. 최근에 본 책에 따르면 알버트 아인슈타인이나 베누아 망델브로와 같은 천재들은 이 세상 대부분의 것들을 “이미지(image)”로 인식했으며, 천재가 아니더라도 대부분의 사람들은 글자나 수식, 그리고 기억까지도 뇌에서 이미지로 인지한다고 한다. 이미지 분석과 딥 러닝과 관련된 연구가 앞으로 더욱 기대되는 이유 중 하나이다.

전체 코드

본 실습의 전체 코드는 여기에서 열람하실 수 있습니다!

케라스와 함께하는 쉬운 딥러닝 (10) - CNN 모델 개선하기 1

|

합성곱 신경망 4 - CNN 모델 개선하기 1

Objective: 케라스로 개선된 CNN 모델을 만들어 본다.

지난 포스팅에서 케라스로 간단한 CNN 모델을 만들고 이를 Digits 데이터 셋에 적용해보았다.

이번 포스팅에서는 지난 포스팅의 간단한 CNN 모델에서 나아가, 이를 더 깊게(deep cnn) 만들어 보자.

깊어지는 합성곱 신경망(Deep CNN)

2012년 AlexNet이 제안되기 이전의 뉴럴 네트워크는 대부분 레이어가 몇 개 없는 얕은 구조(shallow structure)를 가지고 있었다. 하지만 AlexNet이 ILSVRC 2012에서 8개의 레이어로 깊은 구조가 복잡한 데이터를 학습하는 데 효과적이라는 것을 증명한 이후로 CNN은 갈수록 깊어져 2015년 ILSVRC의 우승자인 ResNet은 152개의 레이어를 자랑한다.


깊어진 CNN은 더욱 많은 feature extraction 연산과 비선형 활성 함수(nonlinear activation function)를 통해 이미지와 같은 복잡한 데이터의 추상적인 표현(abstract representation)을 캐치해낼 수 있다는 강점을 가지지만, 한편으로는 파라미터의 숫자가 많아지면서 경사하강법을 통한 학습과정이 까다로워지고, 과적합(overfitting)의 문제가 발생하기도 한다.

그러므로, 깊어진 CNN은 모델의 학습과 검증에 있어 대부분의 복잡한 뉴럴 네트워크가 그렇듯, 더욱 주의를 요한다.

MNIST 데이터 셋 불러오기

MLP에서도 사용했던 MNIST 데이터 셋을 불러온다. 그때와 다른 점이 있다면, 그때는 MLP 모델에 입력하기 위해 (28, 28, 1) 짜리 사이즈의 데이터를 flatten해 784차원의 1차원 벡터로 만들었다면, 여기에서는 3차원 이미지 데이터를 그대로 사용한다는 것이다.

import numpy as np
import matplotlib.pyplot as plt

from keras.datasets import mnist
from keras.utils.np_utils import to_categorical

(X_train, y_train), (X_test, y_test) = mnist.load_data()
# reshaping X data: (n, 28, 28) => (n, 28, 28, 1)
X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], X_train.shape[2], 1))
X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], X_test.shape[2], 1))
# converting y data into categorical (one-hot encoding)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
(60000, 28, 28, 1), (10000, 28, 28, 1), (60000, 10), (10000, 10)  

간단한 CNN 모델 만들기

지난 포스팅에서 만들었던 것과 비슷하게, 하나의 convolution 레이어와 pooling 레이어를 가진 CNN 모델을 만들어 보자.

from keras.models import Sequential
from keras import optimizers
from keras.layers import Dense, Activation, Flatten, Conv2D, MaxPooling2D

def basic_cnn():
    model = Sequential()

    model.add(Conv2D(input_shape = (X_train.shape[1], X_train.shape[2], X_train.shape[3]), filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))

    # prior layer should be flattend to be connected to dense layers
    model.add(Flatten())
    # dense layer with 50 neurons
    model.add(Dense(50, activation = 'relu'))
    # final layer with 10 neurons to classify the instances
    model.add(Dense(10, activation = 'softmax'))

    adam = optimizers.Adam(lr = 0.001)
    model.compile(loss = 'categorical_crossentropy', optimizer = adam, metrics = ['accuracy'])

    return model

model = basic_cnn()
model.summary()

model.summary() 를 통해 모델의 대략적인 구조를 파악해볼 수 있다. 간단한 CNN 모델은 491,060개의 학습 가능한 파라미터를 가진다.

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_13 (Conv2D)           (None, 28, 28, 50)        500       
_________________________________________________________________
activation_4124 (Activation) (None, 28, 28, 50)        0         
_________________________________________________________________
max_pooling2d_8 (MaxPooling2 (None, 14, 14, 50)        0         
_________________________________________________________________
flatten_6 (Flatten)          (None, 9800)              0         
_________________________________________________________________
dense_11 (Dense)             (None, 50)                490050    
_________________________________________________________________
dense_12 (Dense)             (None, 10)                510       
=================================================================
Total params: 491,060
Trainable params: 491,060
Non-trainable params: 0
_________________________________________________________________

모델을 학습시키고 학습 과정을 시각화해 본다.

history = model.fit(X_train, y_train, batch_size = 50, validation_split = 0.2, epochs = 100, verbose = 0)

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.legend(['training', 'validation'], loc = 'upper left')
plt.show()


학습 결과를 검증해 본다.

results = model.evaluate(X_test, y_test)
print('Test accuracy: ', results[1])
Test accuracy:  0.9811

학습 및 검증 결과 검증 정확도 98.11%로 간단한 모델 치고는 나쁘지 않은 성능이 나왔다. 이번에는 이를 조금 더 깊게 만들어 정확도를 더 끌어올릴 수 있는지 확인해 보자.

깊은 CNN 모델 만들기

6개의 convolution 레이어와 3개의 pooling 레이어를 가진 깊은 cnn 구조를 생성해 보자.

def deep_cnn():
    model = Sequential()

    model.add(Conv2D(input_shape = (X_train.shape[1], X_train.shape[2], X_train.shape[3]), filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(Conv2D(filters = 50, kernel_size = (3,3), strides = (1,1), padding = 'same'))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))

    # prior layer should be flattend to be connected to dense layers
    model.add(Flatten())
    # dense layer with 50 neurons
    model.add(Dense(50, activation = 'relu'))
    # final layer with 10 neurons to classify the instances
    model.add(Dense(10, activation = 'softmax'))

    adam = optimizers.Adam(lr = 0.001)
    model.compile(loss = 'categorical_crossentropy', optimizer = adam, metrics = ['accuracy'])

    return model

model = deep_cnn()
model.summary()

아래 summary에서 보듯이 학습 가능한 파라미터의 수가 136,310개로 간단한 cnn 모델에 비해 훨씬 많은 것을 확인해볼 수 있다.

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_22 (Conv2D)           (None, 28, 28, 50)        500       
_________________________________________________________________
activation_4133 (Activation) (None, 28, 28, 50)        0         
_________________________________________________________________
conv2d_23 (Conv2D)           (None, 28, 28, 50)        22550     
_________________________________________________________________
activation_4134 (Activation) (None, 28, 28, 50)        0         
_________________________________________________________________
max_pooling2d_13 (MaxPooling (None, 14, 14, 50)        0         
_________________________________________________________________
conv2d_24 (Conv2D)           (None, 14, 14, 50)        22550     
_________________________________________________________________
activation_4135 (Activation) (None, 14, 14, 50)        0         
_________________________________________________________________
conv2d_25 (Conv2D)           (None, 14, 14, 50)        22550     
_________________________________________________________________
activation_4136 (Activation) (None, 14, 14, 50)        0         
_________________________________________________________________
max_pooling2d_14 (MaxPooling (None, 7, 7, 50)          0         
_________________________________________________________________
conv2d_26 (Conv2D)           (None, 7, 7, 50)          22550     
_________________________________________________________________
activation_4137 (Activation) (None, 7, 7, 50)          0         
_________________________________________________________________
conv2d_27 (Conv2D)           (None, 7, 7, 50)          22550     
_________________________________________________________________
activation_4138 (Activation) (None, 7, 7, 50)          0         
_________________________________________________________________
max_pooling2d_15 (MaxPooling (None, 3, 3, 50)          0         
_________________________________________________________________
flatten_9 (Flatten)          (None, 450)               0         
_________________________________________________________________
dense_17 (Dense)             (None, 50)                22550     
_________________________________________________________________
dense_18 (Dense)             (None, 10)                510       
=================================================================
Total params: 136,310
Trainable params: 136,310
Non-trainable params: 0
_________________________________________________________________
history = model.fit(X_train, y_train, batch_size = 50, validation_split = 0.2, epochs = 100, verbose = 0)

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.legend(['training', 'validation'], loc = 'upper left')
plt.show()

모델의 정확도가 계속 들쭉날쭉하며, 특히 80 에포크 후에 모델의 정확도가 전반적으로 떨어지는 것을 볼 수 있다.


results = model.evaluate(X_test, y_test)
print('Test accuracy: ', results[1])
Test accuracy:  0.9368

최종 검증 정확도도 93.68%로 깊은 모델을 만들었음에도 불구하고 간단한 모델에 비해서 6%가량 낮은 정확도를 보이는 것을 볼 수 있다. 서두에서 깊은 모델의 경우 학습이 어렵다고 말한 문제를 반영하는 것으로 추측해볼 수 있다.

이제 다음 포스팅에서 깊은 모델을 어떻게 더 잘 학습시킬 수 있는지에 대해 알아보자.

전체 코드

본 실습의 전체 코드는 여기에서 열람하실 수 있습니다!

케라스와 함께하는 쉬운 딥러닝 (9) - 간단한 합성곱 신경망(CNN) 모델 만들기

|

합성곱 신경망 3 - 간단한 합성곱 신경망(CNN) 모델 만들기 (Basic CNN)

Objective: 케라스로 간단한 합성곱 신경망 모델을 만들어 본다.

지지난 포스팅지난 포스팅에서 cnn 구조를 이해하기 위해 이미지 데이터, 풀링/합성곱 연산, 패딩, 필터 등에 대해서 알아보았다.

이번 포스팅부터는 합성곱 신경망(CNN) 모델을 실제로 구현해 보면서 익혀보자.

합성곱 신경망

CNN은 MLP에 합성곱 레이어(convolution layer)와 풀링 레이어(pooling layer)라는 고유의 구조를 더한 뉴럴 네트워크라고 할 수 있다.

  • 합성곱 레이어: 필터(filter), 혹은 커널(kernel)이라고 하는 작은 수용 영역(receptive field)을 통해 데이터를 인식한다.
  • 풀링 레이어: 특정 영역에서 최대값만 추출하거나, 평균값을 추출하여 차원을 축소하는 역할을 한다.


CNN은 MLP나 뒤에서 나올 순환형 신경망(RNN)에 비해 학습해야 할 파라미터의 개수가 상대적으로 적어 학습이 빠르다는 장점이 있다.

2013년에 AlexNet이 제안되어 ImageNet 대회에서 획기적인 성적을 낸 이후로 CNN에 대한 연구가 활발히 되어 이제는 이미지 인식 뿐 아니라 자연어처리에도 흔히 쓰이며, CNN의 학습 과정을 해석하고 시각화하려는 시도도 자주 등장하고 있다.

p align = “center”>
</p>

Digits 데이터 셋 불러오기

이번에는 MNIST와 비슷한 형태지만 데이터셋의 크기가 작은 scikit-learn의 digits 데이터 셋을 활용해 보자.

import numpy as np
import matplotlib.pyplot as plt

from sklearn import datasets
from sklearn.model_selection import train_test_split
from keras.utils.np_utils import to_categorical
data = datasets.load_digits()
plt.imshow(data.images[0])    # show first number in the dataset
plt.show()
print('label: ', data.target[0])    


label:  0   # 첫 번째 데이터 인스턴스의 라벨(클래스)는 0이다

Digits 데이터 인스턴스의 개수는 총 1797 개이며, 데이터의 모양은 8 X 8이다.

# shape of data
print(X_data.shape)    # (8 X 8) format
print(y_data.shape)
(1797, 8, 8)
(1797,)

데이터 셋 전처리

데이터의 모양을 바꾼다. X 데이터는 (3차원으로 바꿔) 차원을 하나 늘리고, Y 데이터는 one-hot 인코딩을 해준다.

# reshape X_data into 3-D format
# note that this follows image format of Tensorflow backend
X_data = X_data.reshape((X_data.shape[0], X_data.shape[1], X_data.shape[2], 1))

# one-hot encoding of y_data
y_data = to_categorical(y_data)

전체 데이터를 학습/검증 데이터 셋으로 나눈다

# partition data into train/test sets
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size = 0.3, random_state = 777)

print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(1257, 8, 8, 1)
(540, 8, 8, 1)
(1257, 10)
(540, 10)

1257개의 학습 데이터를 가지고 모델을 학습시키고, 540개의 검증 데이터로 이를 평가해본다.

모델 생성하기

MLP 모델을 생성하는데 사용하였던 Sequential()로 모델을 생성한다.

from keras.models import Sequential
from keras import optimizers
from keras.layers import Dense, Activation, Flatten, Conv2D, MaxPooling2D

model = Sequential()

합성곱 레이어

이미지 처리를 위해서는 일반적으로 2D convolution layer (Conv2D)를 사용한다. 사용자가 지정해주어야 하는 Conv2D의 주요 파라미터는 아래와 같다.

  • 필터의 사이즈(kernel_size): 합성곱 연산을 진행할 필터(커널)의 사이즈를 의미한다. 구체적으로, 수용 영역의 너비(width)와 높이(height)를 설정해 준다.
  • 필터의 개수(filters): 서로 다른 합성곱 연산을 수행하는 필터의 개수를 의미한다. 필터의 개수는 다음 레이어의 깊이(depth)를 결정한다.
  • 스텝 사이즈(strides): 필터가 이미지 위를 움직이며 합성곱 연산을 수행하는데, 한 번에 움직이는 정도(가로, 세로)를 의미한다.
  • 패딩(padding): 이미지 크기가 작은 경우 이미지 주위에 0으로 이루어진 패딩을 추가해 차원을 유지할 수 있다.


model.add(Conv2D(input_shape = (X_data.shape[1], X_data.shape[2], X_data.shape[3]), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'valid'))

활성함수

MLP와 동일하게 ReLU 활성함수를 사용한다

model.add(Activation('relu'))

풀링 레이어

일반적으로 이미지 인식을 위해서는 맥스 풀링(max pooling), 혹은 애버리지 풀링(average pooling)이 사용되며 특정 영역을 묘사하는 대표값을 뽑아 파라미터의 수를 줄여주는 역할을 한다


model.add(MaxPooling2D(pool_size = (2,2)))

완전 연결 레이어(Dense 혹은 fully-connected layer)

CNN의 마지막 단에 MLP와 동일한 완전 연결 레이어를 넣을 수도 있고, 넣지 않을 수도 있다.

MLP로 연결하기 전에 3차원의 데이터의 차원을 줄이기 위해 Flatten() 을 추가해 주는것에 유의하자.

model.add(Flatten())
model.add(Dense(50, activation = 'relu'))
model.add(Dense(10, activation = 'softmax'))     ### 이미지를 분류하기 위한 마지막 레이어

모델 컴파일 및 학습

모델을 컴파일하고 학습을 진행시킨다.

adam = optimizers.Adam(lr = 0.001)
model.compile(loss = 'categorical_crossentropy', optimizer = adam, metrics = ['accuracy'])
history = model.fit(X_train, y_train, batch_size = 50, validation_split = 0.2, epochs = 100, verbose = 0)

모델 학습 과정을 시각화해본다. 빠르게 정확도가 올라가는 것으로 보아 학습이 잘 되는 것 같다.

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.legend(['training', 'validation'], loc = 'upper left')
plt.show()


모델 평가

검증 데이터로 모델을 평가해본다

results = model.evaluate(X_test, y_test)
print('Test accuracy: ', results[1])
Test accuracy:  0.972222222222

최종 결과로 97%가 넘는 높은 정확도를 기록하였다. 가장 간단한 형태의 CNN을 구현했음에도 불구하고 상당히 정확히 숫자를 분류해내는 것을 알 수 있다. 이제 다음 포스팅부터는 CNN에 대해서 더 자세히 알아보자.

전체 코드

본 실습의 전체 코드는 여기에서 열람하실 수 있습니다!

케라스와 함께하는 쉬운 딥러닝 (8) - CNN 구조 이해하기 2

|

합성곱 신경망 2 - CNN 구조 이해하기 두번째

Objective: 이미지 및 영상 분석, 자연어처리 등에 폭넓게 쓰이는 합성곱 신경망의 구조에 대해 알아본다.

지난 포스팅에서 CNN 구조를 이해하기에 앞서, 컴퓨터가 이미지 데이터를 어떻게 받아들이고 이미지가 텐서로 어떻게 표현되는지에 대해서 알아보았다.

이번 포스팅에서는 현대 CNN을 이루는 핵심적인 구조인 합성곱 레이어(convolution layer)와 풀링 레이어(pooling layer)의 연산 과정에 대해서 알아보자.

언급했듯이, CNN은 아래와 같이 크게 합성곱 레이어(CONV), 풀링 레이어(POOL), 그리고 완전 연결된 레이어(FC)로 이루어져 있다.


이름에서 유추할 수 있듯이 합성곱 레이어에서 convolution 연산이 이루어지고, 풀링 레이어에서 pooling 연산이 이루어진다. 그리고 패딩(padding)과 필터(filter), 혹은 커널(kernel)이라는 개념이 중요하게 등장한다. 자세한 설명은 여기를 참고하자.

케라스에서는 convolution, pooling 연산을 함수로 제공해 따로 구현할 필요가 없으며, 패딩과 필터도 인자값을 설정함으로써 쉽게 조절할 수 있다. 그래도 기본적인 연산 과정을 이해해 두는것이 좋으니 하나하나 알아보자.

패딩(Padding)

앞서 언급하였듯이, convolution과 pooling 연산은 파라미터의 수를 줄여나가는 과정이다. 하지만 이러한 과정에서 지나치게 데이터가 축소되어 정보가 소실되는 것을 방지하기 위해 데이터에 0으로 이루어진 패딩을 주는 경우가 있다.

케라스에서 padding을 설정하는 방법은 아래와 같이 두 가지가 있다.

  • 합성곱 혹은 풀링 연산을 수행하는 레이어에 파라미터로 설정. 이 경우 아래와 같은 두 가지 옵션 중 하나를 선택할 수 있다.
  1. valid: 패딩을 하지 않음(사이즈가 맞지 않을 경우 가장 우측의 열 혹은 가장 아래의 행을 드랍한다).
# when padding = 'valid'
model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'valid'))
print(model.output_shape)

패딩을 주지 않으니(valid) 아래와 같이 (10 X 10)에서 (8 X 8)로 차원이 축소된 것을 볼 수 있다.

(None, 8, 8, 10)
  1. same: 필터의 사이즈가 k이면 사방으로 k/2 만큼의 패딩을 준다.
# when padding = 'same'
model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'same'))
print(model.output_shape)

패딩을 설정을 same으로 주니 아래와 같이 차원이 (10 X 10)으로 유지되는 것을 확인할 수 있다.

(None, 10, 10, 10)
  • 패딩 레이어(ZeroPadding1D, ZeroPadding2D, ZeroPadding3D 등)를 따로 생성해 모델에 포함시키는 방법. 이 방법은 모델을 생성할 때 Sequential API가 아닌 Functional API를 활용할 때 유용하다(Functional API는 아직 다루지 않았는데, 궁금하신 분은 여기를 참고하면 된다)

# user-customized padding# user-c
input_layer = Input(shape = (10, 10, 3))
padding_layer = ZeroPadding2D(padding = (1,1))(input_layer)

model = Model(inputs = input_layer, outputs = padding_layer)
print(model.output_shape)

(10 X 10) 크기의 데이터에 (1, 1)의 패딩을 해주었더니 사방으로 1만큼 확장되어 아래와 같이 (12 X 12)의 output shape이 출력되는 것을 볼 수 있다.

(None, 12, 12, 3)

필터/커널(Filter/kernel)

합성곱 레이어를 보면 padding외에도 크게 세 개의 중요한 파라미터가 등장한다. 바로 filters, kernel_size, strides이다. 이 세 개의 파라미터가 합성곱 레이어에의 출력 모양을 결정한다고 할 수 있다.


  • filters: 몇 개의 다른 종류의 필터를 활용할 것인지를 나타냄. 출력 모양의 깊이(depth) 를 결정한다.
  • kernel_size: 연산을 수행할 때 윈도우의 크기를 의미한다.
  • strides: 연산을 수행할 때 윈도우가 가로 그리고 세로로 움직이면서 내적 연산을 수행하는데, 한 번에 얼마나 움직일지를 의미한다.
# when filter size = 10
model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'same'))
print(model.output_shape)

필터 수가 10이므로 아래와 같이 출력의 depth도 10이다.

(None, 10, 10, 10)
# when filter size = 10
model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 20, kernel_size = (3,3), strides = (1,1), padding = 'same'))
print(model.output_shape)

필터 수를 늘리면 아래와 같이 출력의 depth도 늘어난다.

(None, 10, 10, 20)

풀링(pooling)

일반적으로 윈도우 내에서 출력의 최대값을 추출하는 맥스 풀링(max pooling)이 활용되나, 평균값을 뽑는 애버리지 풀링(average pooling)이 활용되기도 한다.

pool_size 는 합성곱 레이어에서 kernel_size와 같이 윈도우의 크기를 의미하며, 패딩은 합성곱 레이어와 똑같이 적용되며(valid혹은 same), strides가 미리 설정되지 않을 경우 pool_size와 동일하게 설정된다.

model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'same'))
print(model.output_shape)

# when 'strides' parameter is not defined, strides are equal to 'pool_size'
model.add(MaxPooling2D(pool_size = (2,2), padding = 'valid'))
print(model.output_shape)

(10 X 10) 크기의 출력에 (2 X 2) 크기의 풀링의 (2, 2)만큼의 stride로 적용하니 출력 모양이 (5 X 5)로 결정된다.

(None, 10, 10, 10)
(None, 5, 5, 10)

stride를 명시적으로 설정해줄 경우(1,1)

model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'same'))
model.add(MaxPooling2D(pool_size = (2,2), strides = (1,1), padding = 'valid'))
print(model.output_shape)
(None, 9, 9, 10)

애버리지 풀링도 출력 모양은 똑같다(값은 다르다는 점에 유의한다).

model = Sequential()
model.add(Conv2D(input_shape = (10, 10, 3), filters = 10, kernel_size = (3,3), strides = (1,1), padding = 'same'))
model.add(AveragePooling2D(pool_size = (2,2), padding = 'valid'))
print(model.output_shape)
(None, 5, 5, 10)

전체 코드

본 실습의 전체 코드는 여기에서 열람하실 수 있습니다!

케라스와 함께하는 쉬운 딥러닝 (7) - CNN 구조 이해하기

|

합성곱 신경망 1 - CNN 구조 이해하기

Objective: 이미지 및 영상 분석, 자연어처리 등에 폭넓게 쓰이는 합성곱 신경망의 구조에 대해 알아본다.

지난 포스팅을 마지막으로, 케라스에서 MLP를 효율적으로 학습시키기 위한 방법에 대해서 알아보았다. 그 방법은 아래와 같다.

  • 가중치 초기화(Weight Initialization)
  • 활성함수(Activation Function)
  • 최적화(Optimization)
  • 배치 정규화(Batch Normalization)
  • 드랍아웃(Dropout)
  • 앙상블(Model Ensemble)
  • 학습 데이터 추가(More training samples)

이 방법은 MLP뿐 아니라 다른 신경망 구조에도 비슷하게 적용될 수 있으며, 현업에 적용할 만한 인공신경망을 만드는데 꼭 고려해야 하는 요소이므로 잘 알아두어야 한다.

이번 포스팅부터는 이미지를 분석하는데 특화된 인공신경망 구조로 알려져 있지만, 사실은 이미지뿐 아니라 영상, 텍스트, 음성 등 다양한 데이터를 분석하는데 활용되는 합성곱 신경망(CNN; Convolutional Neural Networks)에 대해서 알아보자.

이미지 데이터에 대한 이해

MLP에서 MNIST 데이터를 분석하면서 간단히 알아보았지만, 이미지 데이터는 픽셀(pixel)을 단위로 이루어져 있으며 기본적으로 3차원이다. MNIST의 경우 2차원(28 X 28)이었는데 왜 3차원이냐고 묻는 사람이 있을 수도 있겠지만, MNIST는 흑백(greyscale)이기때문에 2차원처럼 보이는 것이지, 실제로는 3차원(28 X 28 X 1)이라고 할 수 있다. 그리고 일반적인 이미지는 RGB 세 개의 채널 강도를 가지고 있으며 a X b X c의 3차원 구조를 가지고 있다.


a X b X c의 이미지 구조에서 일반적으로 a는 너비(width)를, b는 높이(height)를, c는 컬러 채널의 수(일반적으로 RGB 채널의 경우 c=3)을 결정한다. 그러므로 위와 같은 이미지 데이터는 4 X 4 X 3 의 차원 구조를 가지고 있다고 할 수 있다.

케라스의 processing.image 모듈은 이미지를 불러오고 처리할 수 있는 유용한 함수들을 제공한다. 이제 실제 아래와 같은 강아지 이미지를 불러와 이미지 데이터에 대한 이해를 도와보자.


image 모듈에서 제공하는 keras.processing.image.load_img 함수를 통해 외부 이미지를 Python 환경으로 불러올 수 있다. 인자로 불러올 이미지의 크기(target_size)를 픽셀 단위로 설정할 수 있다.

img = image.load_img('dog.jpg', target_size = (100, 100))
img


불러온 이미지에 img_to_array 함수를 씌우면 이미지를 NumPy 배열로 변환해 준다. 이미지의 모양을 출력해보면 아래와 같다. 이미지의 크기를 (100 X 100)으로 설정하였고, 컬러 이미지이므로 컬러 채널의 크기가 3이 되어 (100 X 100 X 3)의 3차원 구조를 갖는 배열이 생성되었다.

img = image.img_to_array(img)
print(img.shape)
(100, 100, 3)

이미지 텐서

이미지 데이터는 3차원인데, 실제 케라스나 텐서플로에서 이미지 데이터를 다루기 위한 텐서(tensor)의 모양을 출력해보면 아래와 같이 4차원 구조를 가지고 있다. 이는 초심자들에게 굉장히 많은 혼란을 가져오곤 하는데, 이는 대용량 이미지를 학습할 때 일반적인 경사하강법(Gradient Descent)에 비해 빠르게 수렴하는 Mini-batch Stochastic Gradient Descent (Mini-batch SGD)를 흔히 사용하기 때문이다.


Keras Conv2D 레이어의 입력과 출력

자세히 보면 4차원 텐서의 shape이 (samples, rows, cols, channels) (텐서플로 기준)과 같이 이루어져 있는 것을 볼 수 있다. 여기에서 rowscols는 위에서 본 이미지 데이터의 너비와 높이에 해당하고, channels는 RGB 채널과 같다. 즉, 달라지는 것은 맨 처음에 붙는 samples인데, 이는 Mini-batch SGD를 위해 전체 학습 데이터에서 랜덤샘플링을 통해 미니 배치를 만들때 그 미니 배치의 크기를 의미한다.

결론적으로, 한 번 경사하강법으로 학습을 할 때 NumPy 배열이 (samples, rows, cols, channels) 형태로 입력된다고 보면 된다. 이미지 데이터와 이미지 텐서의 차이에 대해서 혼란이 없길 바란다. 이미지 데이터는 3차원이다!

이제 다음 포스팅에서는 CNN을 이루는 핵심적인 요소인 패딩(padding)과 필터(filter), 풀링(pooling) 연산 등에 대해서 알아보자.

전체 코드

본 실습의 전체 코드는 여기에서 열람하실 수 있습니다!