목차
전이 학습(Transfer Learning)
지금까지 대규모 데이터세트에 대해 사전 트레이닝된 모델을 다운
하지만,
✔ 필요한 작업을 수행하는 사전 트레이닝된 모델을 찾을 수 없을때
✔ 모델을 처음부터 트레이닝하기 위한 충분히 크고 다양한 데이터세트가 없는 경우
전이학습을 이용하면 된다!!!
➡ 사전 트레이닝된 모델을 원래의 트레이닝 작업과 어느 정도 중첩되는 작업에 대해 다시 트레이닝가능
➡ 소규모 데이터세트에 대해 정확하고 강력한 모델을 트레이닝할 수 있는 가능성 높음
구현하기 전 읽어 볼 지식
- 📌필수📌Pre-Trained Models: 이 모델을 사용할 것이다
- 전이 학습
목표
- 전이 학습을 위한 사전 트레이닝된 모델 준비
- 사전 트레이닝된 모델로 자체적인 소규모 데이터세트에 대해 전이 학습 수행
- 훨씬 더 나은 성능을 위해 모델을 추가로 파인튜닝
맞춤형 분류기
전에 구현한 개만 출입이 가능한 개구멍에서 특정한 개만 출입이 가능하게 만들어 보겠다.
- Bo라는 개를 위한 자동 개구멍을 만들어 보겠다!! -> 다른 개를 분류하고싶으면 그 개의 사진을 왕창 가져오자!!
- 문제: Bo라는 개의 사진이 30개 밖에 없다(크고 다양한 데이터 세트가 없다는 것이다)
사전 트레이닝된 모델 다운로드
VGG16
의 마지막 레이어: 1,000개의 분류 라벨 중 하나의 동물로 분휴하는 밀집 레이어
필요한 마지막 레이어: Bo인지 아닌지를 분류하는 레이어
➡ 즉, 모델의 마지막 레이어를 제거해야함
include_top=False
: 모델의 마지막 레이어 제거
- 제거 후엔 우리가 원하는 새 레이어 추가 가능
from tensorflow import keras
base_model = keras.applications.VGG16(
weights='imagenet', # Load weights pre-trained on ImageNet.
input_shape=(224, 224, 3),
include_top=False)
그럼 모델을 확인해보자
base_model.summary()
👀 결과 보기
Model: "vgg16"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 224, 224, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 224, 224, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 112, 112, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 112, 112, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 112, 112, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 56, 56, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 56, 56, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 56, 56, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 28, 28, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 28, 28, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 28, 28, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 14, 14, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 14, 14, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 7, 7, 512) 0
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________
기본 모델 동결
새 레이어를 사전 트레이닝된 모델에 추가하기 전 수행해야할 중요한 단계
모델의 사전 트레이닝된 레이어를 동결해야 함
- 트레이닝할 때 사전 트레이닝된 모델에서 기본 레이어는 업데이트하지 않는 것
- 새로운 분류를 위해 우리가 끝에 추가하는 새 레이어만 업데이트
초기에 동결하는 이유: 중요한 정보가 손실될 가능성을 배제하기 위해
- 나중에 파인튜닝이라는 과정에서 이러한 레이어를 동결 해제하고 트레이닝할 수 있는 옵션존재
- 그러므로 필요시 파인튜닝에서 고치면 됌
동결하는 코드
base_model.trainable = False
새 레이어 추가
트레이닝 가능한 새 레이어를 사전 트레이닝된 모델에 추가
- 새 레이어: 사전 트레이닝된 레이어의 피처를 취해 새 데이터세트에 대한 예측으로 변환
[추가할 레이어]
- 풀링 레이어
- 마지막 레이어: Bo인지 아닌지를 분류
inputs = keras.Input(shape=(224, 224, 3))
# Separately from setting trainable on the model, we set training to False
x = base_model(inputs, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
# A Dense classifier with a single unit (binary classification)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)
그럼 추가된 모델을 살펴보겠다
model.summary()
👀 결과 보기
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_2 (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
vgg16 (Model) (None, 7, 7, 512) 14714688
_________________________________________________________________
global_average_pooling2d (Gl (None, 512) 0
_________________________________________________________________
dense (Dense) (None, 1) 513
=================================================================
Total params: 14,715,201
Trainable params: 513
Non-trainable params: 14,714,688
_________________________________________________________________
❔ 그런데 중간 레이어들이 모두 어디갔나요 ❔
vgg16 (Model)
내부 레이어 전체를 표시하는 대신 vgg16 사전 트레이닝된 모델을 하나의 유닛으로 보여줌
또한 Non-trainable params: 14,714,688
에서 알 수 있듯이 트레이닝 불가한 다수의 매개변수도 있다
모델 컴파일
손실 및 지표 옵션으로 모델을 컴파일 필요
하지만 전 모델과는 다른 방식을 취해야 한다.
- 전 모델: 다수의 범주 존재 ➡ 손실 계산을 위한 범주형 크로스 엔트로피를 선택
- 현재모델: 바이너리 분류 문제(Bo인지 아닌지 여부) ➡ 바이너리 크로스 엔트로피를 선택
from_logits=True
: 출력 값이 softmax 등으로 정규화되지 않았음을 손실 함수에 알림
# Important to use binary crossentropy and binary accuracy as we now have a binary classification problem
model.compile(loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()])
데이터 증강
데이터 증강 더 알아보러 가기
매우 작은 데이터세트를 다루고 있는 만큼 데이터를 증강이 필수적임
- 기존 이미지를 약간 수정하면 모델이 보다 광범위한 이미지를 확인하고 학습가능
- 단순히 트레이닝하는 사진만 기억하는 대신 Bo의 새로운 사진을 인식하는 법을 학습하는 데 도움이 됨
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# create a data generator
datagen = ImageDataGenerator(
samplewise_center=True, # set each sample mean to 0
rotation_range=10, # randomly rotate images in the range (degrees, 0 to 180)
zoom_range = 0.1, # Randomly zoom image
width_shift_range=0.1, # randomly shift images horizontally (fraction of total width)
height_shift_range=0.1, # randomly shift images vertically (fraction of total height)
horizontal_flip=True, # randomly flip images
vertical_flip=False) # we don't expect Bo to be upside-down so we will not flip vertically
데이터 로드
지금까지 로드한 방식
[준비한 이미지 세트 설명]
각자 이 이미지 세트의 종류를 참고하여 분류하고자하는 동물의 이미지들을 준비해주시면 된다
- Bo인 이미지 를 위한 폴더
- Bo가 아닌 이미지를 위한 폴더
flow_from_directory
이란?
- 모델에 맞게 이미지 크기를 변경(244x244픽셀 및 3채널)
- 폴더에서 바로 이미지를 로드
# load and iterate training dataset
train_it = datagen.flow_from_directory('data/presidential_doggy_door/train/',
target_size=(224, 224),
color_mode='rgb',
class_mode='binary',
batch_size=8)
# load and iterate validation dataset
valid_it = datagen.flow_from_directory('data/presidential_doggy_door/valid/',
target_size=(224, 224),
color_mode='rgb',
class_mode='binary',
batch_size=8)
모델 트레이닝
모델을 트레이닝하고 성능을 확인
steps_per_epoch
의 수를 명확하게 하는 것이 중요
model.fit(train_it, steps_per_epoch=12, validation_data=valid_it, validation_steps=4, epochs=20)
👀 결과 보기
Epoch 1/20
12/12 [==============================] - 5s 428ms/step - loss: 1.1611 - binary_accuracy: 0.7500 - val_loss: 0.5092 - val_binary_accuracy: 0.8000
Epoch 2/20
12/12 [==============================] - 2s 204ms/step - loss: 0.3859 - binary_accuracy: 0.8681 - val_loss: 0.5454 - val_binary_accuracy: 0.7667
Epoch 3/20
12/12 [==============================] - 2s 133ms/step - loss: 0.3611 - binary_accuracy: 0.8791 - val_loss: 0.9003 - val_binary_accuracy: 0.7667
Epoch 4/20
12/12 [==============================] - 2s 153ms/step - loss: 0.2447 - binary_accuracy: 0.9688 - val_loss: 0.3580 - val_binary_accuracy: 0.9000
Epoch 5/20
12/12 [==============================] - 2s 130ms/step - loss: 0.1432 - binary_accuracy: 0.9231 - val_loss: 0.2620 - val_binary_accuracy: 0.9000
Epoch 6/20
12/12 [==============================] - 2s 148ms/step - loss: 0.0607 - binary_accuracy: 0.9890 - val_loss: 0.0933 - val_binary_accuracy: 0.9667
Epoch 7/20
12/12 [==============================] - 2s 153ms/step - loss: 0.0756 - binary_accuracy: 0.9792 - val_loss: 0.2147 - val_binary_accuracy: 0.9333
Epoch 8/20
12/12 [==============================] - 2s 133ms/step - loss: 0.0848 - binary_accuracy: 0.9560 - val_loss: 0.2267 - val_binary_accuracy: 0.9000
Epoch 9/20
12/12 [==============================] - 2s 135ms/step - loss: 0.0758 - binary_accuracy: 0.9780 - val_loss: 0.2275 - val_binary_accuracy: 0.8667
Epoch 10/20
12/12 [==============================] - 2s 138ms/step - loss: 0.0520 - binary_accuracy: 0.9583 - val_loss: 0.0993 - val_binary_accuracy: 0.9333
Epoch 11/20
12/12 [==============================] - 2s 164ms/step - loss: 0.0547 - binary_accuracy: 0.9890 - val_loss: 0.1716 - val_binary_accuracy: 0.9333
Epoch 12/20
12/12 [==============================] - 1s 118ms/step - loss: 0.0045 - binary_accuracy: 1.0000 - val_loss: 0.0145 - val_binary_accuracy: 1.0000
Epoch 13/20
12/12 [==============================] - 2s 149ms/step - loss: 0.0156 - binary_accuracy: 1.0000 - val_loss: 0.0671 - val_binary_accuracy: 0.9667
Epoch 14/20
12/12 [==============================] - 1s 125ms/step - loss: 0.0245 - binary_accuracy: 0.9890 - val_loss: 0.2151 - val_binary_accuracy: 0.9333
Epoch 15/20
12/12 [==============================] - 2s 148ms/step - loss: 0.0092 - binary_accuracy: 1.0000 - val_loss: 0.0128 - val_binary_accuracy: 1.0000
Epoch 16/20
12/12 [==============================] - 2s 137ms/step - loss: 0.0022 - binary_accuracy: 1.0000 - val_loss: 0.0999 - val_binary_accuracy: 0.9667
Epoch 17/20
12/12 [==============================] - 2s 154ms/step - loss: 0.0300 - binary_accuracy: 0.9780 - val_loss: 0.0037 - val_binary_accuracy: 1.0000
Epoch 18/20
12/12 [==============================] - 1s 120ms/step - loss: 6.2258e-04 - binary_accuracy: 1.0000 - val_loss: 0.0674 - val_binary_accuracy: 0.9667
Epoch 19/20
12/12 [==============================] - 2s 151ms/step - loss: 0.0030 - binary_accuracy: 1.0000 - val_loss: 0.0331 - val_binary_accuracy: 1.0000
Epoch 20/20
12/12 [==============================] - 2s 131ms/step - loss: 0.0039 - binary_accuracy: 1.0000 - val_loss: 0.0024 - val_binary_accuracy: 1.0000
<tensorflow.python.keras.callbacks.History at 0x7f2c5802a940>
트레이닝 및 검증 정확도 둘 다 상당히 높다!!
결과 개선
파인 튜닝
파인튜닝: 정교한 파라미터 튜닝
- 기존에 학습이 된 레이어에 내 데이터를 추가로 학습시켜 파라미터를 업데이트 해야 한다.
- 기존에 학습되어져 있는 모델을 기반으로 아키텍쳐를 새로운 목적(나의 이미지 데이터에 맞게)변형하고 이미 학습된 모델 Weights로 부터 학습을 업데이트하는 방법
❔ 그럼 위에 풀링 계층과 마지막 레이어를 추가한 건 파인 튜닝이 아닌가요 ❔
아닙니다! 피쳐를 추출해내는 레이어의 파라미터를 업데이트 하지 않기 때문입니다
➡마지막 레이어를 업데이트 하지 않냐고 생각할 수 있지만 내가 새로 구성한 레이어이기 때문에 업데이트가 아니며 초기 웨이트가 랜덤이기 때문에 정교하지도 않다.
[파인 튜닝 시 나쁜 예]
- 완전히 랜덤한 초기 파라미터를 씀
- 가장 아래쪽의 레이어(일반적인 피쳐를 학습한 덜추상화된 레이어) 의 파라미터를 학습
➡ 오버피팅4 이 일어나거나 전체 파라미터가 망가지는 문제가 생김
모델 파인 튜닝
새 레이어가 트레이닝되었으므로 이제 파인튜닝이라는 마지막 요령을 적용하여 모델을 개선
1️⃣ 전체 모델을 동결 해제
2️⃣ 아주 작은 학습률로 다시 트레이닝
그러면 사전 트레이닝된 기본 레이어가 아주 작은 단계를 취해 약간씩 조정되면서 모델이 조금씩 개선
✔ 동결된 레이어를 포함하는 모델이 완전히 트레이닝된 후에만 수행해야 함
- 모델에 추가한 트레이닝되지 않은 풀링 및 분류 레이어는 임의로 초기화됨
- 이미지를 올바르게 분류하기 위해서는 이 두 레이어에 꽤 많은 업데이트가 필요
역전파 프로세스 과정에서 마지막 레이어의 대규모 초기 업데이트가 사전 트레이닝된 레이어에서도 대규모 업데이트를 초래했을 가능성이 있음
➡ 이러한 업데이트는 중요한 사전 트레이닝된 피처를 손상시켰을 것
➡ 하지만 최종 레이어가 트레이닝되고 수렴된 만큼 모델에 대한 업데이트 전체가 크게 축소되어(특히 매우 작은 학습률로) 이전 레이어의 피처를 손상시키지 않을 것
그럼 이제 사전 트레이닝된 레이어를 동결 해제한 다음 모델을 파인튜닝해보자!!
# Unfreeze the base model
base_model.trainable = True
# It's important to recompile your model after you make any changes
# to the `trainable` attribute of any inner layer, so that your changes
# are taken into account
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate = .00001), # Very low learning rate
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()])
model.fit(train_it, steps_per_epoch=12, validation_data=valid_it, validation_steps=4, epochs=10)
👀 결과 보기
Epoch 1/10
12/12 [==============================] - 8s 652ms/step - loss: 0.0105 - binary_accuracy: 1.0000 - val_loss: 0.0048 - val_binary_accuracy: 1.0000
Epoch 2/10
12/12 [==============================] - 2s 175ms/step - loss: 6.2571e-04 - binary_accuracy: 1.0000 - val_loss: 0.0023 - val_binary_accuracy: 1.0000
Epoch 3/10
12/12 [==============================] - 2s 178ms/step - loss: 1.7461e-04 - binary_accuracy: 1.0000 - val_loss: 1.9970e-04 - val_binary_accuracy: 1.0000
Epoch 4/10
12/12 [==============================] - 2s 163ms/step - loss: 1.6196e-05 - binary_accuracy: 1.0000 - val_loss: 0.0054 - val_binary_accuracy: 1.0000
Epoch 5/10
12/12 [==============================] - 2s 179ms/step - loss: 4.0472e-06 - binary_accuracy: 1.0000 - val_loss: 0.0299 - val_binary_accuracy: 1.0000
Epoch 6/10
12/12 [==============================] - 2s 165ms/step - loss: 1.4624e-04 - binary_accuracy: 1.0000 - val_loss: 0.1470 - val_binary_accuracy: 0.9667
Epoch 7/10
12/12 [==============================] - 2s 179ms/step - loss: 0.0446 - binary_accuracy: 0.9890 - val_loss: 0.0273 - val_binary_accuracy: 1.0000
Epoch 8/10
12/12 [==============================] - 2s 176ms/step - loss: 4.5418e-04 - binary_accuracy: 1.0000 - val_loss: 0.0270 - val_binary_accuracy: 1.0000
Epoch 9/10
12/12 [==============================] - 2s 162ms/step - loss: 3.8445e-06 - binary_accuracy: 1.0000 - val_loss: 0.0107 - val_binary_accuracy: 1.0000
Epoch 10/10
12/12 [==============================] - 2s 180ms/step - loss: 2.2483e-05 - binary_accuracy: 1.0000 - val_loss: 0.0244 - val_binary_accuracy: 1.0000
<tensorflow.python.keras.callbacks.History at 0x7f2bcc6c65c0>
예측 검사
잘 트레이닝된 모델이 준비되었으니 이제 Bo를 위한 개구멍을 만들겠다 지난 개구멍의 경우와 동일한 방식으로 사전 처리하겠다
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from tensorflow.keras.preprocessing import image as image_utils
from tensorflow.keras.applications.imagenet_utils import preprocess_input
def show_image(image_path):
image = mpimg.imread(image_path)
plt.imshow(image)
def make_predictions(image_path):
show_image(image_path)
image = image_utils.load_img(image_path, target_size=(224, 224))
image = image_utils.img_to_array(image)
image = image.reshape(1,224,224,3)
image = preprocess_input(image)
preds = model.predict(image)
return preds
음수: 정답
양수: 오답
Bo 분류기
이제 분류기를 만들어보자
def presidential_doggy_door(image_path):
preds = make_predictions(image_path)
if preds[0] < 0:
print("It's Bo! Let him in!")
else:
print("That's not Bo! Stay out!")
메모리
넘어가기 전에 다음 셀을 실행하여 GPU 메모리를 지우기
이는 다음 노트북으로 넘어가기 위한 필수 작업
import IPython
app = IPython.Application.instance()
app.kernel.do_shutdown(True)