Fashion MNIST 실습


케라스의 fashion_mnist 데이터의 카테고리를 인공신경망을 활용해 분류해보려 합니다.

처음으로 scikit-leran의 로지스틱 회귀를 통해 간단하게 분류 정확도를 체크하고,

간단한 인공신경망 구축, 은닉층 추가, 옵티마이저 설정과 과적합을 피하기 위한 방법을 추가하며 결과를 확인해보겠습니다.


Keras의 fashion_mnist데이터

  • 10개의 패션 아이템 클래스를 가지고 있습니다.

  • 28 x 28 픽셀의 흑백 이미지로 이루어져 있습니다.

keras의 fashion mnist 데이터 가져오기
from tensorflow.keras.datasets import fashion_mnist

(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()

X_train.shape, y_train.shape
((60000, 28, 28), (60000,))
데이터 확인해보기
import matplotlib.pyplot as plt
import numpy as np

# 0 ~ 9번째 X데이터 출력
fig, axs = plt.subplots(1, 10, figsize=(10, 10))

for i in range(10):
    axs[i].imshow(X_train[i], cmap='gray_r')
    axs[i].axis('off')
plt.show()

# 0 ~ 9번째 Y데이터 출력
print([y_train[i] for i in range(10)])

# 카테고리 번호를 출력해보기(return_counts : 각 카테고리당 몇개씩 들어있는지 확인)
print(np.unique(y_train, return_counts=True))

[9, 0, 0, 3, 0, 2, 7, 2, 5, 5]
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8), array([6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000]))

sikit-learn의 로지스틱 회귀로 예측 성능 확인해보기

  • 픽셀값은 0 ~ 255까지 이므로 0부터 1사이의 값으로 맞춰주기 위해 X데이터를 255.0으로 나누어줍니다.

  • 3차원의 X데이터를 1차원으로 펼쳐주기 위해 reshape를 해줍니다.

X_train = X_train / 255.0
X_test = X_test / 255.0

X_train_scaled = X_train.reshape(-1, 28 * 28)
X_test_scaled = X_test.reshape(-1, 28 * 28)

X_train_scaled.shape, X_test_scaled.shape
((60000, 784), (10000, 784))
  • 로지스틱 회귀를 적용하기 위해 loss 매개변수에 손실 함수로 ‘log’를 지정합니다.

  • max_iter 매개변수에 반복횟수로 5를 지정합니다.

  • 반복 실행시 결과가 동일하게 나오기 위해 난수 초기값을 random_state 매개변수로 지정합니다.

# scikit_learn의 SGDClassifier 클래스를 활용하여 경사하강법을 이용한 로지스틱 회귀를 사용
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import cross_validate

sc = SGDClassifier(loss='log', max_iter=5, random_state=42)

scores = cross_validate(sc, X_train_scaled, y_train, n_jobs=-1)
print(np.mean(scores['test_score']))  # 훈련데이터에 있어서 약 82프로의 정확도를 얻음
0.8192833333333333

10개의 클래스를 분류해야하는데 손실함수를 ‘log’로 사용하는 이유

  • SGD는 cross_entropy를 지정하는 곳이 따로 없기 때문에 10개의 클래스를 분류하기 위해 10개의

  • 예를 들어, 부츠를 양성, 나머지 9개를 음성으로 분류하여 1개의 모델을 훈련

  • 티셔츠를 양성, 나머지 9개를 음성으로 분류하여 1개의 모델을 훈련 -> 이런식으로 10개의 모델을 훈련

  • 10개의 계산값이 나오면 softmax함수(이진 분류일땐 sigmoid함수)를 사용하여 확률로 바꿔줍니다.

  • 이런식으로 이진분류를 다중분류처럼 사용하는 방법을 OVR(One verses Rest)라고 합니다.

인공 신경망으로 구현

  • 로지스틱 회귀 경우 : 픽셀1 x w1 + 픽셀2 x w2 + … + 픽셀784 x w784 + b => 10개의 모델

  • 이를 인공 신경망으로 구현해봅니다.

# 검증 데이터 0.2 비율로 분리
from sklearn.model_selection import train_test_split
X_train_scaled, X_val_scaled, y_train, y_val = train_test_split(
    X_train_scaled, y_train, test_size=0.2, random_state=42)

X_train_scaled.shape, y_train.shape, X_val_scaled.shape, y_val.shape
((48000, 784), (48000,), (12000, 784), (12000,))
모델 정의
  • Dense 레이어를 사용하여 은닉층과 출력층을 생성합니다.

  • 은닉층의 활성화 함수에는 자주 쓰이는 ‘relu’를 사용합니다.

  • 출력층의 활성화 함수에는 다중분류이므로 ‘softmax’(이진분류일때는 ‘sigmoid’)를 사용합니다.

모델 컴파일
  • 손실 함수

    • 이진 분류 : binary_crossentropy

    • 다중 분류 : categorical_crossentropy

  • sparse_categorical_crossentropy란?

    • y데이터의 값은 0 ~ 9 까지의 정수인것을 확인했습니다.

    이 정수값을 그대로 사용할 순 없고, 출력층에는 10개의 유닛에서 softmax함수값을 거쳐 10개의 확률값이 나옵니다.

    • crossentropy의 공식에 따라 10개의 확률값에 각각 로그를 취하고 타깃값과 곱하게 됩니다.

    (샘플이 티셔츠일 확률 : a1 => -log(a1) x target값, -log(a2) x target값, …)

    • 여기서, 티셔츠는 첫번째 원소가 1이고 나머지 0([1, 0, 0, … , 0])인 원 핫 인코딩이 되어있어야지만

    첫번째 unit을 제외한 나머지 unit에서의 출력값이 모두 0이 곱해져 상쇄되어 티셔츠에 해당되는 뉴런의 출력값만 손실에 반영됩니다.

    • 하지만 원 핫 인코딩을 사용하지 않고, Y데이터의 정수값 그대로를 사용하려면 sparse_categorical_crossentropy를 사용합니다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

model = Sequential()

# 다중분류이므로 softmax 사용
model.add(Dense(10, activation='softmax', input_shape=(784, )))

model.compile(loss='sparse_categorical_crossentropy',
             optimizer='adam',
             metrics=['acc'])
# 모델 학습
history = model.fit(X_train_scaled, y_train, epochs=15, batch_size=100,
                    validation_data=(X_val_scaled, y_val))
Epoch 1/15
480/480 [==============================] - 1s 1ms/step - loss: 0.7469 - acc: 0.7560 - val_loss: 0.5672 - val_acc: 0.8115
Epoch 2/15
480/480 [==============================] - 1s 1ms/step - loss: 0.5187 - acc: 0.8279 - val_loss: 0.5018 - val_acc: 0.8309
Epoch 3/15
480/480 [==============================] - 0s 1ms/step - loss: 0.4758 - acc: 0.8400 - val_loss: 0.4818 - val_acc: 0.8371
Epoch 4/15
480/480 [==============================] - 0s 1ms/step - loss: 0.4544 - acc: 0.8457 - val_loss: 0.4677 - val_acc: 0.8382
Epoch 5/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4401 - acc: 0.8502 - val_loss: 0.4564 - val_acc: 0.8411
Epoch 6/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4293 - acc: 0.8547 - val_loss: 0.4435 - val_acc: 0.8470
Epoch 7/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4214 - acc: 0.8556 - val_loss: 0.4428 - val_acc: 0.8488
Epoch 8/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4149 - acc: 0.8583 - val_loss: 0.4364 - val_acc: 0.8489
Epoch 9/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4089 - acc: 0.8605 - val_loss: 0.4265 - val_acc: 0.8509
Epoch 10/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4049 - acc: 0.8609 - val_loss: 0.4224 - val_acc: 0.8533
Epoch 11/15
480/480 [==============================] - 1s 1ms/step - loss: 0.4026 - acc: 0.8615 - val_loss: 0.4261 - val_acc: 0.8514
Epoch 12/15
480/480 [==============================] - 1s 1ms/step - loss: 0.3986 - acc: 0.8623 - val_loss: 0.4190 - val_acc: 0.8552
Epoch 13/15
480/480 [==============================] - 1s 1ms/step - loss: 0.3952 - acc: 0.8644 - val_loss: 0.4185 - val_acc: 0.8533
Epoch 14/15
480/480 [==============================] - 1s 1ms/step - loss: 0.3925 - acc: 0.8634 - val_loss: 0.4213 - val_acc: 0.8560
Epoch 15/15
480/480 [==============================] - 1s 1ms/step - loss: 0.3901 - acc: 0.8659 - val_loss: 0.4155 - val_acc: 0.8560
# 테스트 데이터를 모델에 적용하여 평가하기 [loss, acc]
model.evaluate(X_test_scaled, y_test)
313/313 [==============================] - 0s 854us/step - loss: 0.4530 - acc: 0.8427
[0.45295682549476624, 0.8427000045776367]

심층 신경망

  • 입력층 784개의 뉴런과 은닉층 100개 뉴런, 출력층 10개 뉴런을 생성해보았습니다.
# 모델을 좀 더 깊게(은닉층 추가) 수정
model = Sequential()

model.add(Dense(100, activation='relu', input_shape=(784, )))  # 은닉층
model.add(Dense(10, activation='softmax'))   # 출력층

model.compile(loss='sparse_categorical_crossentropy',
             optimizer='adam',
             metrics=['acc'])
model의 summary() 메서드
  • layer의 정보, 각 layer의 출력의 크기, 파라미터의 갯수를 확인할 수 있습니다.

  • 은닉층의 파라미터 갯수 : 784개의 입력 층과 은닉층의 100개의 뉴런이 있고, 각 뉴런은 b(절편)이 존재하므로

100개의 b값이 존재합니다.

따라서, 784 x 100 + 100 = 78500이 됩니다.

  • 출력층의 파라미터 갯수 : 은닉층에서 나오는 100개의 출력, 10개의 출력층 뉴런이 있으므로

100 x 10 + 10 = 1010이 됩니다.

model.summary()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_1 (Dense)             (None, 100)               78500     
                                                                 
 dense_2 (Dense)             (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________
model.fit(X_train_scaled, y_train,
         epochs=15, batch_size=128, validation_data=(X_val_scaled, y_val))
Epoch 1/15
375/375 [==============================] - 1s 2ms/step - loss: 0.5933 - acc: 0.7972 - val_loss: 0.4648 - val_acc: 0.8373
Epoch 2/15
375/375 [==============================] - 1s 1ms/step - loss: 0.4281 - acc: 0.8506 - val_loss: 0.4153 - val_acc: 0.8555
Epoch 3/15
375/375 [==============================] - 1s 1ms/step - loss: 0.3919 - acc: 0.8607 - val_loss: 0.3894 - val_acc: 0.8612
Epoch 4/15
375/375 [==============================] - 1s 1ms/step - loss: 0.3631 - acc: 0.8710 - val_loss: 0.3836 - val_acc: 0.8612
Epoch 5/15
375/375 [==============================] - 1s 2ms/step - loss: 0.3414 - acc: 0.8799 - val_loss: 0.3521 - val_acc: 0.8702
Epoch 6/15
375/375 [==============================] - 1s 2ms/step - loss: 0.3286 - acc: 0.8834 - val_loss: 0.3569 - val_acc: 0.8742
Epoch 7/15
375/375 [==============================] - 1s 1ms/step - loss: 0.3144 - acc: 0.8865 - val_loss: 0.3525 - val_acc: 0.8730
Epoch 8/15
375/375 [==============================] - 1s 1ms/step - loss: 0.3006 - acc: 0.8908 - val_loss: 0.3457 - val_acc: 0.8777
Epoch 9/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2924 - acc: 0.8941 - val_loss: 0.3365 - val_acc: 0.8792
Epoch 10/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2800 - acc: 0.8987 - val_loss: 0.3267 - val_acc: 0.8859
Epoch 11/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2723 - acc: 0.9005 - val_loss: 0.3283 - val_acc: 0.8832
Epoch 12/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2653 - acc: 0.9034 - val_loss: 0.3252 - val_acc: 0.8847
Epoch 13/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2559 - acc: 0.9055 - val_loss: 0.3183 - val_acc: 0.8844
Epoch 14/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2508 - acc: 0.9078 - val_loss: 0.3135 - val_acc: 0.8886
Epoch 15/15
375/375 [==============================] - 1s 2ms/step - loss: 0.2430 - acc: 0.9116 - val_loss: 0.3236 - val_acc: 0.8815
<keras.callbacks.History at 0x7f89740a0340>
model.evaluate(X_test_scaled, y_test)
313/313 [==============================] - 0s 714us/step - loss: 0.3513 - acc: 0.8715
[0.3512624502182007, 0.8715000152587891]

Relu함수와 Flatten층

  • Relu함수

간단하게 말하면 Max함수입니다.

뉴런의 출력값이 0보다 크면 그대로 출력, 0보다 작으면 0으로 출력해주는 단순한 함수입니다.

  • Flatten층

실제로 가중치는 존재하지 않는 층이며, 편의를 위해서 추가하는 층입니다.

위에서 X데이터를 28x28 -> 784의 1차원 배열로 풀어주는 전처리 과정을 포함했습니다.

이러한 작업을 Fletten이 대신 해줍니다.

Flatten의 input_shape에 (28, 28)로 지정해주면 784의 1차원 배열로 전달해줍니다.

summary()에서 보면 Flatten층의 출력 크기는 784로 나오는것을 확인할 수 있습니다.

# Flatten층 사용하기 위해 reshape을 하지 않아도 됩니다.
# 그렇기 위해 귀찮겠지만 다시 데이터를 다시 로드했습니다.

(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()

X_train = X_train / 255.0
X_test = X_test / 255.0
from tensorflow.keras.layers import Flatten

model = Sequential()

model.add(Flatten(input_shape=(28, 28)))
model.add(Dense(100, activation='relu'))
model.add(Dense(10, activation='softmax'))

model.compile(loss='sparse_categorical_crossentropy',
             optimizer='adam',
             metrics=['acc'])
model.summary()
Model: "sequential_6"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten_4 (Flatten)         (None, 784)               0         
                                                                 
 dense_11 (Dense)            (None, 100)               78500     
                                                                 
 dense_12 (Dense)            (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________
history = model.fit(X_train, y_train, epochs=20,
                    batch_size=128, validation_split=0.2)
Epoch 1/20
375/375 [==============================] - 1s 2ms/step - loss: 0.5939 - acc: 0.8002 - val_loss: 0.4570 - val_acc: 0.8438
Epoch 2/20
375/375 [==============================] - 1s 1ms/step - loss: 0.4267 - acc: 0.8518 - val_loss: 0.4048 - val_acc: 0.8581
Epoch 3/20
375/375 [==============================] - 1s 2ms/step - loss: 0.3846 - acc: 0.8652 - val_loss: 0.3798 - val_acc: 0.8639
Epoch 4/20
375/375 [==============================] - 1s 1ms/step - loss: 0.3614 - acc: 0.8720 - val_loss: 0.3696 - val_acc: 0.8674
Epoch 5/20
375/375 [==============================] - 1s 1ms/step - loss: 0.3388 - acc: 0.8785 - val_loss: 0.3745 - val_acc: 0.8658
Epoch 6/20
375/375 [==============================] - 1s 2ms/step - loss: 0.3238 - acc: 0.8849 - val_loss: 0.3453 - val_acc: 0.8755
Epoch 7/20
375/375 [==============================] - 1s 2ms/step - loss: 0.3120 - acc: 0.8859 - val_loss: 0.3625 - val_acc: 0.8701
Epoch 8/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2972 - acc: 0.8907 - val_loss: 0.3300 - val_acc: 0.8813
Epoch 9/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2896 - acc: 0.8948 - val_loss: 0.3335 - val_acc: 0.8792
Epoch 10/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2773 - acc: 0.8988 - val_loss: 0.3262 - val_acc: 0.8821
Epoch 11/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2702 - acc: 0.9020 - val_loss: 0.3237 - val_acc: 0.8848
Epoch 12/20
375/375 [==============================] - 1s 1ms/step - loss: 0.2620 - acc: 0.9047 - val_loss: 0.3225 - val_acc: 0.8835
Epoch 13/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2531 - acc: 0.9072 - val_loss: 0.3294 - val_acc: 0.8831
Epoch 14/20
375/375 [==============================] - 1s 1ms/step - loss: 0.2489 - acc: 0.9087 - val_loss: 0.3149 - val_acc: 0.8861
Epoch 15/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2437 - acc: 0.9115 - val_loss: 0.3265 - val_acc: 0.8832
Epoch 16/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2362 - acc: 0.9131 - val_loss: 0.3123 - val_acc: 0.8893
Epoch 17/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2324 - acc: 0.9145 - val_loss: 0.3193 - val_acc: 0.8874
Epoch 18/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2246 - acc: 0.9180 - val_loss: 0.3212 - val_acc: 0.8847
Epoch 19/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2232 - acc: 0.9179 - val_loss: 0.3140 - val_acc: 0.8867
Epoch 20/20
375/375 [==============================] - 1s 2ms/step - loss: 0.2141 - acc: 0.9224 - val_loss: 0.3102 - val_acc: 0.8913
model.evaluate(X_test, y_test)
313/313 [==============================] - 0s 765us/step - loss: 0.3447 - acc: 0.8824
[0.34466418623924255, 0.8823999762535095]

옵티마이저

  • 학습률 : 경사하강법을 비유하자면, 산을 내려가면서 최적값을 찾아갈때 이동하는 거리.

학습률을 너무 높게 잡는다면 최적값을 지나칠 수도 있기 때문에 적절한 학습률 조정이 필요합니다.

기본 경사 하강법 옵티마이저

  • SGD

    • 기본 학습률은 0.01입니다.

    model.compile(optimizer=’sgd’)

    • 학습률 조정도 가능합니다. (0.1로 변경)

    sgd = tensorflow.keras.optimizers.SGD(learning_rate=0.1)

  • 모멘텀

    • SGD에서 momentum > 0

    sgd = tensorflow.keras.optimizers.SGD(momentum=0.9)

  • 네스테로프 모멘텀

    • SGD의 설정에서 nesterov = True를 주어 사용합니다.


경사하강법에서 학습률에 따라 최적값을 찾아갈때, 최적값과 멀리 있을 땐 높은 이동 거리로 빠르게 접근하고,

최적값과 가까워질 땐 좁은 거리로 이동하며 최대한 최적값에 수렴해가는게 좋습니다.

이렇게 변화할 수 있는 학습률을 가지고 있는 것이 적응적 학습률 옵티마이저입니다.


적응적 학습률 옵티마이저

  • RMSProp

  • Adam

  • Adagrad

정확도와 손실 시각화해보기

model.fit()의 history는 훈련 과정 중 epoch별로 loss와 acc의 결과를 담고 있습니다.

이를 활용해 모델의 정확도와 손실을 시각화 해볼 수 있습니다.


시각화를 한 결과, epoch가 증가할 수록 훈련 데이터와 검증 데이터의 loss가 같이 줄어들고,

정확도는 동시에 증가하는 것을 볼 수 있습니다.

즉, 과적합이 많이 나타나지 않는 괜찮은 성능의 모델이라 확인할 수 있습니다.

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['acc']
val_acc = history.history['val_acc']

epochs = range(1, len(loss) + 1)
fig = plt.figure(figsize=(10, 5))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, loss, color='blue', label='train_loss')
ax1.plot(epochs, val_loss, color='orange', label='val_loss')
ax1.set_title('train and val loss')
ax1.set_xlabel('epochs')
ax1.set_ylabel('loss')
ax1.legend()

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, acc, color='green', label='train_acc')
ax2.plot(epochs, val_acc, color='red', label='val_acc')
ax2.set_title('train and val acc')
ax2.set_xlabel('epochs')
ax2.set_ylabel('acc')
ax2.legend()
<matplotlib.legend.Legend at 0x7f8940a50910>

드롭아웃

  • 가장 많이 사용하는 과적합을 피하는 방법 중 하나입니다.

  • 모델을 훈련할 때, 0 ~ 1사이의 확률로 랜덤적으로 뉴런의 계산을 끄고 학습을 진행합니다.

  • 제외하지 않은 나머지 뉴런에서만 훈련이 이루어집니다.

  • 특정 뉴런의 의존하게 되는 계산을 막을 수 있습니다.

  • 모델을 평가할때는 모든 뉴런을 사용합니다.

from tensorflow.keras.layers import Dropout

model = Sequential()

model.add(Flatten(input_shape=(28, 28)))
model.add(Dense(100, activation='relu'))
model.add(Dropout(0.3))   # 0.3비율로 Dropout
model.add(Dense(10, activation='softmax'))

model.compile(loss='sparse_categorical_crossentropy',
             optimizer='adam',
             metrics=['acc'])

model.summary()
Model: "sequential_9"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten_7 (Flatten)         (None, 784)               0         
                                                                 
 dense_17 (Dense)            (None, 100)               78500     
                                                                 
 dropout_4 (Dropout)         (None, 100)               0         
                                                                 
 dense_18 (Dense)            (None, 10)                1010      
                                                                 
=================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
_________________________________________________________________
history = model.fit(X_train, y_train,
                   epochs=20,
                   validation_split=0.2)
Epoch 1/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.5865 - acc: 0.7941 - val_loss: 0.4211 - val_acc: 0.8484
Epoch 2/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.4389 - acc: 0.8434 - val_loss: 0.4098 - val_acc: 0.8489
Epoch 3/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.4026 - acc: 0.8535 - val_loss: 0.3723 - val_acc: 0.8635
Epoch 4/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3810 - acc: 0.8605 - val_loss: 0.3629 - val_acc: 0.8709
Epoch 5/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3659 - acc: 0.8661 - val_loss: 0.3551 - val_acc: 0.8708
Epoch 6/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3514 - acc: 0.8716 - val_loss: 0.3622 - val_acc: 0.8639
Epoch 7/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3432 - acc: 0.8738 - val_loss: 0.3498 - val_acc: 0.8747
Epoch 8/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3318 - acc: 0.8772 - val_loss: 0.3304 - val_acc: 0.8803
Epoch 9/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3288 - acc: 0.8775 - val_loss: 0.3341 - val_acc: 0.8805
Epoch 10/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3150 - acc: 0.8828 - val_loss: 0.3430 - val_acc: 0.8723
Epoch 11/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3119 - acc: 0.8841 - val_loss: 0.3254 - val_acc: 0.8847
Epoch 12/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3086 - acc: 0.8851 - val_loss: 0.3414 - val_acc: 0.8780
Epoch 13/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.3001 - acc: 0.8880 - val_loss: 0.3325 - val_acc: 0.8831
Epoch 14/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2963 - acc: 0.8879 - val_loss: 0.3212 - val_acc: 0.8842
Epoch 15/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2917 - acc: 0.8915 - val_loss: 0.3254 - val_acc: 0.8861
Epoch 16/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2840 - acc: 0.8941 - val_loss: 0.3248 - val_acc: 0.8859
Epoch 17/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2847 - acc: 0.8938 - val_loss: 0.3197 - val_acc: 0.8875
Epoch 18/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2788 - acc: 0.8957 - val_loss: 0.3405 - val_acc: 0.8804
Epoch 19/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2766 - acc: 0.8964 - val_loss: 0.3131 - val_acc: 0.8906
Epoch 20/20
1500/1500 [==============================] - 2s 1ms/step - loss: 0.2723 - acc: 0.8971 - val_loss: 0.3260 - val_acc: 0.8868

시각화를 해보면, Dropout을 사용했을 때 train_loss와 val_loss의 폭이 좀 더 줄어들고,

val_loss의 증가율이 좀 더 낮아졌음을 볼 수 있습니다.

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['acc']
val_acc = history.history['val_acc']

epochs = range(1, len(loss) + 1)
fig = plt.figure(figsize=(10, 5))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, loss, color='blue', label='train_loss')
ax1.plot(epochs, val_loss, color='orange', label='val_loss')
ax1.set_title('train and val loss')
ax1.set_xlabel('epochs')
ax1.set_ylabel('loss')
ax1.legend()

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, acc, color='green', label='train_acc')
ax2.plot(epochs, val_acc, color='red', label='val_acc')
ax2.set_title('train and val acc')
ax2.set_xlabel('epochs')
ax2.set_ylabel('acc')
ax2.legend()
<matplotlib.legend.Legend at 0x7f88e63ab9d0>

모델 저장 및 복원

  • model.save_weights() : 모델의 구조는 저장하지않고, 파라미터만 저장합니다. (가중치, 편향)

  • model.load_weights() : 저장된 모델 객체를 불러옵니다.


  • model.save() : 모델 구조 자체를 모두 저장합니다.

  • model.load_model() : 저장된 모델을 불러옵니다.

# 모델 저장
model.save('./fashion_mnist_model-whole.h5')

model.predict() : 10개의 클래스가 존재하므로 각 샘플마다 10개의 확률을 출력해줍니다.

10개의 확률 중 np.argmax()를 통해 가장 높은 값을 찾아서 그 인덱스를 예측값으로 사용합니다.

얻은 예측값과 실제 테스트 데이터를 비교하여 평균을 내어 정확도를 내줍니다.

아래에서 예측을 수행한 결과 약 88%의 정확도를 확인할 수 있습니다.

# model.predict() : 각 샘플마다 10개의 확률을 출력해주는 메서드
# 10개의 확률 중 가장 높은 값을 찾아서 그 인덱스를 예측 값으로 사용합니다.

predictions = model.predict(X_test)
val_labels = np.argmax(predictions, axis=1)   # axis=1 : 행 기준으로 연산 수행(<-> axis=0 : 열 기준)
print(np.mean(val_labels == y_test))   # True : 1, False : 0
0.8728

조기종료

학습이 진행될수록 학습셋의 정확도는 올라가지만 과적합으로 인해 테스트셋의 실험 결과가 점점 나빠질 수 있습니다.

이렇게 학습이 진행되어도 텟트셋 오차가 줄지 않을 경우 학습을 멈추게 하는 함수입니다.


  • patience = 2 : 검증셋의 손실이 2번 증가하면 중지

  • restore_best_weights=True : 가장 손실이 낮았던 곳으로 되돌리기

import keras

checkpoint_cb = keras.callbacks.ModelCheckpoint('fashion_mnist_model-whole.h5')
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
                                                 restore_best_weights=True)

history = model.fit(X_train, y_train, epochs=50, verbose=0,
                   validation_split=0.2, callbacks=[checkpoint_cb, early_stopping_cb])

print(f"종료될 떄의 epoch : {early_stopping_cb.stopped_epoch}")

loss = history.history['loss']
val_loss = history.history['val_loss']

plt.plot(loss)
plt.plot(val_loss)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
종료될 떄의 epoch : 11

댓글남기기