학습 목표
🥕 케라스 API를 사용하여 합성곱 신경망 모델을 만들어 MNIST 이미지 분류 작업
import tensorflow as tf
tf.keras.utils.set_random_seed(42)
실행마다 동일한 결과를 얻기 위해 케라스에 랜덤 시드를 사용하고 텐서플로 연산을 결정적으로 만들어둡니다.
from tensorflow import keras
from sklearn.model_selection import train_test_split
(train_input, train_target), (test_input, test_target) = \
keras.datasets.fashion_mnist.load_data()
train_scaled = train_input.reshape(-1, 28, 28, 1) / 255.0
train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
합성곱 신경망은 2차원 이미지를 그대로 사용하기 때문에 입력 이미지는 채널 차원이 있어야 합니다. 흑백 이미지의 경우 채널 차원이 없는 2차원 배열이지만 마지막에 채널 차원을 추가해서 입력해줍니다.
(48000, 28, 28) 크기의 train_input을 (48000, 28, 28, 1)
크기의 train_scaled로 만들었습니다.
model = keras.Sequential()
model.add(keras.layers.Conv2D(32, kernel_size=3, activation='relu',
padding='same', input_shape=(28,28,1)))
Sequential 클래스의 객체를 만들고 첫 번째 합성곱 층인 Conv2D를 추가합니다.
model.add(keras.layers.MaxPooling2D(2))
그 다음 풀링 층을 추가합니다. MaxPooling2D 클래스를 호출해서 최대 풀링을 사용했습니다. kernel_size가 가로세로 크기가 같다면 위와 같이 2를 지정해서 (2, 2) 크기의 풀링을 적용할 수 있습니다.
model.add(keras.layers.Conv2D(64, kernel_size=(3,3), activation='relu',
padding='same'))
model.add(keras.layers.MaxPooling2D(2))
두번째 합성곱-풀링 층을 추가합니다. 필터의 개수를 64개로 늘렸습니다.
특성맵의 크기가 풀링층을 통과하면서 절반으로 줄어 (7, 7, 64)가 됩니다.
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(100, activation='relu'))
model.add(keras.layers.Dropout(0.4))
model.add(keras.layers.Dense(10, activation='softmax'))
그 다음 특성 맵을 일렬로 펼쳐서 출력층에 전달합니다. 이 때 은닉층과 드롭아웃층을 추가해줍니다.
합성곱 신경망 구성이 끝났으니 모델 구조를 출력해보겠습니다!
model.summary()
summary( ) 매서드의 출력 결과에 합성곱 층과 풀링 층의 효과가 나타나 있습니다.
모델 파라미터 개수
첫 번째 합성곱 층:
두 번째 합성곱 층:
은닉층:
출력층:
keras.utils.plot_model(model)
plot_model( ) 함수를 사용해서 모델의 층 구성을 그림으로 그려보겠습니다.
keras.utils.plot_model(model, show_shapes=True)
show_shapes=True로 지정하면 입력과 출력의 크기를 함께 표시할 수 있습니다.
지금까지 만든 MNIST 패션 이미지 데이터를 분류하는 합성곱 신경망 모델을 한층 한층 만들었는데요, 신경망을 그림으로 표현하면 아래 내용과 같습니다.
이제 모델을 컴파일하고 훈련해보겠습니다.
케라스 API에서는 모델의 종류나 구성 방식과 상관없이 컴파일과 훈련 과정이 같습니다.
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-cnn-model.keras',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
restore_best_weights=True)
history = model.fit(train_scaled, train_target, epochs=20,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
Epoch 1/20 2025-02-09 19:41:01.175848: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz 2025-02-09 19:41:01.451088: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled. 1496/1500 [============================>.] - ETA: 0s - loss: 0.5084 - accuracy: 0.81792025-02-09 19:41:19.370317: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled. 1500/1500 [==============================] - 20s 11ms/step - loss: 0.5086 - accuracy: 0.8179 - val_loss: 0.3471 - val_accuracy: 0.8738 Epoch 2/20 1500/1500 [==============================] - 16s 11ms/step - loss: 0.3324 - accuracy: 0.8813 - val_loss: 0.2927 - val_accuracy: 0.8907 Epoch 3/20 1500/1500 [==============================] - 16s 11ms/step - loss: 0.2853 - accuracy: 0.8957 - val_loss: 0.2570 - val_accuracy: 0.9051 Epoch 4/20 1500/1500 [==============================] - 16s 10ms/step - loss: 0.2509 - accuracy: 0.9089 - val_loss: 0.2375 - val_accuracy: 0.9154 Epoch 5/20 1500/1500 [==============================] - 16s 10ms/step - loss: 0.2261 - accuracy: 0.9170 - val_loss: 0.2391 - val_accuracy: 0.9141 Epoch 6/20 1500/1500 [==============================] - 16s 10ms/step - loss: 0.2051 - accuracy: 0.9237 - val_loss: 0.2273 - val_accuracy: 0.9175 Epoch 7/20 1500/1500 [==============================] - 15s 10ms/step - loss: 0.1890 - accuracy: 0.9296 - val_loss: 0.2168 - val_accuracy: 0.9202 Epoch 8/20 1500/1500 [==============================] - 15s 10ms/step - loss: 0.1734 - accuracy: 0.9338 - val_loss: 0.2169 - val_accuracy: 0.9212 Epoch 9/20 1500/1500 [==============================] - 16s 10ms/step - loss: 0.1589 - accuracy: 0.9416 - val_loss: 0.2245 - val_accuracy: 0.9176
콜백(callbacks): 모델 학습의 조기 종료 조건
훈련 세트의 정확도가 이전보다 훨씬 좋아졌네요!!
손실 그래프를 그려서 조기 종료가 잘 이루어졌는지 확인해보겠습니다.
import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
검증 세트에 대한 손실이 점차 감소하다가 정체되기 시작하고 훈련세트에 대한 손실은 점점 더 낮아지고 있습니다. 이 그래프를 기반으로 일곱 번째 에포크를 최적으로 생각할 수 있습니다.
model.evaluate(val_scaled, val_target)
375/375 [==============================] - 2s 5ms/step - loss: 0.2168 - accuracy: 0.9202
검증 세트에 대한 모델 성능을 평가해보았습니다. fit( ) 매서드의 출력 중 일곱 번째 에포크의 출력과 동일합니다. EarlyStopping 콜백을 통해서 최상의 모델 파라미터로 잘 복원된 것을 알 수 있습니다.
predict( ) 매서드를 사용해 훈련된 모델을 가지고 새로운 데이터에 대한 예측을 만들어 보겠습니다.
plt.imshow(val_scaled[0].reshape(28, 28), cmap='gray_r')
plt.show()
val_scaled[0] 데이터는 핸드백 이미지입니다. 모델이 이 이미지에 대해서 어떤 예측을 만드는지 확인해보겠습니다.
preds = model.predict(val_scaled[0:1])
print(preds)
[[8.7171188e-19 1.7660993e-26 4.9542759e-21 1.7801612e-19 6.3786056e-18 4.7823439e-21 2.4926043e-19 3.2195997e-17 1.0000000e+00 4.5894585e-22]]
출력 결과를 보면 아홉번째 값이 1이고 다른값은 0에 가깝습니다. 막대그래프로도 표현해보겠습니다.
plt.bar(range(1, 11), preds[0])
plt.xlabel('class')
plt.ylabel('prob.')
plt.show()
9번째 클래스가 무엇인지 확인해보겠습니다.
import numpy as np
print(classes[np.argmax(preds)])
가방
해당 샘플을 '가방'으로 잘 예측했네요!!!
마지막으로 테스트 세트로 합성곱 신경망의 일반화 성능을 확인해보겠습니다.
model.evaluate(test_scaled, test_target)
[0.23534929752349854, 0.9161999821662903]
테스트 세트에 대한 점수는 검증 세트보다 조금 더 작습니다.
이 모델을 실제로 패션 아이템을 분류하는 데 사용한다믄 91%의 성능을 기대할 수 있습니다.
자료 출처: 한빛 미디어