[VISION] 비전 시스템을 위한 딥러닝(6) - 전이학습

Study/Vision & Deep Learning · 2023. 5. 30. 18:59

이전 장에서 고급 합성곱 신경망 구조에 대해 알아보았다. (게시물이 날아가서 새로 써야 할 듯... 제일 오래걸렸는데 ㅜㅜ 5시간...)

기억할만한 것들은 축소층, 인셉션 모듈과 잔차 블록 정도였다.

  • VGGNet: 하이퍼파라미터 설정 복잡도를 줄임

층 수를 늘릴수록 신경망의 표현력이 높아지고, 학습 결과가 좋아질 수 있지만 계산 부하가 커지는 단점이 있다.

이러한 특징들을 어떻게 해결하고 개선해갔는지 확인해 볼 수 있는 장이었다.

 

이번 장에서는 전이학습에 대해 알아보겠다.

전이학습은 딥러닝에서 가장 중요한 기법 중에 하나다.

전이학습은 다른 연구진들이 만들어 놓은 오픈 소스 모델을 토대로 우리가 해결할 문제와 관련된 상대적으로 데이터셋으로 학습시키는 것이다.

6.1 전이학습으로 해결할 수 있는 문제

전이학습을 사용하는 실질적인 이유는 대부분의 실제 문제에서 문제를 해결할 만큼 복잡한 모델을 학습할만한 수백만 개의 레이블링 된 데이터를 얻기 어렵기 때문이다.

개념은 어렵지 않다. 대규모 데이터를 대상으로 신경망을 학습하고, 추출된 유용한 특징을 특징 맵 형태로 새로운 신경망으로 옮기는 것이다.

이를 통해 최고 수준의 성능을 가진 모델을 내려받아 바로 활용할 수도 있고, 다른 문제를 위한 새로운 모델에 포함시켜 활용할 수 있다.

먼저 전이학습의 원리와 다양한 접근 방식을 설명하겠다.

딥러닝이 제 성능을 내기 위해선 놀랄 만큼 많은 양의 레이블링된 데이터가 필요하다. 그래서 실질적으로 데이터 부족과 과다한 계산 요구량과 같은 문제 때문에 합성곱 신경망을 완전히 처음부터 학습시키는 경우는 드물다.

전이학습의 장점 중 하나는 일반화 성능을 확보하고 과적합을 방지하는 효과다.

기존 데이터로 훈련된 모델을 가져와 새로운 데이터로 훈련시킴으로써 과적합을 낮춰 일반화 성능을 높일 수 있다.

6.2 전이학습이란

전이학습의 정의는 다음과 같다.

신경망이 어떤 과업을 위해 많은 양의 데이터를 이용해 학습한 지식(특정 맵)을 학습 데이터가 상대적으로 적은 다른 유사한 과업으로 옮겨오는 것을 말한다.

하지만, 무조건 가져다 쓸 때만 전이학습을 쓰는 것은 아니다.

대규모 학습 데이터와 계산 클러스터를 따로 준비했더라도 미세 조정을 거친 사전 학습된 신경망을 사용해서 전이학습을 활용할 수 있다.

예제를 통해 알아보자

먼저 우리가 해결해야하는 문제와 비슷한 특징을 갖는 데이터셋을 물색한다.

그다음에는 해당 데이터셋으로 학습되었고 준수한 성능을 가진 신경망을 선택한다.

여기서는 이미지넷 데이터셋으로 학습된 VGG16 신경망을 활용한다.

VGG16 신경망을 도입하기 위해 먼저 사전 학습된 가중치를 포함한 모델을 내려받고, 분류기 부분을 제거한 다음 우리가 해결하려는 문제의 분류기 부분을 새로 만들어 추가한다.

그리고 새로 구성한 신경망을 학습한다.

이러한 방법을 사전 학습된 신경망을 특징 추출기로 활용하는 방법이라고 한다.

현재 예제에서는 신경망 구현과 가중치를 모두 내려받아 base_model을 구성한다.

base_model = vgg16.VGG16(weights = "imagenet", include_top=False, input_shape = (224,224, 3))
base_model.summary()

지금 내려받은 신경망 구조에는 include_top 인수의 값을 False로 했기 때문에 분류기 부분이 빠져있다.

특징 추출기 부분에 해당하는 층의 가중치는 고정시킨다.

# iterate through its layers and lock them to make them not trainable with this code
for layer in base_model.layers:
    layer.trainable = False

base_model.summary()

다른 내용은 모두 그대로지만, 모든 층의 가중치를 고정했으므로 Trinable param의 수가 0이 된다.

분류기 부분을 새로 구현해서 추가한다.

from keras.layers import Dense, Flatten, Dropout, BatchNormalization
from keras.models import Model

# use “get_layer” method to save the last layer of the network
# save the output of the last layer to be the input of the next layer
last_layer = base_model.get_layer('block5_pool')
last_output = last_layer.output

# flatten the classifier input which is output of the last layer of VGG16 model
x = Flatten()(last_output)

x = Dense(2, activation='softmax', name='softmax')(x)

base_model의 입력을 입력으로, 소프트맥스층의 출력을 출력으로 사용하는 새로운 모델인 new_model을 구축한다.

새 모델은 사전학습된 VGGNet에서 전용한 특징 추출기와 아직 학습되지 않은 새 소프트맥스층으로 구성된다.

이렇게 구성한 모델은 새로운 모델을 처음부터 학습시키는 것보다 훨씬 빨리 학습을 끝낸다.

또한, 전이학습을 통해 이미지넷 + 직접 구축한 학습 데이터를 모두 학습했으므로 성능도 훨씬 뛰어나다.

 

6.3 전이학습의 원리

이번에는 전이학습이 어떤 원리로 이 같은 효과를 제공하는지 알아보자.

또한, 해결하려는 과업과 다르거나 관계가 없는 데이터셋으로 학습한 신경망이 좋은 성능을 보이는 이유도 설명한다.

  1. 신경망 학습 과정에서 길제로 학습되는 것은?
    특징맵.
  2. 특징은 어떻게 학습되는가?
    역전파 계산 과정을 통해 최적화된 가중치가 된다.
  3. 특징과 가중치의 관계는?
    특징 맵은 입력 이미지가 가중치 필터를 통과하며 합성곱 연산을 거친 결과.
  4. 두 신경망 사이에서 실제로 옮겨지는 대상은?
    사전 학습된 신경망의 최적화된 가중치. 이를 시작접으로 삼고 해결하려는 문제를 위해 다시 학습시킨다.

 

사전학습된 신경망은 무엇을 가리킬까?

합성곱 신경망을 학습할 때 신경망은 이미지에서 특징 맵 형태로 특징을 추출한다.

신경망이 만족스러운 성능을 보이는 상태를 학습된 신경망이라고 한다.

학습이 끝나면 신경망 구조와 학습된 가중치 두 가지가 남는다.

그러므로 사전 학습된 신경망을 활용하려면 이 두 가지를 함께 내려 받아야 한다.

인셉션 등과 같은 대규모 모델은 이미지넷과 같은 대규모 데이터셋을 대상으로 학습되므로 거의 모든 특징이 이미 추출되어 사용할 수 있는 상태가 된다.

6.3.1 신경망이 특징을 학습하는 방법

신경망은 단순한 것부터 각 층마다 차근차근 복잡도를 올라가며 특징을 학습하고, 이렇게 학습된 특징을 특징 맵이라고 한다.

신경망의 뒷쪽으로 갈수록 해당 이미지의 특징적인 특징이 학습된다.

층을 거쳐 가며 보다 복잡한 특징을 나타내는 활성화 맵이 생성된다.

그림에서 보이는 것처럼 앞쪽 층의 특징은 저수준 특징으로 4개 모델에서 별 차이가 없다. 즉, 다른 과업을 위해 쉽게 재사용할 수 있다는 말이다.

6.3.2 뒤쪽 층에서 학습된 특징의 재사용성

신경망의 뒤쪽 층에서 학습된 특징의 재사용 가능 여부는 기반 모델의 데이터셋과 새로운 데이터셋의 유사성과 관계가 깊다.

고수준 특징은 과업마다 크게 다르므로, 고수준 내지 중수준 특징의 재사용성 여부는 원 도메인과 목표 도메인과의 유사성에 따라 판단해야 한다.

 

6.4 전이학습의 세 가지 방식

전이학습 방식은 사전 학습된 신경망을 분류기로 이용, 사전 학습된 신경망을 특징 추출기로 이용, 미세 조정 등 크게 세 가지로 나뉜다.

6.4.1 사전 학습된 신경망을 분류기로 이용하기

사전 학습된 모델을 어떤 변경이나 추가 학습 없이 새로운 이미지를 분류하는 데 사용한다.

이 방식은 원 도메인과 목표 도메인이 매우 유사하고, 사전 학습된 신경망을 즉시 사용할 수 있는 경우에 적용한다.

과정은 아래와 같다. 주석을 참고하며 살펴보기 바란다.

# 필요한 라이브러리를 임포트한다.
from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.applications.vgg16 import preprocess_input
from keras.applications.vgg16 import decode_predictions
from keras.applications.vgg16 import VGG16

# 사전 학습된 VGG16 신경망과 가중치를 내려받는다.
# 이때 include_top을 true로 설정해서 전체 신경망의 가중치를 내려받는다.
# load the model
model = VGG16(weights="imagenet", include_top=True, input_shape = (224,224,3))

# load an image from file
image = load_img('dog.jpg', target_size=(224, 224))

# convert the image pixels to a numpy array
image = img_to_array(image)

# reshape data for the model
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))

# prepare the image for the VGG model
image = preprocess_input(image)

# predict the probability across all output classes
yhat = model.predict(image)

# convert the probabilities to class labels
label = decode_predictions(yhat)

# retrieve the most likely result, e.g. highest probability
label = label[0][0]

# print the classification
print('%s (%.2f%%)' % (label[1], label[2]*100))

모델은 높은 신뢰도 99.72%를 보여준다. 이는 정확한 견종을 예측하도록 학습된 상태임을 확인할 수 있다.

6.4.2 사전 학습된 신경망을 특징 추출기로 이용하기

이 방식은 사전 학습된 CNN의 특징 추출기 부분 가중치를 고정하고, 분류기 부분을 제거한 다음 새로운 분류기 부분을 추가한다.

이 또한 원 도메인과 새로운 과업이 큰 차이가 없을 때 주로 사용하는 방식이다.

사전 학습된 신경망의 분류기 부분은 원 분류 과업에 특화된 경우가 많고, 이후 모델이 학습된 클래스넷에만 한정되기 때문에 제거해야 한다.

6.4.3 미세 조정하기

목표 도메인과 원 도메인이 많이 동떨어진 경우에 사용한다.

원 도메인에서 정확한 특징을 추출해서 목표 도메인에 맞게 미세 조정을 거치면 된다.

미세 조정은 특징 추출에 쓰이는 신경망의 일부를 고정하고, 고정되지 않은 층과 새롭게 추가된 분류기 부분의 층을 함께 학습하는 방식이다.

다음과 같이 기존 특징 맵을 보전하는 단계를 결정한다.

  • 두 도메인이 매우 비슷하다면 마지막 특징 맵까지 보전
  • 두 도메인이 다르다면 특징 맵은 1까지만 보전

1) 미세 조정이 모델을 처음부터 학습시키는 것보다 나은가?

사전 학습된 신경망의 가중치는 이미 학습 데이터를 학습하며 최적화된 상태이다.

따라서, 이 신경망을 새로운 문제에서 재사용하면 한번 최적화된 가중치를 대상으로 학습이 진행되어 수렴이 빨라진다.

2) 미세 조정에서는 학습률을 작게 설정한다.

미세 조정에서는 합성곱층의 학습률을 무작위 값으로 초기화된 가중치를 가진 분류기 보다 작게 설정한다.

합성곱층 부분의 가중치는 이미 최적값에 가까워서 빠르게 수정할 필요가 상대적으로 적기 때문이다.

 

6.5 적합한 전이학습 수준 선택하기

앞쪽에 위치한 합성곱층일수록 일반적인 특징, 뒤쪽에 위치한 합성곱층일수록 데이터셋에 특화된 특징을 추출하는 경향은 앞서 설명했다.

전이학습의 적합한 수준은 원 도메인과 목표 도메인의 유사성에 따라 달라진다.

적합한 수준을 결정하는 중요한 요소는 다음 두 가지이다.

  • 목표 데이터셋의 크기:
    목표 데이터셋의 크기가 작다면 많은 층을 학습시키기 어렵고, 새로운 데이터에 대해 과적합을 일으키기 쉽다. 이러한 경우에는 미세 조정 범위를 줄이고 원 데이터셋의 의존도를 높인다.
  • 원 도메인과 목표 도메인의 유사성:
    비슷한 특징을 다수 포함한다면 미세 조정 범위가 줄어들고, 반대인 경우 늘어난다.

 

6.5.1 시나리오 1: 목표 데이터셋의 크기가 작고, 두 도메인이 유사

사전 학습된 고수준 특징도 재사용 가능하다.

따라서 특징 추출기 부분의 가중치를 고정하고 분류기 부분만 새로 학습하는 것이 좋다.

데이터셋 크기가 작으면 대상의 모든 특징을 담지 못 해 일반화 성능이 떨어지고 과적합 가능성이 높아진다.

6.5.2 시나리오 2: 목표 데이터셋의 크기가 크고, 두 도메인이 유사

특징 추출기 부분의 가중치를 고정하고, 분류기 부분을 재학습한다.

충분한 데이터가 있으므로 미세 조정 범위를 적절히 넓혀도 과적합에 대한 우려가 조금 줄어든다.

사전 학습된 신경망의 6~80% 정도를 고정하는 것이 적절하다.

6.5.3 시나리오 3: 목표 데이터셋의 크기가 작고, 두 도메인이 상이

고수준 특징까지 고정하는 것은 적절하지 않다.

앞쪽 층 일부 저수준 특징을 고정하거나 특징 재활용 없이 전체 신경망을 미세 조정 범위로 삼는 것이 좋다.

하지만, 목표 데이터셋이 작으므로 이런 경우 사전 학습된 신경망의 앞부분 1/3이나 절반 정도를 고정하는 수준에서 시작하는 것이 좋다.

6.5.4 시나리오 4: 목표 데이터셋의 크기가 크고, 두 도메인이 상이

전이 학습 없이 전체 신경망을 처음부터 학습할 수 있다는 유혹에 빠지기 쉽다.

그러나 실제로는 사전학습된 가중치를 재학습하는 것이 더 이로운 경우가 많다.

학습 시간이 빠르고, 목표 데이터셋의 크기가 크므로 과적합에 대한 우려 없이 재학습을 할 수 있다.

 

6.6 오픈 소스 데이터셋

  • MNIST
    특성:
    MNIST는 필기 숫자 인식에 널리 사용되는 데이터 세트이다.
    60,000개의 훈련 이미지와 10,000개의 테스트 이미지로 구성되며, 각각 28x28 픽셀 크기를 가진다.
    장점:
    간단하고 작업하기 쉬워 컴퓨터 비전 초보자에게 적합하다. 이미지 크기가 작고 복잡성이 제한적이기 때문에 빠른 훈련 및 평가가 가능하다.
    단점:
    실제 시나리오를 대표하지 않을 수 있는 비교적 단순한 데이터 세트. 다른 데이터 세트에 비해 다양성과 복잡성이 제한적이다.
  • Fashion-MNIST
    특징:
    Fashion-MNIST는 의류 품목 분류에 중점을 두고 MNIST를 대체하기 위해 설계된 데이터 세트이다.
    60,000개의 훈련 이미지와 10,000개의 테스트 이미지(28x28 픽셀 크기)가 포함되어 있다.
    장점:
    MNIST에 비해 더 까다로운 작업을 제공한다. 의류 품목의 다양한 시각적 패턴과 변형을 더 폭넓게 표현한다.
    단점:
    ImageNet과 같은 복잡한 데이터 세트에 비해 상대적으로 단순함. 이미지 크기가 작아 공간 해상도가 제한됨.
  • CIFAR(CIFAR-10 및 CIFAR-100)
    특징:
    CIFAR 데이터 세트는 10개 또는 100개의 객체 클래스가 있는 작은 컬러 이미지로 구성된다.
    CIFAR-10은 60,000개의 32x32 이미지를 10개 클래스로 나누고, CIFAR-100은 100개의 클래스를 포함한다.
    장점:
    컬러 이미지로 더 높은 수준의 복잡성과 사실감을 제공한다. 더 작은 CNN 아키텍처를 벤치마킹하고 일반화 성능을 평가하는 데 적합하다.
    단점:
    최신 데이터 세트에 비해 이미지 해상도가 상대적으로 낮다.
    ImageNet과 같은 대규모 데이터 세트에 비해 객체의 다양성이 제한적이다.
  • ImageNet
    특징:
    ImageNet은 수천 개의 객체 카테고리에 걸쳐 수백만 개의 레이블이 지정된 이미지가 포함된 대규모 데이터 세트다.
    장점:
    광범위한 실제 객체 범주와 변형을 나타냅니다. 대규모 물체 인식 작업에 대한 까다로운 벤치마크를 제공한다.
    단점:
    규모가 크기 때문에 모델을 훈련하고 평가하는 데 상당한 컴퓨팅 리소스가 필요하다. 데이터 세트에 라벨링 오류와 노이즈가 존재할 수 있어 훈련 및 평가에 영향을 미칠 수 있다.
  • MS COCO(컨텍스트에 따른 마이크로소프트 공통 객체)
    특성:
    MS COCO는 객체 감지, 세분화 및 캡션 작업에 널리 사용되는 데이터 세트이다. 다양한 객체 범주와 상세한 주석이 포함된 많은 수의 이미지가 포함되어 있다.
    장점:
    다양한 객체 범주와 복잡한 장면을 표현한다. 여러 작업에 대한 풍부한 주석을 제공하여 다양한 연구가 가능하다.
    단점:
    더 크고 복잡한 데이터 세트이므로 더 많은 계산 리소스가 필요하다. 주석이 모든 인스턴스에 정확하게 적용되지 않을 수 있어 정확한 객체 감지에 어려움이 있다.
  • Google 오픈 이미지
    특징:
    Google 오픈 이미지는 수천 개의 객체 카테고리를 포괄하는 수백만 개의 이미지가 포함된 대규모 데이터 세트이다. 객체 감지, 인스턴스 세분화 및 시각적 관계 작업을 위해 설계되었다. 
    장점:
    광범위한 객체와 장면을 포함하는 방대하고 다양한 이미지 컬렉션을 제공한다. 복잡한 작업을 위한 바운딩 박스 주석과 개체 간 관계를 제공한다.
    단점:
    데이터 세트가 복잡하여 상당한 계산 리소스가 필요하고 훈련 시간이 더 오래 걸린다. 주석에 오류나 불일치가 있을 수 있으므로 모델 개발 시 세심한 주의가 필요하다.

 

6.7 프로젝트 1: 사전 학습된 신경망을 특징 추출기로 사용하기

매우 적은 양의 데이터만 사용해서 개와 고양이의 이미지를 분류하는 분류기를 만든다.

시나리오 1 목표 데이터셋의 규모가 작고, 원 도메인과 목표 도메인의 유사성이 높은 경우 전이학습을 어떻게 구현해야 하는지에 대한 좋은 참고가 될 것이다.

이런 경우에는 사전 학습된 합성곱 신경망을 특징 추출기로 사용하는 것이 좋다.

새로 구현한 분류기 부분을 추가해 목표 데이터셋으로 재학습시키는 과정을 거친다.

이번 프로젝트에서는 자신만의 데이터 저장소를 구성하고 케라스 라이브러리를 이용해서 이들 데이터를 학습에 사용할 수 있도록 준비하는 방법을 익힌다.

1) 라이브러리 임포트

from keras.preprocessing.image import ImageDataGenerator
from keras.preprocessing import image
from keras.applications import imagenet_utils
from keras.applications import vgg16
from keras.applications import mobilenet
from tensorflow.keras.optimizers import Adam, SGD
from keras.metrics import categorical_crossentropy

from sklearn.metrics import confusion_matrix
import itertools
import matplotlib.pyplot as plt
%matplotlib inline

 

2) 데이터 전처리

ImageDataGenerator라는 클래스로 데이터 강화를 해줄 수 있다.

여기서는 데이터를 텐서 형태로만 변환했다.

train_path  = './exmples/chapter_06/dogs_vs_cats_project/data/train'
valid_path  = './exmples/chapter_06/dogs_vs_cats_project/data/valid'
test_path  = './exmples/chapter_06/dogs_vs_cats_project/data/test'

train_batches = ImageDataGenerator(preprocessing_function=vgg16.preprocess_input).flow_from_directory(
    train_path, target_size=(224,224), batch_size=30)
valid_batches = ImageDataGenerator(preprocessing_function=vgg16.preprocess_input).flow_from_directory(
    valid_path, target_size=(224,224), batch_size=30)
test_batches = ImageDataGenerator(preprocessing_function=vgg16.preprocess_input).flow_from_directory(
    test_path, target_size=(224,224), batch_size=30)

 

3) 사전 학습된 가중치 읽기

대규모 데이터셋에 사전 학습된 VGG16 신경망의 가중치를 읽어 들인다.

분류기 부분을 제거하기 위해 include_top = False로 지정한다.

base_model = vgg16.VGG16(weights = "imagenet", include_top=False, input_shape = (224,224, 3))
base_model.summary()

전부 훈련 가능한 파라미터이다.

4) 모든 합성곱층 (특징 추출기 부분) 가중치 고정

코드에서는 base_model에 포함된 합성곱층의 가중치를 고정한 다음 이 부분을 특징 추출기로 사용한다.

# iterate through its layers and lock them to make them not trainable with this code
for layer in base_model.layers:
    layer.trainable = False

base_model.summary()

고정되었으므로 훈련 가능한 가중치가 없다.

5) 새로운 분류기를 추가한다.

유닛이 64개인 전결합층, 2개인 소프트맥스층을 추가하고, 과적합 방지를 위해 배치 정규화층과 드롭아웃층도 추가한다.

from keras.layers import Dense, Flatten, Dropout, BatchNormalization
from keras.models import Model

# use “get_layer” method to save the last layer of the network
# save the output of the last layer to be the input of the next layer
last_layer = base_model.get_layer('block5_pool')
last_output = last_layer.output

# flatten the classifier input which is output of the last layer of VGG16 model
x = Flatten()(last_output)

# add FC layer, which has 64 units and relu activation 
x = Dense(64, activation='relu', name='FC_2')(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
# add our new softmax layer with 2 hidden units
x = Dense(2, activation='softmax', name='softmax')(x)

# instantiate a new_model using keras’s Model class
new_model = Model(inputs=base_model.input, outputs=x)

# print the new_model summary
new_model.summary()

6) 모델을 컴파일하고 학습을 시작한다.

new_model.compile(Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['accuracy'])

new_model.fit_generator(train_batches, steps_per_epoch=4,
                   validation_data=valid_batches, validation_steps=2, epochs=20, verbose=2)

7) 모델의 성능을 평가한다.

먼저 데이터셋을 텐서로 변환하는 load_dataset() 메서드를 정의한다.

def load_dataset(path):
    data = load_files(path)
    paths = np.array(data['filenames'])
    targets = np_utils.to_categorical(np.array(data['target']))
    return paths, targets
    
from sklearn.datasets import load_files
from keras.utils import np_utils
import numpy as np

test_files, test_targets = load_dataset('data/test')

그다음에 테스트 데이터를 변환한 텐서 teset_tensor를 만든다.

from keras.preprocessing import image  
from keras.applications.vgg16 import preprocess_input

def path_to_tensor(img_path):
    # loads RGB image as PIL.Image.Image type
    img = image.load_img(img_path, target_size=(224, 224))
    # convert PIL.Image.Image type to 3D tensor with shape (224, 224, 3)
    x = image.img_to_array(img)
    # convert 3D tensor to 4D tensor with shape (1, 224, 224, 3) and return 4D tensor
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)

test_tensors = preprocess_input(paths_to_tensor(test_files))

이제 케라스의 evaluate() 메서드를 실행해서 모델의 정확도를 측정할 수 있다.

print('\nTesting loss: {:.4f}\nTesting accuracy: {:.4f}'.format(*new_model.evaluate(test_tensors, test_targets)))

# evaluate and print test accuracy
score = new_model.evaluate(test_tensors, test_targets)
print('\n', 'Test accuracy:', score[1])

 

6.8 프로젝트 2: 미세조정

이번 예제에서는 목표 데이터셋의 규모도 작고, 원 도메인과 목표 도메인이 매우 다른 경우를 다뤄보겠다.

프로젝트의 목표는 0부터 9를 나타내는 10가지 수화를 구별하는 분류기를 구축한다.

훈련 데이터는 1,712건, 검증 데이터 300건, 테스트 데이터 50건으로 데이터셋의 크기가 매우 작은 것을 알 수 있다.

1) 필요한 라이브러리르 임포트 한다.

from keras.preprocessing.image import ImageDataGenerator
from keras.preprocessing import image
from keras.applications import imagenet_utils
from keras.applications import vgg16
from tensorflow.keras.optimizers import Adam, SGD
from keras.metrics import categorical_crossentropy

from keras.layers import Dense, Flatten, Dropout, BatchNormalization
from keras.models import Model

from sklearn.metrics import confusion_matrix
import itertools
import matplotlib.pyplot as plt
%matplotlib inline

 

2) 데이터를 전처리 한다.

동일하게 ImageDataGenerator 클래스의 flow_from_directory() 메서드를 사용한다.

train_path  = 'dataset/train'
valid_path  = 'dataset/valid'
test_path  = 'dataset/test'

# ImageDataGenerator generates batches of tensor image data with real-time data augmentation. 
# The data will be looped over (in batches).
# in this example, we won't be doing any image augmentation
train_batches = ImageDataGenerator().flow_from_directory(train_path, 
                                                         target_size=(224,224), 
                                                         batch_size=10)

valid_batches = ImageDataGenerator().flow_from_directory(valid_path,
                                                         target_size=(224,224), 
                                                         batch_size=30)

test_batches = ImageDataGenerator().flow_from_directory(test_path, 
                                                        target_size=(224,224), 
                                                        batch_size=50, 
                                                        shuffle=False)
# ImageDataGenerator 클래스는 실시간으로 데이터 강화를 수행하며 여러 개의 배치로 나뉜 텐서를 생성해준다.
# 학습은 이렇게 분할된 배치를 차례대로 반복 입력하는 방식으로 진행된다. 여기선 쓰지 않는다.

 

3) 사전 학습된 가중치를 읽어 들인다.

VGG16 신경망의 사전 학습된 가중치를 내려받는데 여기서 파라미터 pooling='avg'인 것에 주목하라.

해당 파라미터는 마지막 합성곱층의 출력에 전역 풀링을 적용하라는 의미이다. 따라서 모델의 출력도 2차원 텐서 형태가 된다.

여기서는 전결합층을 위해 사용하는 1차원 변환층 (Flatten)을 대체하는 역할을 한다.

base_model = vgg16.VGG16(weights = "imagenet", include_top=False, input_shape = (224,224, 3), pooling='avg')
base_model.summary()

 

4) 특징 추출기 부분에 해당하는 일부 층의 가중치를 고정한다.

나머지 가중치는 목표 데이터셋을 이용해서 미세 조정한다. 수준은 시행착오를 통해 결정한다.

VGG16에는 13개의 합성곱층이 있다. 원 도메인과 목표 도메인의 유사도를 기준으로 가중치를 고정할 합성곱 층 수를 결정하면 된다.

여기서는 뒤쪽 5개의 합성곱층만 초기 미세 조정 수준으로 결정한다.

# iterate through its layers and lock them to make them not trainable with this code
for layer in base_model.layers[:-5]:
    layer.trainable = False

base_model.summary()

결과적으로 98%의 정확도를 달성했다. 

 

5) 분류기 부분을 새로 추가해서 모델을 완성한다.

# use “get_layer” method to save the last layer of the network
last_layer = base_model.get_layer('global_average_pooling2d')

# save the output of the last layer to be the input of the next layer
last_output = last_layer.output

# add our new softmax layer with 3 hidden units
x = Dense(10, activation='softmax', name='softmax')(last_output)

# instantiate a new_model using keras’s Model class
new_model = Model(inputs=base_model.input, outputs=x)

# print the new_model summary
new_model.summary()

 

6) 모델을 컴파일한 후 학습을 시작한다.

new_model.compile(Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['accuracy'])

from keras.callbacks import ModelCheckpoint

checkpointer = ModelCheckpoint(filepath='signlanguage.model.hdf5', save_best_only=True)

history = new_model.fit_generator(train_batches, steps_per_epoch=18,
                   validation_data=valid_batches, validation_steps=3, epochs=20, verbose=1, callbacks=[checkpointer])

 

7) 모델의 정확도를 평가한다.

모델의 정확도를 더 심층적으로 평가하려면 4장에서 설명한 혼동 행렬을 작성해야 한다.

혼동 행렬은 분류 모델의 성능을 평가하는 데 사용되는 표로, 테스트 데이터에 대한 모델의 성능을 평가하는 데 사용되는 표로, 테스트 데이터에 대한 모델의 성능을 더 잘 이해하는 데 도움이 된다.

def load_dataset(path):
    data = load_files(path)
    paths = np.array(data['filenames'])
    targets = np_utils.to_categorical(np.array(data['target']))
    return paths, targets
    
from sklearn.datasets import load_files
from keras.utils import np_utils
import numpy as np

test_files, test_targets = load_dataset('dataset/test')

from keras.preprocessing import image  
from keras.applications.vgg16 import preprocess_input
from tqdm import tqdm

def path_to_tensor(img_path):
    # loads RGB image as PIL.Image.Image type
    img = image.load_img(img_path, target_size=(224, 224))
    # convert PIL.Image.Image type to 3D tensor with shape (224, 224, 3)
    x = image.img_to_array(img)
    # convert 3D tensor to 4D tensor with shape (1, 224, 224, 3) and return 4D tensor
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)

test_tensors = preprocess_input(paths_to_tensor(test_files))
new_model.load_weights('signlanguage.model.hdf5')
print('\nTesting loss: {:.4f}\nTesting accuracy: {:.4f}'.format(*new_model.evaluate(test_tensors, test_targets)))

from sklearn.metrics import confusion_matrix

cm_labels = ['0','1','2','3','4','5','6','7','8','9']

cm = confusion_matrix(np.argmax(test_targets, axis=1),
                      np.argmax(new_model.predict(test_tensors), axis=1))
plt.imshow(cm, cmap=plt.cm.Blues)
plt.colorbar()
indexes = np.arange(len(cm_labels))
for i in indexes:
    for j in indexes:
        plt.text(j, i, cm[i, j])
plt.xticks(indexes, cm_labels, rotation=90)
plt.xlabel('Predicted label')
plt.yticks(indexes, cm_labels)
plt.ylabel('True label')
plt.title('Confusion matrix')
plt.show()

혼동 행렬을 읽는 법은 다음과 같다. 예측 레이블 축의 숫자가 얼마나 정확히 분류되었는가를 정답 레이블 축에서 보면 된다.

예를 들어 예측 레이블 숫자 0을 보면 이미지 5장이 모두 올바르게 분류되었음을 알 수 있다.

반응형