본문 바로가기

Learning/Vision AI

06. 전이학습

목차

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

  • 데이터 부족 : 어느정도 성능을 확보하여 문제를 해결하기 위해서는 대량의 데이터셋이 필요하지만, 레이블링된 데이터를 확보하는 것은 실제로 어려운 일이다.
  • 과다한 계산 요구량 : 수백만장의 이미지를 이용해서 신경망을 학습 시키기 위해서는 엄청난 양의 계산 자원이 필요하다. 또한 만족스러운 성능을 내는 하이퍼파라미터를 선택하기 위해 많은 실험을 반복해야하는 비용도 무시할 수 없다.
  • 일반화 성능 확보의 어려움 : 실제 이미지는 각도, 날씨, 해상도 등 많은 변수들이 있다. 그러므로 기존에 없던 경우에도 좋은 일반화 성능을 내는 robust한 모델을 만들어야 한다.

6.2 전이학습이란?

: 신경망이 어떤 task를 위해 많은 양의 데이털르 이용해 학습한 Feature Map을 학습 데이터가 상대적으로 적은 다른 유사한 task로 옮겨오는 것(사전 학습된 신경망의 일부 층을 전용해서 새로운 문제 해결)

직관적인 이해를 위해 사전 학습된 VGG16 코드 예제를 통해 전이학습을 이해해보자!!

 

1. 가중치를 포함한 VGG16 신경망을 내려받아 base model을 만들고, 신경망의 분류기 부분을 제거한다.

from keras.applications.vgg16 import VGG16

# include_top=False로 분류기 부분의 가중치는 내려받지 않음
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(224,224,3))
base_model.summary()

 

2. 모델 요약에서 보는것과 같이 분류기 부분(3개의 전결합층)이 빠진것을 볼 수 있고, 약 1400만개의 학습 가능한 파라미터가 있다. 이들 층의 가중치는 변경하지 않고 그대로 사용하며 새로 구현한 분류기 부분만 추가한다.

 

(layer 생략)

3. 특징 추출기 부분에 해당하는 층의 가중치는 고정시킨다.(미리 학습된 가중치가 추가 학습으로 변하지 않도록)

for layer in base_model.layers:
    layer.trainable = False
base_model.summary()

-> 학습 가능한 파라미터가 0이 된 것을 볼 수 있다.

 

4. 분류기 부분을 새로 구현해서 추가한다. 분류 대상 클래스가 2개이므로 유닛이 2개인 소프트맥스층 추가

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

last_layer = base_model.get_layer('block5_pool') # 신경망의 마지막 층에 접근
last_output = last_layer.output # 마지막 층의 출력을 다음 층의 입력으로 

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

5. 새로운 모델 구축. (사전 학습된 VGGNet의 특징추출기 + 학습되지 않은 새로운 소프트맥스층)

-> 소프트맥스층만 학습

new_model = Model(inputs=base_model.input, outputs=x)
new_model.summary()

6.3 전이학습의 원리

전이학습의 핵심 질문

1. 신경망의 학습 과정에서 실제로 학습되는 것은? Feature Map

2. 특징은 어떻게 학습되는가? 역전파 계산 과정에서 가중치는 오차 함숫값이 최소가 되도록 수정되어 최적 가중치가 됨

3. 특징과 가중치의 관계는? Feature Map은 입력 이미지가 가중치 필터를 통과하며 Conv 연산을 걸친 결과다.

4. 두 신경망 사이에서 실제로 옮겨는 대상은 무엇일까? 특징을 옮기기 위해 사전 학습된 신경망의 최적화된 가중치를 내려받았다. 이들 가중치를 재사용해서 새로운 모델의시작점으로 삼고 우리가 해결하려는 문제를 위해 다시 학습

 

사전 학습된 신경망?
신경망은 이미지에서 Featrue Map 형태로 특징을 추출한다. 여기서 말하는 특징이란 직선, 모서리, 특정한 대상 등이다. 신경망의 학습 과정에서 가중치는 순방향 계산과 역전파 계산을 되풀이하며 반복적으로 수정되는데, 이런 학습 과정과 하이퍼파라미터 튜닝을 거쳐 신경망이 만족스러운 성능을 보이게 된 상태를 학습된 신경망이라고 한다. 여기서 사전 학습된 신경망을 활용하기 위해서는 신경망 구조와 가중치를 함께 활용해야한다. 

학습 중에 특징을 추출하기 위해서는 훈련 데이터에 포함된 특징이여야 하는데 흥미로운점은 이렇게 사전 학습된 모델에는 학습 데이터에 포함되지 않았던 특징까지 포함되어 다른 신경망을 학습하는 데 도움을 준다는 것이다.

 

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

  • 신경망은 단순한 것부터 각 층마다 복잡도를 올려가며 특징을 학습(학습된 특징을 Feature map이라 함)
  • 고차원 특징을 인식하는 층은 입력된 특징 중 해당 대상을 구별하는데, 중요한 특징은 증폭하고, 그렇지 않은 특징은 억제한다.
  • 예를 들어, 사람의 얼굴을 인식하는 모델을 구축할 때, 첫번째 층에서 직선, 모서리, 얼룩 같은 저수준 특징이 학습되고, 중수준 특징은 저수준 특징을 조합해서 도령, 꼭짓점, 원 등을 인식한다. 중수준 특징 부터는 과업에 따라 조금씩 차이가 나타나고 층이 깊어질 수록 과업과 매우 관련 깊은 특징을 도출한다.

  • 앞쪽 층은 모서리, 직선 얼룩 등의 저수준 특징을 추출하기 때문에 다른 task을 위해 쉽게 재사용 가능하다.

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

  • 뒤쪽 층의 재사용 가능 여부느느 base model의 데이터셋과 새로운 데이터셋의 유사성과 관계가 깊다.
  • 저수준의 특징은 서로 다른 task에서도 재사용이 가능하지만, 고수준 특징은 task마다 크게 다르다.

6.4 전이학습의 세 가지 방식

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

  • 사전 학습된 신경망의 가중치를 고정하거나 추가적인 학습이 필요하지 않다.
  • 비슷한 task에 대해 학습된 신경망을 골라 새로운 task에 그대로 적용하는 방식
  • 원 도메인과 목표 도메인이 매우 유사하고, 사전 학습된 신경망을 즉시 사용할 수 있는 경우 적용

1. 사전 학습된 VGG16 신경망과 가중치 내려받기(include_top=True)

from tensorflow.keras.utils import img_to_array
import keras.utils as image
from keras.applications.vgg16 import preprocess_input, decode_predictions, VGG16

model = VGG16(weights='imagenet', include_top=True, input_shape=(224,224,3))

2. 입력 이미지를 읽고 전처리

image = load_img('경로/파일이름.jpg', target_size=(224,224))
image = image_to_array(image)
image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
image = preprocess_input(image)

3. 예측 실행

yhat = model.predict(image)
label = decode_predictions(yhat)
label = label[0][0]
print(label[1], label[2])

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

  • 특징 추출기 부분 가중치를 고정하고 분류기 부분을 제거한 다음 새로운 분류기 부분(전결합층)을 추가
  • 원 도메인의 데이터셋과 새로운 과업이 큰 차이가 없을 때 주로 사용하는 방식
  • 예를 들어, imagenet은 1,000개 이상의 분류 클래스가 있으므로 개와 고양이만 분류하면 되는 task에서는 새로운 분류기 부분을 만들어 추가하는 것이 효율적임

6.4.3 미세 조정하기(Fine Tuning)

  • 목표 도메인이 원 도메인과 많이 동떨어진 경우에 원 도메인의 특징을 추출해서 목표 도메인에 맞게 미세조정
  • 미세조정 : 특징 추출에 쓰이는 신경망의 일부 층을 고정하고 고정하지 않은 층과 새로 추가된 분류기 부분의 층을 함께 학습하는 방식
  • 사전 학습된 층을 어디까지 보전할 것인가 결정

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

모델을 처음 학습시키면 가중치를 랜덤하게 초기화한 후에 최적값을 찾아가기 때문에 사전 학습된 신경망 전체를 재학습한다고 하더라도 미세 조정이 학습 속도가 더 빠르다.

 

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

Conv층 부분의 가중치는 이미 최적값에 가까워서 빠르게 수정할 필요가 상대적으로 적기 때문에 무작위 값으로 초기화된 분류기보다 작게 설정하는 것이 좋다.

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

전이 학습의 적합한 수준을 결정하는 중요한 요소

  • 목표 데이터셋의 크기 : 목표 데이터셋의 크기가 작다면 많은 층을 학습시키기 어렵고 새로운 데이터에 대해 과적합을 일으키기 쉽다. 이런 경우에는 미세 조정 범위를 줄이고 원 데이터셋의 의존도를 높여야 한다.
  • 원 도메인과 목표 도메인의 유사성 : 도메인이 유사하지 않을수록 미세 조정 범위가 넓어져야 한다.

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

  • 두 도메인이 유사하므로 사전 학습된 고수준 특징도 재사용 가능하다. 따라서 특징 추출기 부분의 가중치를 고정하고 분류기 부분만 새로 학습하는 것이 좋다.
  • 목표 데이터 셋의 크기가 작기 때문에 특징 추출기에 해당하는 층을 미세 조정 범위에 포함시키면 대상의 모든 가능한 특징을 담지 못할 가능성이 높기 때문에 일반화 성능이 떨어진다 그러므로 미세 조정 범위를 넓힐수록 과적합을 일으킬 가능성이 높다.

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

  • 두 도메인이 유사하므로 특징 추출기 부분의 가중치를 고정하고 분류기 부분을 재학습한다.
  • 시나리오1과 비슷하지만 데이터셋의 크기가 크기 때문에 미세 조정 범위를 적절히 넓혀도 된다.
  • 사전 학습된 신경망의 60~80% 정돌르 고정하고 나머지 부분을 목표 데이터셋으로 재학습하는 것이 적절하다.

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

  • 두 도메인이 크게 다르므로 사전 학습된 신경망을 도메인에 특화된 고수준 특징까지 고정하는 것은 적적치 않다.
  • 앞쪽 층의 일부 저수준 특징을 고정하거나 특징 재활용 없이 전체 신경망을 미세 조정 범위에 포함 시키는게 좋다.
  • 목표 데이터셋의 크기가 너무 작아 전체 신경망 재학습이 어렵다면, 사전 학습된 신경망의 앞부분 1/3이나 절반 정도의 특징을 고정하는 수준으로 시작하는것이 좋음

6.5.4 시나리오4 : 목표 데이터셋의 크기가 크고, 두 도메인이 크게 다른 경우

  • 목표 데이터셋의 크기가 크더라도 사전 학습된 가중치를 재학습 하는 것이 더 좋은 경우가 많다.

※ 정리

시나리오 목표 데이터셋 크기 두 도메인의 유사성 적합한 접근법
1 작다 유사 사전학습된 신경망을 특징 추출기로 사용
2 크다 유사 전체 신경망을 미세 조정
3 작다 크게 다르다 신경망 앞부분을 미세 조정
4 크다 크게 다르다 전체 신경망을 미세조정

6.6 오픈 소스 데이터셋

6.6.1 MNIST

  • 손글씨 이미지 데이터셋
  • 0~9까지의 숫자가 쓰인 손글씨 이미지로 구성
  • 6만장의 Train data와 1만 장의 Test  data로 구성
  • Image shape : 28x28x1

6.6.2 Fashion-MNIST

  • Fashion관련 이미지 데이터셋
  • 티셔츠, 바지, 드레스, 코드, 운동화, 가방 등 10가지 패션 의류 클래스

6.6.3 CIFAR-10

  • 비행기, 자동차, 고양이, 개, 새, 사슴 등 10가지 클래스(각 클래스마다 6,000장의 이미지)
  • Image shape : 32x32x3
  • 5만장의 Train data와 1만 장의 Test  data로 구성
  • CIFAR-100 : 클래스 100개(각 클래스마다 600장의 이미지) 100개의 클래스는 다시 20개의 슈퍼클래스로 구분되어 대분류레이블과 소분류 레이블이 부여되어 있다.

6.6.4 ImageNet

  • 단어와 구로 정의된 2만 2천가지에 이르는 클래스
  • 약 1,400만 장 이상의 이미지

6.6.5 MS COCO

  • 객체 인식, 인스턴스 세그멘테이션, 이미지 캡셔닝, 인체 주요 부위 파악(openpose) 등의 연구를 위해 만든 오픈 소스 데이터베이스
  • 약 32만 8,000장의 이미지에 약 20만 장 이상에 레이블이 부여가 되어 있다.
  • 80개 물체 카테고리, 150만 건의 물체

6.6.6 Google Open Image

  • 구글이 만든 오픈 소스 이미지 데이터베이스
  • 물체 위치가 표시된 이미지 데이터베이스 중 가장 큰 규모
  • 약 1,500만 개이 상의 물체 위치가 태깅 되어 있음, 물체 종류도 약 600개

6.6.7 Kaggle

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

  • 적은 양의 데이터만 사용해서 개와 고양이 이미지를 분류하는 분류기 만들기
  • 시나리오1 : 목표 데이터셋이 작고 두 도메인이 유사한 경우
  • 사전 학습된 신경망의 특징 추출기 부분을 고정한 후 새로 구현한 분류기 부분을 추가해서 목표 데이터셋으로 재학습

1. 필요한 라이브러리 Import

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. 이미지 로드 및 전처리(데이터 강화는 생략)

train_path  = './deep_learning_for_vision_systems-master/chapter_06/dogs_vs_cats_project/data/train'
valid_path  = './deep_learning_for_vision_systems-master/chapter_06/dogs_vs_cats_project/data/valid'
test_path  = './deep_learning_for_vision_systems-master/chapter_06/dogs_vs_cats_project/data/test'

# ImageDataGenerator 클래스의 flow_from_directory 메서드 사용을 위해서는 디렉토리 구조를 맞춰야함
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. 모든 Conv층(특징 추출기)의 가중치를 고정

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

last_layer = base_model.get_layer('block5_pool') # 신경망의 마지막 층 가져오기
last_output = last_layer.output # 마지막 층의 출력을 다음 층의 입력으로 

x = Flatten()(last_output)
x = Dense(64, activation='relu', name='FC_2')(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
x = Dense(2, activation='softmax', name='softmax')(x)

new_model = Model(inputs=base_model.input, outputs=x)
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()메서드 정의
  • 테스트 데이터를 변환한 텐서 test_tensor 만들기
  • evaluate() 메서드를 통해 성능 평가
from sklearn.datasets import load_files
from keras.utils import np_utils
import numpy as np

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

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

import keras.utils as 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))

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

6.8 프로젝트2 : 미세 조정

  • 0~9를 나타내는 10가지 수화를 구별할 수 있는 분류기 구축하기
  • 시나리오3 : 목표 데이터셋의 규모도 작고 두 도메인이 다른 경우
  • image_size : 100x100x3
  • Train data : 1,712건,  Valid data : 300건, Test data : 50건

1. 필요한 라이브러리 Import

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. 데이터 로드 및 전처리

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

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)

3. VGG16 신경망의 가중치 가져오기

  • pooling='avg' : 마지막 Conv층의 출력에 전역 평균 풀링 적용 -> 모델의 출력도 2차원 텐서 형태가 된다.
  • 전경합층 입력을 위해 사용하는 Flatten층 대체하는 역할
base_model = vgg16.VGG16(weights = "imagenet", include_top=False, input_shape = (224,224, 3), pooling='avg')
base_model.summary()

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

  • 나머지 가중치는 목표 데이터셋을 이용해서 미세 조정
  • 두 도메인이 얼마나 유사하냐에 따라 고정할 합성곱층 수를 결정 -> 시행착오 필요
  • 예제는 뒤쪽 5개의 합성곱층만 초기 미세 조정 수준으로 결정
  • 성능이 불만족스럽다면 미세 조정 수준 늘리기
for layer in base_model.layers[:-5]:
    layer.trainable = False

base_model.summary()

5. 분류기 부분을 새로 추가해서 새로운 모델 완성하기

last_layer = base_model.output # base_model의 출력을 다음 층에 입력

x = Dense(10, activation='softmax', name='softmax')(last_output)

new_model = Model(inputs=base_model.input, outputs=x)
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. 모델의 정확도 평가

  • 위의 프로젝트1과 동일
  • confusion matrix를 통해 성능 파악을 해보자
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()


참고 : 비전 시스템을 위한 딥러닝(모하메드 엘겐디)

전체 코드 : https://github.com/moelgendy/deep_learning_for_vision_systems