[튜토리얼4] 텐서플로우 허브를 이용한 전이 학습

텐서플로우 허브는 이미 학습된 모델의 구성 요소를 공유하는 방법입니다. 이번 튜토리얼을 통해서 다음을 알아보겠습니다.

  1. tf.keras로 텐서플로우 허브를 사용하는 방법
  2. 텐서플로우 허브로 이미지 분류를 하는 방법
  3. 간단한 전이 학습 방법
import warnings
warnings.simplefilter('ignore')

import matplotlib.pylab as plt
import tensorflow as tf

import tensorflow_hub as hub

from tensorflow.keras import layers

ImageNet 분류기(classifier)

분류기 다운받기

hub.module을 사용하여 모바일넷(mobilenet)을 불러오고 tf.keras.layers.Lambda를 사용하여 이를 케라스 레이어로 감싸줍니다. tfhub.dev의 TensorFlow 2와 호환가능한 모든 이미지 분류기 URL은 여기에서 확인할 수 있습니다.

classifier_url ="https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/2" #@param {type:"string"}
IMAGE_SHAPE = (224, 224)

classifier = tf.keras.Sequential([
    hub.KerasLayer(classifier_url, input_shape=IMAGE_SHAPE+(3,))
])

단일 이미지로 실행하기

이미지를 하나 다운받아 모델을 실행해봅시다.

import numpy as np
import PIL.Image as Image

grace_hopper = tf.keras.utils.get_file('image.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg')
grace_hopper = Image.open(grace_hopper).resize(IMAGE_SHAPE)
grace_hopper
grace_hopper = np.array(grace_hopper)/255.0
grace_hopper.shape

배치의 차원 수(batch dimension)를 넣고 이미지를 모델에 전달합니다.

result = classifier.predict(grace_hopper[np.newaxis, ...])
result.shape

결과 값으로는 이미지에 대해 각 클래스의 확률을 계산한 1001개로 구성된 로짓(logit)의 요소 벡터(element vector)가 도출되고 이미지에 대한 각 클래스의 확률을 계산합니다.

최상위 클래스의 ID는 argmax로 확인할 수 있습니다

predicted_class = np.argmax(result[0], axis=-1)
predicted_class

예측값 디코딩하기

우리는 이제 예측된 클래스 ID를 가지고 있습니다. ImageNet의 레이블을 가져와 예측 값을 디코딩해봅시다.

labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
imagenet_labels = np.array(open(labels_path).read().splitlines())
plt.imshow(grace_hopper)
plt.axis('off')
predicted_class_name = imagenet_labels[predicted_class]
_ = plt.title("Prediction: " + predicted_class_name.title())

간단한 전이학습

TF Hub를 사용하면 모델의 최상위 레이어을 재학습하여 데이터셋의 클래스를 쉽게 인식할 수 있습니다.

데이터셋

이번 튜토리얼에서는 텐서플로우의 꽃 데이터셋을 사용할 것입니다:

data_root = tf.keras.utils.get_file(
  'flower_photos','https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
   untar=True)

tf.keras.preprocessing.image.ImageDataGenerator을 사용하면 쉽게 데이터를 모델에 불러올 수 있습니다.

TensorFlow Hub의 모든 이미지 모듈은 [0, 1] 범위 내의 float 형태의 입력 값을 기대합니다. ImageDataGeneratorrescale 매개 변수를 사용하여 이 작업을 수행합니다.

이미지 크기는 나중에 처리할 것입니다.

image_generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1/255)
image_data = image_generator.flow_from_directory(str(data_root), target_size=IMAGE_SHAPE)

빠른 학습을 위해 이미지의 개수를 줄이겠습니다.

image_data.samples = 20
image_data.samples

결과 객체는 image_batch, label_batch 쌍을 반환하는 반복자(iterator)입니다.

for image_batch, label_batch in image_data:
    print("Image batch shape: ", image_batch.shape)
    print("Label batch shape: ", label_batch.shape)
    break

이미지 배치에 분류기 사용하기

이제 이미지 배치에 분류기를 실행해봅시다.

result_batch = classifier.predict(image_batch)
result_batch.shape
predicted_class_names = imagenet_labels[np.argmax(result_batch, axis=-1)]
predicted_class_names

이제 이러한 예측이 이미지와 얼마나 일치하는지 확인해봅시다.

plt.figure(figsize=(10,9))
plt.subplots_adjust(hspace=0.5)
for n in range(30):
    plt.subplot(6,5,n+1)
    plt.imshow(image_batch[n])
    plt.title(predicted_class_names[n])
    plt.axis('off')
_ = plt.suptitle("ImageNet predictions")

결과가 완벽하지는 않지만, 나온 결과들이 모델이 학습한 클래스가 아니라는 점을 고려하면 상당히 괜찮다고 볼 수 있습니다.(“Daisy” 제외).

헤드리스(headless) 모델 다운받기

또한 텐서플로우 허브는 최상위 분류 레이어 없이 모델을 배포합니다. 이는 전이 학습을 쉽게 만들어줍니다.

tfhub.dev의 텐서플로우 2와 호환가능한 이미지 피쳐 벡터 URL은 여기서 확인할 수 있습니다.

feature_extractor_url = "https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/2" #@param {type:"string"}

피쳐 추출기(extractor)를 생성합니다.

feature_extractor_layer = hub.KerasLayer(feature_extractor_url,
                                         input_shape=(224,224,3))

각 이미지에 대해 길이가 1280인 벡터를 반환합니다:

feature_batch = feature_extractor_layer(image_batch)
print(feature_batch.shape)

피쳐 추출기 레이어의 변수를 프리즈(freeze)하여 새로운 분류기 레이어에 한정(modify)하여 학습하도록 합니다.

feature_extractor_layer.trainable = False

분류 헤드(classification head) 부착하기

tf.keras.Sequential에 허브 레이어를 감싸고 새로운 분류 레이어를 추가합니다.

model = tf.keras.Sequential([
  feature_extractor_layer,
  layers.Dense(image_data.num_classes, activation='softmax')
])

model.summary()
predictions = model(image_batch)
predictions.shape

모델 학습시키기

컴파일을 사용하여 교육 프로세스를 구성합니다:

model.compile(
  optimizer=tf.keras.optimizers.Adam(),
  loss='categorical_crossentropy',
  metrics=['acc'])

.fit 메서드를 사용해서 모델을 학습시킵니다.

간단하게 학습하기 위해 에포크(epoch)는 3로 설정합니다. 맞춤형 콜백(callback)을 사용하여 에포크별 평균 대신 각 배치의 손실과 정확도를 기록해서 학습 진행 상황을 시각화합니다.

class CollectBatchStats(tf.keras.callbacks.Callback):
    def __init__(self):
        self.batch_losses = []
        self.batch_acc = []

    def on_train_batch_end(self, batch, logs=None):
        self.batch_losses.append(logs['loss'])
        self.batch_acc.append(logs['acc'])
        self.model.reset_metrics()
steps_per_epoch = np.ceil(image_data.samples/image_data.batch_size)

batch_stats_callback = CollectBatchStats()

history = model.fit_generator(image_data, epochs=2,
                              steps_per_epoch=steps_per_epoch,
                              callbacks = [batch_stats_callback])

이제 학습을 몇 번만 반복해도, 모델이 작업에 진전되고 있다는 것을 알 수 있습니다.

plt.figure()
plt.ylabel("Loss")
plt.xlabel("Training Steps")
plt.ylim([0,2])
plt.plot(batch_stats_callback.batch_losses)
plt.figure()
plt.ylabel("Accuracy")
plt.xlabel("Training Steps")
plt.ylim([0,1])
plt.plot(batch_stats_callback.batch_acc)

예측 결과 확인하기

이전의 그림을 다시 보려면 먼저 정리된 클래스 이름의 리스트를 가져옵니다:

class_names = sorted(image_data.class_indices.items(), key=lambda pair:pair[1])
class_names = np.array([key.title() for key, value in class_names])
class_names

모델을 통해 이미지 배치를 실행하고 인덱스를 클래스 이름으로 변환합니다.

predicted_batch = model.predict(image_batch)
predicted_id = np.argmax(predicted_batch, axis=-1)
predicted_label_batch = class_names[predicted_id]

결과를 봅시다.

label_id = np.argmax(label_batch, axis=-1)
plt.figure(figsize=(10,9))
plt.subplots_adjust(hspace=0.5)
for n in range(30):
    plt.subplot(6,5,n+1)
    plt.imshow(image_batch[n])
    color = "green" if predicted_id[n] == label_id[n] else "red"
    plt.title(predicted_label_batch[n].title(), color=color)
    plt.axis('off')
_ = plt.suptitle("Model predictions (green: correct, red: incorrect)")

모델 내보내기(export)

모델을 학습시켰으면 저장된 모델로 내보냅니다:

import time
t = time.time()

export_path = "/tmp/saved_models/{}".format(int(t))
model.save(export_path, save_format='tf')

export_path

이제 다시 불러올 수 있는지, 또 동일한 결과값을 내는지 확인합니다.

reloaded = tf.keras.models.load_model(export_path)
result_batch = model.predict(image_batch)
reloaded_result_batch = reloaded.predict(image_batch)
abs(reloaded_result_batch - result_batch).max()

저장된 모델은 나중에 인퍼런스(inference)를 하기 위해 나중에 불러오거나 TFLiteTFjs로 변환할 수 있습니다.

Copyright 2018 The TensorFlow Authors.

#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.