Deep Learning

tesorflow에서 convnet 전이학습 시키기

마크투비 2021. 5. 31. 22:42

출처: https://www.tensorflow.org/tutorials/images/transfer_learning?hl=ko 

 

이 튜토리얼에서는 사전 훈련된 네트워크에서 전이 학습을 사용하여 고양이와 개의 이미지를 분류할 것이다. 사전 훈련된 모델은 이전에 대규모 데이터셋에서 훈련되어 저장된 네트워크로, 일반적으로 대규모 이미지 분류 작업에서 훈련된 것이다. 사전 훈련된 모델을 그대로 사용하거나 전이 학습을 사용하여 이 모델을 주어진 작업으로 사용자 정의할 수 있다. 이미지 분류를 위한 전이 학습을 직관적인 시각에서 바라보면 모델이 충분히 크고 일반적인 데이터 집합에서 훈련된다면, 이 모델은 사실상 시각 세계의 일반적인 모델로서 기능할 것이다. 그런 다음 대규모 데이터셋에서 대규모 모델을 training 하여 처음부터 시작할 필요 없이 학습된 특징 맵을 활용할 수 있다. 여기서는 사전 훈련된 모델을 사용자 정의하는 두 가지 방법을 시도 해볼 것이다.

  1. 특징 추출: 새 샘플에서 의미 있는 shape을 추출하기 위해 이전 네트워크에서 학습한 표현을 사용한다. 사전 훈련된 모델 위에 처음부터 교육할 새 분류기를 추가하기만 하면 이전에 데이터셋으로 학습한 특징 맵의 용도를 재사용할 수 있다. 
  2. 미세 조정: 고정된 기본 모델의 일부 최상위 층을 고정 해제하고 새로 추가 된 분류기 층과 기본 모델의 마지막 층을 함께 훈련시킨다. 이를 통해 기본 모델에서 고차원 특징 표현을 "미세 조정"하여 특정 작업에 보다 관련성이 있도록 할 수 있다.

일반적인 기계 학습의 일련의 과정을 진행한다.

  1. 데이터 검사 및 이해
  2. 입력 파이프 라인 빌드(이 경우 Keras ImageDataGenerator를 사용)
  3. 모델 작성(사전 훈련된 기본 모델(또한 사전 훈련된 가중치)에 적재, 분류 층을 맨 위에 쌓기)
  4. 모델 훈련
  5. 모델 평가
from __future__ import absolute_import, division, print_function, unicode_literals

import os

import numpy as np

import matplotlib.pyplot as plt
try:
  # %tensorflow_version은 Colab에서만 지원됩니다.
  %tensorflow_version 2.x
except Exception:
  pass
import tensorflow as tf

keras = tf.keras

데이터 전처리

사전 훈련된 컨볼루션 네트워크로부터 기본 모델 생성하기

Google에서 개발한 MobileNet V2 모델로부터 기본 모델을 생성한다. 이 모델은 1.4M 이미지와 1000개의 클래스로 구성된 대규모 데이터셋인 ImageNet 데이터셋를 사용해 사전 훈련된 모델이다. ImageNet은 잭프루트  주사기와 같은 다양한 범주의 연구용 훈련 데이터셋이다.

먼저 기능 추출에 사용할 MobileNet V2 층을 선택 해야 한다. 가장 최근의 분류 층 ("맨 위층", 대부분의 머신 러닝 모델 다이어그램은 아래에서 위로 이동하므로)은 유용하지 않다. 대신에 flatten 연산을 하기 전에 맨 아래 층을 가지고 진행한다. 이 층을 "병목 층"이라고한다. 병목 층은 맨 위층보다 일반성을 유지한다.

먼저 ImageNet으로 훈련된 가중치가 저장된 MobileNet V2 모델을 인스턴스화 할 것이다. ** include_top = False ** 로 지정하면 맨 위에 분류 층이 포함되지 않은 네트워크를 로드하므로 특징 추출에 이상적이다.

IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)

# 사전 훈련된 모델 MobileNet V2에서 기본 모델을 생성합니다.
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
                                               include_top=False,
                                               weights='imagenet')

이 특징 추출기는 각 160x160x3 이미지를 5x5x1280 개의 특징 블록으로 변환한다. 

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

# 실행결과
# (32, 5, 5, 1280)

특징 추출

이 단계에서는 이전 단계에서 작성된 컨벌루션 베이스 모델을 동결하고 특징 추출기로 사용한다. 또한 그 위에 분류기를 추가하고 최상위 분류기를 훈련시킨다.

컨볼루션 베이스 모델 고정하기

모델을 컴파일하고 훈련시키기 전에 컨볼루션 베이스 모델을 고정 시키는 것이 중요하다. 고정(layer.trainable = False를 설정하여)하면 훈련 중 지정된 층의 가중치가 업데이트되지 않는다. MobileNet V2에는 많은 층이 있으므로 전체 모델의 훈련 가능한 플래그를 False로 설정하면 모든 층이 고정됩니다.

base_model.trainable = False

# 기본 모델 아키텍처를 살펴봅니다.
base_model.summary()

... 총 parameter가 2257984개나 있다.

분류 층을 맨 위에 추가하기

특징 블록에서 예측을 하기위해 tf.keras.layers.GlobalAveragePooling2D 층을 사용하여 특징을 이미지 한개 당 1280개의 요소 벡터로 변환하여 5x5 공간 위치에 대한 평균을 구하면 다음과 같다.

global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
feature_batch_average = global_average_layer(feature_batch)
print(feature_batch_average.shape)

# (32, 1280)

tf.keras.layers.Dense층을 사용하여 특징을 이미지당 단일 예측으로 변환한다. 이 예측은 logit또는 원시 예측 값으로 취급되므로 활성화 함수가 필요하지 않다. 양수는 클래스 1을 예측하고 음수는 클래스 0을 예측한다.

prediction_layer = keras.layers.Dense(1)
prediction_batch = prediction_layer(feature_batch_average)
print(prediction_batch.shape) # (32, 1)

이제 tf.keras.Sequential모델을 사용하여 특징 추출기와 이 두 층을 쌓으면 다음과 같다.

model = tf.keras.Sequential([
  base_model,
  global_average_layer,
  prediction_layer
])

모델 컴파일

학습하기 전에 모델을 컴파일해야 한다. 두 개의 클래스가 있으므로 모델이 선형 출력을 제공한다. from_logits = True와 함께 이진 교차 엔트로피 손실을 사용한다.

base_learning_rate = 0.0001
model.compile(optimizer=tf.keras.optimizers.RMSprop(learning_rate=base_learning_rate),
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])
model.summary()

 

len(model.trainable_variables)
# 2

모델 훈련

initial_epochs = 10
validation_steps=20

loss0,accuracy0 = model.evaluate(validation_batches, steps = validation_steps)

# 20/20 [==============================] - 4s 73ms/step - loss: 0.9048 - accuracy: 0.4109
print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))

# initial loss: 0.90
# initial accuracy: 0.41
history = model.fit(train_batches,
                    epochs=initial_epochs,
                    validation_data=validation_batches)

학습 곡선

MobileNet V2 기본 모델을 고정된 특징 추출기로 사용했을 때의 학습 및 검증 정확도 / 손실의 학습 곡선을 살펴 보겠다.

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

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

plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')

plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,1.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()

Note: 유효성 검사 지표가 훈련 지표보다 명확하게 더 나은 이유는 tf.keras.layers.BatchNormalization  tf.keras.layers.Dropout과 같은 층이 훈련 중 정확도에 영향을 주기 때문입니다. 이것들은 유효성 검사 손실을 계산할 때 해제됩니다.

훈련 지표가 한 에포크 동안의 평균을 평가하는 반면, 유효성 검사 지표는 에포크 이후에 평가하므로 유효성 검사 지표는 약간 더 많이 훈련 된 모델을 볼 수 있기 때문입니다.

미세 조정

특징 추출 실험에서는 MobileNet V2 기본 모델을 기반으로 몇 개의 층만 학습했다. 사전 훈련된 네트워크의 가중치는 훈련 중에 업데이트 되지 않았다. 성능을 더욱 향상시키는 한 가지 방법은 추가 한 분류기의 훈련과 함께 사전 훈련된 모델의 최상위 레이어 가중치를 훈련(또는 "미세 조정")하는 것이다. 훈련을 통해 가중치는 일반적인 특징 맵에서 개별 데이터셋과 관련된 특징으로 조정된다.

 

주의!

  1. 사전 훈련된 모델을 훈련 불가능으로 설정하여 최상위 분류기를 훈련한 후에만 ​​시도해야 한다. 사전 훈련된 모델 위에 무작위로 초기화된 분류기를 추가하고 모든 레이어를 공동으로 훈련하려고 하면 (분류기가 가중치를 임의 설정하기 때문에) 그래디언트 업데이트의 크기가 너무 커지고 사전 훈련된 모델은 배운 것을 잊어버리게 된다.
  2. 또한 전체 MobileNet 모델이 아닌 소수의 최상위 층을 미세 조정해야 한다. 대부분의 컨볼루션 네트워크에서 층이 높을수록 층이 더 전문화된다. 처음 몇 층은 거의 모든 유형의 이미지로 일반화되는 매우 간단하고 일반적인 특징을 학습한다. 더 높은 수준으로 올라가면 훈련에 사용된 데이터 세트에 맞춰 특징이 점점 더 구체화 된다. 미세 조정의 목표는 이러한 전문화된 특징이 일반적인 학습을 덮어쓰지 않고 새 데이터셋에 맞춰 잘 동작 수 있도록 조정하는 것이다.

최상위 층 고정 해제하기

base_model을 고정 해제하고 맨 아래 층을 훈련 할 수 없도록 설정하면 된다. 그런 다음 모델을 다시 컴파일하고(변경 사항을 적용하기 위해서) 훈련을 다시 시작해야 한다.

base_model.trainable = True

# 기본 모델에 몇 개의 층이 있는지 확인 합니다.
print("Number of layers in the base model: ", len(base_model.layers))

# 해당 층 이후부터 미세 조정
fine_tune_at = 100

# `fine_tune_at` 층 이전의 모든 층을 고정
for layer in base_model.layers[:fine_tune_at]:
  layer.trainable =  False

# 실행 결과
# Number of layers in the base model:  154

모델 컴파일

훨씬 더 낮은 학습 비율로 모델 컴파일한다.

model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer = tf.keras.optimizers.RMSprop(learning_rate=base_learning_rate/10),
              metrics=['accuracy'])
model.summary()

len(model.trainable_variables) # 56

모델 훈련 계속하기

이미 수렴 상태로 훈련된 경우에, 이 단계는 정확도를 몇 퍼센트 포인트 향상시킨다.

fine_tune_epochs = 10
total_epochs =  initial_epochs + fine_tune_epochs

history_fine = model.fit(train_batches,
                         epochs=total_epochs,
                         initial_epoch =  history.epoch[-1],
                         validation_data=validation_batches)

 

MobileNet V2 기본 모델의 마지막 몇 층을 미세 조정하고 그 위의 분류기를 훈련할 때의 학습 및 검증 정확도 / 손실의

학습 곡선을 살펴 보겠다. 검증 손실은 훈련 손실보다 훨씬 높으므로 약간의 과적합이 나올 수 있다. 새로운 훈련용 데이터셋이 상대적으로 작고 원래 MobileNet V2의 데이터셋과 유사하기 때문에 약간의 과적합이 발생할 수 있다. 미세 조정 후 모델은 거의 98% 정확도에 도달한다.

acc += history_fine.history['accuracy']
val_acc += history_fine.history['val_accuracy']

loss += history_fine.history['loss']
val_loss += history_fine.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.ylim([0.8, 1])
plt.plot([initial_epochs-1,initial_epochs-1],
          plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.ylim([0, 1.0])
plt.plot([initial_epochs-1,initial_epochs-1],
         plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()

요약:

  • 특징 추출을 위해 사전 훈련된 모델 사용하기: 작은 데이터셋으로 작업 할 때는 동일한 범주의 클래스의 더 큰 데이터셋으로 훈련시킨 사전 학습된 모델의 특징을 활용하는 것이 일반적입니다. 사전 훈련된 모델을 인스턴스화하고 완전히 연결된 분류기를 맨 위에 추가하면 됩니다. 사전 훈련된 모델은 "고정"되고 분류기의 가중치만 훈련 중에 업데이트됩니다. 이 경우 컨벌루션 베이스 모델은 각 이미지와 관련된 모든 특징을 추출하며 주어진 추출된 특징을 가지고 이미지 클래스를 결정하는 분류기를 훈련합니다.
  • 사전 훈련된 모델을 미세 조정하기: 성능을 더욱 향상시키기 위해 사전 훈련된 모델의 최상위 계층을 미세 조정을 통해 새 데이터셋으로써 재사용 할 수 있습니다. 이 경우 모델이 주어진 데이터셋에 맞는 상위 레벨의 특징을 학습 할 수 있도록 가중치를 조정합니다. 이 기술은 일반적으로 훈련 데이터셋의 규모가 크고, 사전 훈련된 모델이 사용했던 원래 데이터셋과 매우 유사한 경우에 권장됩니다.