Tensorflow 개발자 자격증 준비하기(2)

Tensorflow 개발자 자격증 준비하기(2)

Basic Classification with CNN

이번에는 케라스의 패션 MNIST 데이터셋을 사용해 10개의 카테고리로 옷을 분류하는 문제를 해결해보겠다. 텐서플로 기본 튜토리얼을 참고했습니다.

다음 튜토리얼을 참고했습니다.

첫번째 신경망 훈련하기 : 기초적인 분류 문제

  • 운동화, 셔츠 등의 옷 이미지를 분류하는 신경망 모델을 구축한다.

  • 10개의 카테고리, 7만개의 이미지로 구성된 fasion mnist 데이터셋을 사용한다(기본 mnist 데이터셋은 손글씨 숫자로 이루어져 있다.)

    : 네트워크 훈련에 6만개의 이미지를 사용한다. 테스트 데이터는 1만개의 이미지를 사용한다.

1) 데이터셋 로드하기

1
2
3
4
5
6
7
# tensorflow와 tf.keras를 임포트합니다
import tensorflow as tf
from tensorflow import keras

fashion_mnist = keras.datasets.fashion_mnist

(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
  • load_data() : 4개의 NumPy 배열이 반환된다. (학습에 사용되는 훈련세트, 테스트에 사용되는 훈련세트)
  • 각각의 이미지28 * 28 크기의 numpy배열이다.
  • 각 이미지는 카테고리를 나타내는 0에서 9사이의 정수인 하나의 label과 매핑되어 있다.

2) 데이터 전처리

  • 각 이미지가 갖는 픽셀값의 범위는 0255 이다. 이를 01사이의 값으로 조정한다.

    1
    2
    3
    train_images = train_images / 255.0

    test_images = test_images / 255.0
  • 첫 25개 이미지와, 각 이미지의 카테고리명을 출력해본다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    plt.figure(figsize=(10,10))
    for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    plt.xlabel(class_names[train_labels[i]])
    plt.show()

3) 모델 구성

1
2
3
4
5
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
  • tf.keras.layers.Flatten : 2차원 배열의 이미지포맷을 28 * 28 = 784 픽셀의 1차원 배열로 변환한다.
    • 이미지 내 픽셀의 행을 펼쳐서 일렬로 늘린다.
    • 학습되는 가중치는 없다. 데이터 변환만 진행한다.
  • tf.keras.layers.Dense : 밀집 연결층 혹은 완전 연결층이라고 지칭.
    • 위의 코드에서 첫번째 Dense층은 128개의 노드(뉴런)를 갖는다.
    • 두번째 층은 10개의 카테고리에 이미지가 속할 확률을 출력해내는 softmax층이다.
1
2
3
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
  • optimizer : 데이터와 손실 값을 바탕으로 모델의 가중치를 업데이트하는 방법
  • loss : 훈련하는 동안 모델의 오차를 측정하는 방법. 모델의 학습이 올바른 방향으로 향하도록 이 손실함수의 값을 최소화할 필요가 있다.
  • metrics : 훈련단계와 테스트단계를 모니터링 하기 위해 사용한다. 위의 코드에서 지표로 사용하는 accuracy는 올바르게 분류된 이미지의 비율을 의미한다.

4) 모델 훈련

1
model.fit(train_images, train_labels, epochs=5)

5) 정확도 평가

  • 테스트 데이터셋에서 모델의 성능을 비교한다.
1
2
3
test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)

print('\n테스트 정확도:', test_acc)

나의 풀이

: 일단 먼저 튜토리얼의 코드를 그대로 사용해봤다. 썩 성능이 좋지 않았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Basic Datasets Question
#
# Create a classifier for the Fashion MNIST dataset
# Note that the test will expect it to classify 10 classes and that the
# input shape should be the native size of the Fashion MNIST dataset which is
# 28x28 monochrome. Do not resize the data. YOur input layer should accept
# (28,28) as the input shape only. If you amend this, the tests will fail.
#
import tensorflow as tf
from tensorflow import keras

fashion_mnist = tf.keras.datasets.fashion_mnist

(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
train_images = train_images / 255.0
test_images = test_images / 255.0

def solution_model():

model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])

model.fit(train_images, train_labels, epochs=5)

return model

# Note that you'll need to save your model as a .h5 like this
# This .h5 will be uploaded to the testing infrastructure
# and a score will be returned to you
if __name__ == '__main__':
model = solution_model()

test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print('\n테스트 정확도:', test_acc)
1
2
3
4
5
6
7
8
9
10
11
12
13
Epoch 1/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4936 - accuracy: 0.8256
Epoch 2/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.3743 - accuracy: 0.8652
Epoch 3/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.3343 - accuracy: 0.8778
Epoch 4/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.3118 - accuracy: 0.8846
Epoch 5/5
1875/1875 [==============================] - 3s 2ms/step - loss: 0.2913 - accuracy: 0.8923
313/313 - 0s - loss: 0.3574 - accuracy: 0.8679

테스트 정확도: 0.867900013923645

실제 시험에서 테스트의 정확도는 89%, loss는 33% 이하여야 합격이다.

튜토리얼의 코드가 생각보다 성능이 안나와서 이것저것 개선을 시도해봤다.


시도 1)

: 단순히 생각해봤을때 가장 기본적이라고 생각되는 조건들을 추가해봤다.

  1. epoch 증가 : 학습 횟수를 5에서 30회로 늘렸다.
  2. 검증 데이터셋 사용 : fasion mnist에서 제공되는 테스트 데이터셋을 실제 학습이 잘 되고있는지를 검증하는 validation set으로 추가했다.
  3. 조기종료 조건 추가 : 학습 횟수를 늘린만큼 잘못 학습이 될 경우를 방지하기 위해 조기종료 조건을 추가했다.

대충 바뀐 코드부분은 아래와 같다.

1
2
3
es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)

model.fit(train_images, train_labels, epochs=30, callbacks=es, validation_data=(test_images, test_labels))
  • 검증 데이터셋의 오차가 4번 이상 증가하면 과적합으로 판단하고 학습을 종료시키도록 했다.
1
2
3
4
5
6
Epoch 16/30
1875/1875 [==============================] - 4s 2ms/step - loss: 0.1993 - accuracy: 0.9251 - val_loss: 0.3415 - val_accuracy: 0.8849
Epoch 00016: early stopping
313/313 - 0s - loss: 0.3415 - accuracy: 0.8849

테스트 정확도: 0.8848999738693237

전반적으로 성능이 조금 좋아졌다. 아직 합격기준에는 미치지 못한다.

그리고 계속 이런저런 코드를 찾아보니 categorical_crossentropy라는 손실함수를 사용하는 경우도 있어서 내 코드의 sparse_categorical_crossentropy 손실함수와의 차이점이 궁금해졌다. 찾아봄.

categorical_crossentropysparse_categorical_crossentropy 의 차이점은?

-> 원핫코딩한 데이터의 분류 = categorical_crossentropy. input shape과 output shape의 크기가 같다.

시도 2)

  1. 조기종료 조건 수정 : 데이터셋 오차의 증가 허용을 4번 -> 3번으로 줄였다.
  2. 최적 모델 저장 : 모델 체크포인트를 설정해서 val_accuracy가 증가한 경우에만 모델을 저장했다.
1
2
3
4
5
6
7
8
9
10
11
12
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

...

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=3)
mc = ModelCheckpoint('best_model.h5', monitor = 'val_accuracy', mode = 'max', verbose = 1, save_best_only = True)

model.fit(train_images, train_labels, epochs=30, callbacks=[es, mc], validation_data=(test_images, test_labels))

loaded_model = keras.models.load_model('best_model.h5')

return loaded_model

변화가 사알짝 있었다. 5번 시도해본 결과 1번 합격기준에 맞췄다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Epoch 00011: early stopping
313/313 - 0s - loss: 0.3351 - accuracy: 0.8876
테스트 정확도: 0.8876000046730042

Epoch 00009: early stopping
313/313 - 1s - loss: 0.3240 - accuracy: 0.8842
테스트 정확도: 0.8841999769210815

Epoch 00013: early stopping
313/313 - 0s - loss: 0.3313 - accuracy: 0.8928
테스트 정확도: 0.892799973487854

Epoch 00014: early stopping
313/313 - 0s - loss: 0.3192 - accuracy: 0.8916
테스트 정확도: 0.8916000127792358

Epoch 00010: early stopping
313/313 - 0s - loss: 0.3199 - accuracy: 0.8863
테스트 정확도: 0.8863000273704529

하지만 아직 한참 모자란다ㅠ 특히 loss부분이 크게 줄지 않는 것 같다.

시도 3)

: 모델 복잡도를 증가시키고 Dropout을 추가해줘봤다

1
2
3
4
5
6
7
8
model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(256, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(10, activation='softmax')
])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Epoch 00018: early stopping
313/313 - 0s - loss: 0.3267 - accuracy: 0.8895
테스트 정확도: 0.8895000219345093

Epoch 00009: early stopping
313/313 - 0s - loss: 0.3385 - accuracy: 0.8802
테스트 정확도: 0.8802000284194946

Epoch 00011: early stopping
313/313 - 0s - loss: 0.3385 - accuracy: 0.8805
테스트 정확도: 0.8805000185966492

Epoch 00009: early stopping
313/313 - 0s - loss: 0.3637 - accuracy: 0.8756
테스트 정확도: 0.8755999803543091

Epoch 00017: early stopping
313/313 - 0s - loss: 0.3238 - accuracy: 0.8895
테스트 정확도: 0.8895000219345093

오히려 결과가 더 안좋아졌다. 음.. 어떻게 해야하지

시도 4)

: CNN을 사용하고 모델 복잡도를 확 올려봤다. 컨볼루션 레이어를 생성하는 Conv2D 클래스는 4차원 텐서를 입력으로 받는다. 따라서 fasion_mnist의 28 * 28 이미지 데이터를 reshape해줄 필요가 있다.

1
2
train_images = tf.reshape(train_images, shape=[60000,28,28,1])
test_images = tf.reshape(test_images, shape=[10000,28,28,1])

모델은 구글링으로 적당히 짜깁기해서 아래처럼 구성해봤다.

1
2
3
4
5
6
7
8
9
10
11
model = keras.Sequential([
keras.layers.Conv2D(filters=32, kernel_size=3, strides=(2, 2), activation='relu', input_shape=(28, 28, 1)),
keras.layers.Conv2D(filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
keras.layers.MaxPooling2D(pool_size=2),
keras.layers.Flatten(),
keras.layers.Dense(1024, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(256, activation='relu'),
keras.layers.Dropout(0.2),
keras.layers.Dense(10, activation='softmax')
])

오 유의미한 변화가 있었다. 전반적인 loss가 0.3 이하로 줄어들고 정확도는 90%를 평균적으로 넘었다. 이정도면 턱걸이지만 시험의 통과는 가능한 수준이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Epoch 00008: early stopping
313/313 - 1s - loss: 0.2781 - accuracy: 0.9010
테스트 정확도: 0.9010000228881836

Epoch 00008: early stopping
313/313 - 2s - loss: 0.2836 - accuracy: 0.9018
테스트 정확도: 0.9017999768257141

Epoch 00008: early stopping
313/313 - 1s - loss: 0.3023 - accuracy: 0.9014
테스트 정확도: 0.9014000296592712

Epoch 00008: early stopping
313/313 - 1s - loss: 0.3116 - accuracy: 0.9024
테스트 정확도: 0.902400016784668

Epoch 00010: early stopping
313/313 - 1s - loss: 0.3102 - accuracy: 0.9064
테스트 정확도: 0.9064000248908997

이게 CNN을 사용해서 올라간건지 아니면 모델 복잡도를 증가시킨것도 변화에 의미가 있었는지 궁금해서 시도2) 의 모델에 CNN만 추가해서 다시 테스트해봤다.

배치를 쓰지않고 느려터진 CNN을 돌리려니 모델 학습되는걸 기다리는것만 백년걸린다. 아 힘드러.

1
2
3
4
5
6
7
8
model = keras.Sequential([
keras.layers.Conv2D(filters=32, kernel_size=3, strides=(2, 2), activation='relu', input_shape=(28, 28, 1)),
keras.layers.Conv2D(filters=64, kernel_size=3, strides=(2, 2), activation='relu'),
keras.layers.MaxPooling2D(pool_size=2),
keras.layers.Flatten(),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Epoch 00011: early stopping
313/313 - 1s - loss: 0.2710 - accuracy: 0.9044
테스트 정확도: 0.9043999910354614

Epoch 00011: early stopping
313/313 - 1s - loss: 0.2746 - accuracy: 0.9055
테스트 정확도: 0.9054999947547913

Epoch 00008: early stopping
313/313 - 1s - loss: 0.2923 - accuracy: 0.8992
테스트 정확도: 0.8992000222206116

Epoch 00010: early stopping
313/313 - 1s - loss: 0.2896 - accuracy: 0.8998
테스트 정확도: 0.8998000025749207

Epoch 00012: early stopping
313/313 - 1s - loss: 0.2843 - accuracy: 0.9051
테스트 정확도: 0.9050999879837036

굉장히 애매한 결과가 나왔다. 정확도의 maximum이 모델 복잡도를 올렸을때보다 조금 높아졌지만 minimum도 그만큼 살짝 낮아졌다. 혹시나 해서 최종 레이어 이전에 20%의 비율로 dropout을 추가해주고 다시 학습시켜봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Epoch 00013: early stopping
313/313 - 1s - loss: 0.2861 - accuracy: 0.9081
테스트 정확도: 0.9081000089645386

Epoch 00013: early stopping
313/313 - 1s - loss: 0.2754 - accuracy: 0.9056
테스트 정확도: 0.9056000113487244

Epoch 00013: early stopping
313/313 - 1s - loss: 0.2760 - accuracy: 0.9051
테스트 정확도: 0.9050999879837036

Epoch 00011: early stopping
313/313 - 1s - loss: 0.2751 - accuracy: 0.9064
테스트 정확도: 0.9064000248908997

Epoch 00013: early stopping
313/313 - 1s - loss: 0.2877 - accuracy: 0.9019
테스트 정확도: 0.9018999934196472

오오오 확실히 변화가 있었다. 전반적으로 정확도가 90.5% 이상으로 상승했다. 결국 Dense 레이어를 추가해서 단순히 모델 복잡도를 올려버리는 것 보다는 dropout을 추가해서 복잡도를 낮추는게 더 효과가 있었다.

여기에서 궁금해서 몇가지 더 실험을 해봤다.

  1. 생각보다 loss가 많이 낮아졌기에 손실이 높아지는걸 감수하고 학습을 좀더 진행시켜보면 정확도가 올라갈지 궁금했다. 조기종료 조건의 patience를 5로 높여봤다.

    1
    2
    3
    4
    5
    6
    7
    Epoch 00016: early stopping
    313/313 - 1s - loss: 0.3029 - accuracy: 0.9076
    테스트 정확도: 0.9075999855995178

    Epoch 00013: early stopping
    313/313 - 1s - loss: 0.2799 - accuracy: 0.9045
    테스트 정확도: 0.9045000076293945

    손실이 증가한거에 비해 정확도의 증가는 미미한 편이다.

  2. dropout 비율이 학습에 미치는 영향이 궁금했다. dropout 비율을 높여봤다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # Dropout : 0.5
    Epoch 00012: early stopping
    313/313 - 1s - loss: 0.2802 - accuracy: 0.9046
    테스트 정확도: 0.9046000242233276

    Epoch 00016: early stopping
    313/313 - 1s - loss: 0.2810 - accuracy: 0.9080
    테스트 정확도: 0.9079999923706055

    # Dropout : 0.25
    Epoch 00010: early stopping
    313/313 - 3s - loss: 0.2662 - accuracy: 0.9120
    테스트 정확도: 0.9120000004768372

    Epoch 00011: early stopping
    313/313 - 3s - loss: 0.2499 - accuracy: 0.9139
    테스트 정확도: 0.9139000177383423

    # Dropout : 0.3
    Epoch 00012: early stopping
    313/313 - 3s - loss: 0.2442 - accuracy: 0.9173
    테스트 정확도: 0.9172999858856201

    Epoch 00009: early stopping
    313/313 - 3s - loss: 0.2510 - accuracy: 0.9128
    테스트 정확도: 0.9128000140190125

    대박!!! 드롭아웃 비율을 조절한 것 만으로도 엄청난 변화가 생겼다. 드디어 0.5는 썩 성능이 좋지 않았고 0.3정도가 적당한 듯 싶다. 신기하네.


최종모델

내가 구성한 모델 중 가장 높은 성능을 보여준놈

1
2
3
4
5
6
7
8
9
10
model = keras.Sequential([
keras.layers.Conv2D(filters=32, kernel_size=3, activation='relu', input_shape=(28, 28, 1)),
keras.layers.MaxPooling2D(pool_size=2),
keras.layers.Conv2D(filters=64, kernel_size=3, activation='relu'),
keras.layers.MaxPooling2D(pool_size=2),
keras.layers.Flatten(),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.3),
keras.layers.Dense(10, activation='softmax')
])

추가 (+) 어디 책에서 본 모델

1
2
3
4
5
6
7
8
model = keras.Sequential([
keras.layers.Conv2D(10, (3, 3), padding='same', activation='relu', input_shape=(28, 28, 1)),
keras.layers.MaxPooling2D(pool_size=2),
keras.layers.Flatten(),
keras.layers.Dropout(0.5),
keras.layers.Dense(100, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Epoch 00021: early stopping
313/313 - 2s - loss: 0.2330 - accuracy: 0.9183
테스트 정확도: 0.9182999730110168

Epoch 00017: early stopping
313/313 - 2s - loss: 0.2335 - accuracy: 0.9160
테스트 정확도: 0.9160000085830688

Epoch 00013: early stopping
313/313 - 2s - loss: 0.2499 - accuracy: 0.9131
테스트 정확도: 0.913100004196167

Epoch 00014: early stopping
313/313 - 2s - loss: 0.2392 - accuracy: 0.9165
테스트 정확도: 0.9164999723434448

간단하게 생겼는데 성능이 꽤 좋다. 이유가 뭘까?

댓글