MNIST 데이터를 이용하여 손글씨 이미지에서 숫자를 판별하는 모델을 만들어 보겠습니다. Keras API를 이용하여 간단하게 MNIST 데이터를 사용할 수 있습니다. 입력을 불러온 이미지 데이터로 하고 출력은 0부터 9 사이의 숫자로 하여 모델 구조를 만들겠습니다.
각 이미지는 8-bit의 크기를 가지는 28x28 배열로 이루어져 있습니다. 데이터를 사용하기 위해 reshape()
함수를 이용하여 1차원 배열로 바꿔야 합니다. 그리고 Keras는 0에서 1 사이의 값에서 구동할 때 최적의 성능을 보이기 때문에 데이터를 정규화하는 작업을 해야 합니다. 데이터 정규화는 주어진 데이터를 실수형으로 바꾼 뒤 255로 나누어 주면 됩니다.
주어진 이미지의 숫자가 무엇인지를 분류해야 하므로 클래스에 대해 one-hot 인코딩이 필요합니다. 그래서 정수형 데이터를 one-hot 인코딩을 적용하여 길이가 10인 이진수 벡터로 바꿔줘야 합니다. to_categorical(클래서, 클래스의 개수)
함수를 이용하여 바꿔줄 수 있습니다.
위의 과정을 코드로 작성하면 다음과 같습니다.
# MNIST 데이터셋 불러오기
from tensorflow.keras.dataset import mnist
# to_categorical 함수 불러오기
from tensorflow.keras.utils import to_categorical
# 입출력 데이터 나누기
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 1차원 배열로 변환 및 데이터 정규화
X_train = X_train.reshape(X_train.shape[0], 784).astype('float64') / 255
X_test = X_test.reshape(X_test.shape[0], 784).astype('float64') / 255
# one-hot 인코딩
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
사용하는 데이터에는 784개의 속성과 10개의 클래스가 있습니다.
다음과 같이 모델 구조를 만들어 보겠습니다.
model = Sequential()
model.add(Dense(512, input_dim=784, activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
체크포인트, 학습 자동 중단을 이용하여 최적화를 해보겠습니다.
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
# 체크포인트 및 학습 자동 중단 설정
modelpath = "./MNIST_MLP.hdf5"
checkpointer = ModelCheckpoint(filepath=modelpath, monitor='val_loss',
verbose=1, save_best_only=True)
early_stopping_callback = EarlyStopping(monitor='val_loss', patience=10)
history = model.fit(X_train, y_train, validation_split=0.25, epochs=30,
batch_size=200, verbose=0,
callbacks=[early_stopping_callback, checkpointer])
history를 이용하여 오차를 그래프로 나타낼 수 있습니다.
import matplotlib.pyplot as plt
y_vloss = history.history['val_loss']
y_loss = hisory.history['loss']
# 데이터 그래프로 나타내기
x_len = np.arange(len(y_loss))
plt.plot(x_len, y_vloss, marker='.', c='red', label='Testset_loss')
plt.plot(x_len, y_loss, marker='.', c='blue', label='Testset_loss')
# 그래프 관련 설정 및 표시하기
plt.legend(loc='upper right')
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
import numpy as np
import matplotlib.pyplot as plt
import os
# 데이터 불러오기 및 데이터 전처리
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 784).astype('float64') / 255
X_test = X_test.reshape(X_test.shape[0], 784).astype('float64') / 255
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)
# 모델 구조 생성 및 컴파일
model = Sequential()
model.add(Dense(512, input_dim=784, activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
# 체크포인트 및 자동 중단 설정
MODEL_DIR = "./model/"
if not os.path.exists(MODEL_DIR):
os.mkdir(MODEL_DIR)
modelpath = MODEL_DIR + "MNIST_MLP.hdf5"
checkpointer = ModelCheckpoint(filepath=modelpath, monitor='val_loss',
verbose=1, save_best_only=True)
early_stopping_callback = EarlyStopping(monitor='val_loss', patience=10)
# 학습 실행하기
history = model.fit(X_train, y_train, validation_split=0.25,
epochs=30, batch_size=200, verbose=0,
callbacks=[early_stopping_callback, checkpointer])
# 모델 성능 평가
print("\n Test Accuracy: %.4f" % (model.evaluate(X_test, y_test)[1]))
# 학습 과정 그래프로 나타내기
y_loss = history.history['loss']
y_vloss = history.history['val_loss']
x_len = np.arange(len(y_loss))
plt.plot(x_len, y_loss, marker='.', c='blue', label='Trainset_loss')
plt.plot(x_len, y_vloss, marker='.', c='red', label='Testset_loss')
plt.legend(loc='upper right')
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
컨볼루션 신경망(Convolutional Neural Network, CNN)은 이미지와 커널(또는 필터)에 대한 합성곱(convolution) 계산을 통해 이미지의 특징을 효과적으로 추출하도록 도와주는 기법입니다. 딥러닝을 진행할 때 합성곱층을 추가하여 정확도를 더욱 향상시킬 수 있습니다. 합성곱 신경망을 적용한 구조는 크게 세 개의 층으로 나눌 수 있습니다.
컨볼루션층은 컨볼루션 연산을 통해 이미지의 특징을 추출하는 층입니다. 컨볼루션 연산을 일반적으로 주어진 이미지에 필터를 이미지를 훑으면서 내적(dot product) 계산을 진행하여 출력을 만듭니다. 이렇게 이미지에 필터를 훑어가며 연산하는 것을 이미지 처리에서는 슬라이딩 윈도우 기법이라고도 합니다.
컨볼루션 계산은 필터를 이미지 위에 겹쳐 놓고 각각의 값들을 곱한 뒤 모두 더해주면 됩니다. 다음과 같이 이미지와 필터가 주어진 경우 컨볼루션 계산은 다음과 같습니다.
입력 이미지 | 필터 |
---|---|
![]() | ![]() |
컨볼루션 계산은 각각의 원소들을 곱하고 결과를 모두 더해줍니다.
결과는 다음과 같습니다.
앞서 언급했던 것처럼 이미지를 훑으면서 컨볼루션 계산을 진행하여 출력을 얻습니다. 이때 필터를 이동시키는 칸의 수를 스트라이드(stride)라 합니다.
이미지에 컨볼루션 계산을 진행하게 되면 원래 이미지보다 크기가 작은 결과를 얻게 됩니다. 컨볼루션 계산 이후에도 이미지의 크기를 유지하기 위해 패딩(padding)이라는 것을 추가합니다. 아래의 이미지는 zero-padding을 추가한 것입니다.
Keras API에서는 Conv2D()
를 이용하여 컨볼루션 층을 추가할 수 있습니다.
model.add(Conv2D(32, kernel_size=(3,3), input_shape=(28,28,1), activation='relu'))
Keras에서 Conv2D를 보면 파라미터에 대한 정보를 확인할 수 있습니다.
컨볼루션을 이용하여 이미지 특징을 추출하였지만 결과가 여전히 크고 복잡하면 다시 축소하는 풀링(pooling) 또는 서브 샘플링(sub sampling) 과정을 진행해야 합니다. 풀링 기법은 정해진 구역 안에서 최댓값을 뽑아내는 맥스 풀링(max pooling)과 평균값을 뽑아내는 평균 풀링(average pooling), 최솟값을 뽑아내는 민 풀링(min pooling) 등이 있습니다.
다음과 같은 이미지가 있을 때 가장 많이 사용되는 맥스 풀링을 사용하면 다음과 같습니다.
주어진 이미지
맥스 풀링 결과
Keras에서는 MaxPooling2D()
함수를 이용해서 구현 가능합니다. 다음과 같이 코드를 작성하여 2x2 크기의 구역을 나눠 맥스 풀링을 진행할 수 있습니다.
mode.add(MaxPooling2D(pool_size=(2,2)))
컨볼루션층과 풀링층에서는 이미지의 특징을 추출하는 과정이었고 fully-connected 층에서는 이미지 특징을 이용하여 분류하는 과정이 이루어집니다.
드롭아웃(drop out)은 과적합을 피하기 위해 은닉층에 배치된 노드 중 일부를 임의로 꺼 주는 작업을 추가하는 기법입니다.
Keras API에서는 Dropout()
함수를 이용하면 쉽게 적용이 가능합니다. 다음과 같이 25%의 노드를 끄도록 코드를 작성할 수 있습니다.
model.add(Dropout(0.25))
컨볼루션층과 풀링층에서는 이미지를 2차원 배열로 다루지만 1차원 배열로 바꿔야 활성화 함수가 있는 층에서 사용할 수 있습니다. 이 과정은 Flatten()
함수를 사용하여 진행할 수 있습니다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
import matplotlib.pyplot as plt
import numpy as np
# 데이터 불러오기 및 데이터 전처리
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32') / 255
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1).astype('float32') / 255
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
# CNN 설정
model = Sequential()
model.add(Conv2D(32, kernel_size=(3,3), input_shape(28,28,1), activation='relu'))
model.add(Conv2D(64, (3,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
# 모델 컴파일
model.compile(loss='categorical_crossentropy', optimizer='adam',
metrics=['accuracy'])
# 모델 최적화 관련 설정
modelpath = "./model/MNIST_CNN.hdf5"
checkpointer = ModelCheckpoint(filepath=modelpath, monitor='val_loss',
verbose=1, save_best_only=True)
early_stopping_callback = EarlyStopping(monitor='val_loss', patience=10)
# 모델 실행
history = model.fit(X_train, y_train, validation_split=0.25, epochs=30,
batch_size=200, verbose=0,
callbacks=[early_stopping_callback, checkpointer])
# 테스트 정확도 확인
print("\n Test Accuracy: %.4f" % (model.evaluate(X_test, y_test)[1])
# 그래프로 확인
y_loss = history.history['loss']
y_vloss = history.history['val_loss']
x_len = np.arange(len(y_loss))
plt.plot(x_len, y_loss, marker='.', c='blue', label='Trainset_loss')
plt.plot(x_len, y_vloss, marker='.', c='red', label='Testset_loss'
plt.legend(loc='upper right')
plt.grid()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
테스트 성능
검증셋 오차 그래프
이전보다 CNN을 적용했을 때 확실한 성능의 향상을 확인할 수 있습니다.
Reference
- 해당 글은 "모두의 딥러닝" 16장을 기반으로 작성되었습니다.