갈루아의 반서재

코세라 강의 Introduction to TensorFlow for Artificial Intelligence, Machine Learning, and Deep Learning > 2주차 > Week 2 Resources > Beyond Hello World, A Computer Vision Example 에 나오는 내용을 가지고 GCP 환경에서 주피터랩을 통해 실습을 진행해보았습니다.

먼저 텐서플로우를 임포트한다.

import tensorflow as tf
print(tf.__version__)

2.1.0

Fashion MNIST 데이터는 tf.keras 데이터셋 API 를 통해 바로 사용가능하다. 다음과 같이 로딩할 수 있다.

mnist = tf.keras.datasets.fashion_mnist

load_data 를 호출하면 그래픽에 대한 훈련값 및 테스트값으로, 의류 아이템 이미지와 라벨로 구성된다.

(training_images, training_labels), (test_images, test_labels) = mnist.load_data()

Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz 32768/29515 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz 26427392/26421880 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz 8192/5148 [===============================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz 4423680/4422102 [==============================] - 0s 0us/step

훈련 이미지와 라벨을 출력해서 이 값들이 어떤 형태를 띄는지 확인해보자.

# conda install -c conda-forge matplotlib
import numpy as np
np.set_printoptions(linewidth=200)
import matplotlib.pyplot as plt 
plt.imshow(training_images[0])
print(training_labels[0])
print(training_images[0])

리스트의 모든 값이 0 에서 255 사이의 값임을 알 수 있다. 정규화(normalizing)라는 과정을 통해 모든 값을 0 과 1 사이로 만들면 한결 다루기 쉬워진다. 다음과 같이 각 값을 255로 나누면 된다.

training_images  = training_images / 255.0
test_images = test_images / 255.0

이제 궁금해지는 것은 왜 훈련과 검증이라는 2개의 셋트가 필요한 것이냐는 점이다. 훈련셋에서는 만나보지 못한 검증셋의 데이터를 통해 모델의 정확성을 검증할 수 있기 때문이다.

그러면 이제 모델을 디자인해보자.

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(), 
                                    tf.keras.layers.Dense(128, activation=tf.nn.relu), 
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

Sequential: 신경망의 레이어의 시퀀스 정의

Flatten: 앞서 출력해봤듯이 이미지는 사각형이었다. Flatten 은 그 사각형의 이미지를 1차원 세트로 변환해준다.

Dense: 뉴런 레이어 추가

각각의 뉴런 레이어는 활성화함수(activation function)를 필요로 한다. 여러가지 옵션 선택이 가능하지만 여기서는 다음의 것들을 사용하기로 한다.

Relu 는 간단히 말해 "X>0 이면 X 를 반환하고, 그렇지 않으면 0 을 반환한다"는 의미이다. 즉, 네트워크의 다음 레이어에 X의 값이 0 이상인 경우에만 X  값을 전달한다. 

Softmax 는 일련의 값들을 취해서 그 중 가장 큰 것을 선택한다. 예를 들어 마지막 레이어 출력이 [0.1, 0.1, 0.05, 0.1, 9.5, 0.1, 0.05, 0.05, 0.05] 와 같다면 , 가장 큰 값을 찾아 [0,0,0,0,1,0,0,0,0] 로 변환해준다. 이 함수의 목적은 코딩의 수고를 덜어주는데 있다.

다음으로 해야할 것은, 모델을 빌드하는 것이다. 옵티마이저(optimizer)와 손실함수(loss function)를 통해 컴파일이 가능하다. 그리고 model.fit 을 통해 훈련셋의 데이터를 라벨에 맞도록 훈련을 진행한다. 훈련데이터와 실제의 라벨 사이의 관계를 알아내는 작업이다. 그래서 추후에는 어떤 데이터라도 그 라벨을 예측할 수 있게 되는 것이다.

model.compile(optimizer = tf.optimizers.Adam(),
              loss = 'sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(training_images, training_labels, epochs=5)

Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 10s 163us/sample - loss: 0.4978 - accuracy: 0.8249
Epoch 2/5
60000/60000 [==============================] - 8s 137us/sample - loss: 0.3750 - accuracy: 0.8645
Epoch 3/5
60000/60000 [==============================] - 8s 136us/sample - loss: 0.3382 - accuracy: 0.8765
Epoch 4/5
60000/60000 [==============================] - 8s 128us/sample - loss: 0.3143 - accuracy: 0.8850
Epoch 5/5
60000/60000 [==============================] - 7s 118us/sample - loss: 0.2944 - accuracy: 0.8911
<tensorflow.python.keras.callbacks.History at 0x7f787bcd5210>

훈련이 끝나면 각 에포크 단계마다 정확도 (accuracy value)를 볼 수 있다. 최종 단계의 경우 0.8898 정도 된다. 이것이 의미하는 것은 훈련셋의 데이터를 분류하는데 약 89%의 정확도를 보인다는 것이다. 이미지와 라벨 간 매칭에 있어 89% 의 정확도를 보였다는 뜻이다. 그렇게 높지는 않지만 5번의 에포크를 진행한 것에 비하면 나쁘지 않은 수치다. 그러면 아직 보지못한 데이터에는 어떤 결과를 보일까? 이것이 검증 이미지를 별도로 가져야하는 이유이다. model.evaluate 를 호출해서 2개의 셋에 전달하면 에포크의 손실을 보여줄 것이다.

model.evaluate(test_images, test_labels)

10000/10000 [==============================] - 1s 92us/sample - loss: 0.3878 - accuracy: 0.8648

[11]:

[0.38781969833374025, 0.8648]

약 .8648 의 정확도를 보였다. 약 86% 정확하다는 의미이다. 훈련데이터셋에 정확도보다는 약간 떨어지는 수치이다. 하지만 나머지 과정을 통해 조금 더 올라가는 것을 볼 수 있을 것이다. 

 

탐구문제

연습1:

아래 코드를 실행하면 각각의 검증 이미지에 대한 분류셋을 생성한다. 결과는 숫자의 리스트이다.

classifications = model.predict(test_images)

print(classifications[0])

[1.1417432e-06 1.5584668e-07 6.3831531e-09 5.5041758e-09 1.8500375e-08 2.0826815e-03 2.3167050e-07 1.1368759e-02 3.1246655e-06 9.8654395e-01]

print(test_labels[0]) 을 실행하면, 9 라는 결과를 얻을 것이다.

print(test_labels[0])

9

그러면 결과값 9 는 무엇을 의미할까? 모델의 결과는 10개의 숫자로 이루어진 리스트로, 이 숫자들은 각각의 값에 대응하는 확률값이다. 리스트의 첫 번째 값은 라벨 '0'에 속할 확률을 말한다. 그 다음 값은 라벨 '1'에 속할 확률이다. 라벨 9에서 0.986 이상의 확률을 보이는데, 이 말은 해당 이미지가 거의 라벨 9에 속한다는 말이다. 앵클 부츠가 9번으로 라벨링되어 있으므로, 해당 이미지는 앵클 부츠일 확률이 높다. 

연습 2:

모델의 레이어를 살펴보자. 그럼 이제 dense layer 값을 1024 개로 지정하여 테스트해보자. 앞선 테스트와 비교하여 어떤 차이가 있는가?

import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) ,  (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(1024, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
              loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=5)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[0])
print(test_labels[0])

2.1.0
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 15s 245us/sample - loss: 0.1827
Epoch 2/5
60000/60000 [==============================] - 14s 239us/sample - loss: 0.0739
Epoch 3/5
60000/60000 [==============================] - 14s 241us/sample - loss: 0.0492
Epoch 4/5
60000/60000 [==============================] - 15s 244us/sample - loss: 0.0331
Epoch 5/5
60000/60000 [==============================] - 15s 242us/sample - loss: 0.0269
10000/10000 [==============================] - 1s 145us/sample - loss: 0.0742
[8.2967428e-09 2.7729309e-08 7.2180931e-08 2.3687835e-05 1.8373934e-13 1.5103123e-09 1.1269659e-13 9.9997520e-01 9.5837382e-10 9.1968752e-07]
7

뉴런의 갯수를 1024개로 늘린 결과 계산에 좀 더 많은 시간이 소요되었고, 여기서는 정확도가 소폭 향상된 것을 볼 수 있다. 하지만 항상 정확도가 상승하는 것은 아니라는 점에 유의하자.

연습 3:

만약 Flatten() 레이어를 제거하면 어떻게 될까? 결과에서 보듯이 데이터 형태에 관해 에러가 발생하게 된다. 여기서 다루는 데이터는 28x28 이미지이다. 즉, 28개 뉴런으로 구성된 28개의 레이어 형태를 피할 수 없다. 즉 28x28 을 784x1 로 'flatten' 해야하는 것이다. Flatten() 을 레이어 초기에 도입함으로써, 어레이가 모델에 로딩될때 자동으로 flatten하게 된다.

import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) ,  (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([#tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(64, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
              loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=5)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[0])
print(test_labels[0])

2.1.0
Train on 60000 samples
Epoch 1/5
   32/60000 [..............................] - ETA: 38s

ValueError: Shape mismatch: The shape of labels (received (32, 1)) should equal the shape of logits except for the last dimension (received (32, 28, 10)).

연습 4:

최종 레이어를 10이 아닌 5로 하면 어떻게 되는가? 최종 레이어의 뉴런의 갯수는 분류하고자하는 클래스의 수와 일치해야 한다. 이 케이스에서는 0에서 9까지 총 10개의 뉴런을 가지고 있어야 한다.

import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) ,  (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(64, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(5, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
              loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=5)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[0])
print(test_labels[0])

2.1.0
Train on 60000 samples
Epoch 1/5
   32/60000 [..............................] - ETA: 20:17

 

연습 5:

네트워크에 추가 레이어 구성시 효과를 살펴보자. 512 와 10 개의 레이어 사이에 256 개의 뉴런으로 구성된 또 다른 레이어를 추가해보자. 실행해보면 유의미한 결과가 있다고는 볼 수 없다. 왜냐하면 본 예제에서 사용하는 데이터 자체가 심플한 것이기 때문이다. 좀 더 복잡한 데이터의경우 추가 레이어는 종종 필수적이다.

import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) ,  (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(512, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(256, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
              loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=5)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[0])
print(test_labels[0])

2.1.0
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 15s 256us/sample - loss: 0.1876
Epoch 2/5
60000/60000 [==============================] - 15s 252us/sample - loss: 0.0798
Epoch 3/5
60000/60000 [==============================] - 16s 260us/sample - loss: 0.0550
Epoch 4/5
60000/60000 [==============================] - 15s 250us/sample - loss: 0.0400
Epoch 5/5
60000/60000 [==============================] - 15s 257us/sample - loss: 0.0323
10000/10000 [==============================] - 2s 159us/sample - loss: 0.0876
[3.4637861e-12 8.5085994e-10 1.2124245e-09 1.8324899e-07 7.4745148e-11 1.1681352e-11 2.3622872e-15 9.9999964e-01 5.2372538e-12 1.4574992e-07]
7

연습 6:

에포크를 증가 또는 축소하는 것이 효과를 보자. 이번에는 에포크 수를 기존 5에서 30로 늘려보자. 결과를 보면 손실값 감소가 멈추고 오히려 증가하는 것을 볼 수 있다. 이것이 과적합(overfitting)이라고 불리는 부작용이다. 

import tensorflow as tf
print(tf.__version__)

mnist = tf.keras.datasets.mnist

(training_images, training_labels) ,  (test_images, test_labels) = mnist.load_data()

training_images = training_images/255.0
test_images = test_images/255.0

model = tf.keras.models.Sequential([tf.keras.layers.Flatten(),
                                    tf.keras.layers.Dense(128, activation=tf.nn.relu),
                                    tf.keras.layers.Dense(10, activation=tf.nn.softmax)])

model.compile(optimizer = 'adam',
              loss = 'sparse_categorical_crossentropy')

model.fit(training_images, training_labels, epochs=30)

model.evaluate(test_images, test_labels)

classifications = model.predict(test_images)

print(classifications[34])
print(test_labels[34])

2.1.0
Train on 60000 samples
Epoch 1/30
60000/60000 [==============================] - 6s 102us/sample - loss: 0.2636
Epoch 2/30
60000/60000 [==============================] - 6s 98us/sample - loss: 0.1165
Epoch 3/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0815
Epoch 4/30
60000/60000 [==============================] - 6s 98us/sample - loss: 0.0608
Epoch 5/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0460
Epoch 6/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0380
Epoch 7/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0300
Epoch 8/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0239
Epoch 9/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0194
Epoch 10/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0173
Epoch 11/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0143
Epoch 12/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0114
Epoch 13/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0110
Epoch 14/30
60000/60000 [==============================] - 6s 98us/sample - loss: 0.0080
Epoch 15/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0081
Epoch 16/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0072
Epoch 17/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0070
Epoch 18/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0063
Epoch 19/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0060
Epoch 20/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0057
Epoch 21/30
60000/60000 [==============================] - 6s 94us/sample - loss: 0.0047
Epoch 22/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0061
Epoch 23/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0037
Epoch 24/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0044
Epoch 25/30
60000/60000 [==============================] - 6s 96us/sample - loss: 0.0049
Epoch 26/30
60000/60000 [==============================] - 6s 95us/sample - loss: 0.0036
Epoch 27/30
60000/60000 [==============================] - 6s 95us/sample - loss: 0.0046
Epoch 28/30
60000/60000 [==============================] - 6s 98us/sample - loss: 0.0022
Epoch 29/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0050
Epoch 30/30
60000/60000 [==============================] - 6s 97us/sample - loss: 0.0026
10000/10000 [==============================] - 1s 78us/sample - loss: 0.1131
[1.12313655e-26 1.33498745e-14 2.93009323e-13 2.55683197e-10 1.25783130e-23 1.32930802e-34 2.21634935e-28 1.00000000e+00 7.72813750e-16 6.03968119e-15]
7

연습 7:

훈련 전에 정규화를 통해 데이터 값을 0~255 에서 0~1 로 변환했다. 만약 이 부분을 제거하면 어떻게 될까? 위의 결과는 데이터값을 0~1로 변환한 경우이고, 하단은 이 부분을 제거하고 실행한 결과이다. 상단의 결과에 비하면 형편없는 수준이다. 

import tensorflow as tf
print(tf.__version__)
mnist = tf.keras.datasets.mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images/255.0
test_images=test_images/255.0
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(training_images, training_labels, epochs=5)
model.evaluate(test_images, test_labels)
classifications = model.predict(test_images)
print(classifications[0])
print(test_labels[0])

2.1.0
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 10s 160us/sample - loss: 0.1979
Epoch 2/5
60000/60000 [==============================] - 9s 153us/sample - loss: 0.0814
Epoch 3/5
60000/60000 [==============================] - 9s 153us/sample - loss: 0.0522
Epoch 4/5
60000/60000 [==============================] - 9s 152us/sample - loss: 0.0367
Epoch 5/5
60000/60000 [==============================] - 9s 152us/sample - loss: 0.0267
10000/10000 [==============================] - 1s 101us/sample - loss: 0.0765
[6.3324873e-10 1.1150239e-08 1.8669322e-08 8.7870103e-06 5.4621316e-14 1.2201207e-09 7.1466570e-12 9.9999118e-01 8.9842939e-10 2.9291808e-08]
7

import tensorflow as tf
print(tf.__version__)
mnist = tf.keras.datasets.mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(training_images, training_labels, epochs=5)
model.evaluate(test_images, test_labels)
classifications = model.predict(test_images)
print(classifications[0])
print(test_labels[0])

2.1.0
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 13s 217us/sample - loss: 2.6095
Epoch 2/5
60000/60000 [==============================] - 12s 204us/sample - loss: 0.3304
Epoch 3/5
60000/60000 [==============================] - 12s 202us/sample - loss: 0.2974
Epoch 4/5
60000/60000 [==============================] - 12s 205us/sample - loss: 0.2708
Epoch 5/5
60000/60000 [==============================] - 13s 209us/sample - loss: 0.2421
10000/10000 [==============================] - 1s 122us/sample - loss: 0.2650
[2.0504893e-23 2.7382829e-24 4.1192511e-13 2.8883518e-19 3.3185515e-29 4.9011589e-19 0.0000000e+00 1.0000000e+00 1.1396611e-20 2.2584944e-13]
7

연습 8:

목표한 정확도 또는 손실률에 도달했을 경우에는 굳이 추가적인 에포크가 필요하지는 않다. 다음과 같이 목표점에도달시 콜백을 이용하여 트레이닝을 중단시킬 수 있다.

import tensorflow as tf
print(tf.__version__)

class myCallback(tf.keras.callbacks.Callback):
  def on_epoch_end(self, epoch, logs={}):
    if(logs.get('loss')<0.4):
      print("\nReached 60% accuracy so cancelling training!")
      self.model.stop_training = True

callbacks = myCallback()
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images/255.0
test_images=test_images/255.0
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
model.fit(training_images, training_labels, epochs=5, callbacks=[callbacks])

2.1.0
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 9s 157us/sample - loss: 0.4769
Epoch 2/5
59968/60000 [============================>.] - ETA: 0s - loss: 0.3597
Reached 60% accuracy so cancelling training!
60000/60000 [==============================] - 10s 158us/sample - loss: 0.3598
<tensorflow.python.keras.callbacks.History at 0x7fa2883c5f10>