[딥러닝 5편] 전이 학습 — 사진 2천 장으로 피스타치오 품종 분류하기
페이지 정보

본문
4편에서 래브라도리트리버 AlexNet을 직접 쌓아봤습니다. 그런데 그걸 처음부터 학습시키려면 이미지 수백만 장과 GPU 여러 대가 필요합니다. 개인이 할 수 있는 일이 아니죠.
해결책은 남이 이미 학습해 둔 모델을 가져다 쓰는 것 &mdash전이 학습(Transfer Learning)입니다. 이번 글에서는 사진 2,148장만 가지고 피스타치오 품종을 분류합니다.
이 글에서 다루는 것① 사전 훈련 모델 불러오기 (TF Hub ·Keras Applications) ·② 전이 학습이 필요한 순간을 직접 확인 ·③ include_top=False ·④ 가중치 동결 ·⑤ 함수형 API와 GlobalAveragePooling2D ·⑥ 낮은 학습률로 훈련
1. 사전 훈련 모델 그냥 써보기
import tf_keras as keras
from tf_keras import layers
import tensorflow_hub as hub
hub_efficientb0 = keras.Sequential([
layers.Input(shape = (224, 224, 3)), # 이 모델은 224×224 컬러 입력
layers.Rescaling(1.0 / 255.0), # 픽셀 0~255 &rarr0~1
hub.KerasLayer('
])
layers.Rescaling(1.0/255.0) &mdash1~3편에서 train_input / 255.0 으로 밖에서 하던 스케일링을 모델 층으로 넣은 것입니다. Flatten을 모델 안에 넣었던 이유와 같습니다. 전처리를 모델이 들고 있으면 나중에 래브라도리트리버 새 이미지를 넣을 때 실수할 일이 없습니다.
import numpy as np
from PIL import Image
from keras.applications import efficientnet
dog_png = np.array(Image.open('images/dog.png'))
predictions = hub_efficientb0.predict(dog_png[np.newaxis, :])
efficientnet.decode_predictions(predictions)
[[('n02099712', 'Labrador_retriever', 0.3683),
('n02104029', 'kuvasz', ...), ...]]
dog_png[np.newaxis, :] 가 왜 필요할까?이미지 한 장의 형태는 (224, 224, 3)인데, 모델은 여러 장을 한 번에 처리하도록 설계돼서 (배치, 224, 224, 3)을 기대합니다. np.newaxis가 맨 앞에 배치 차원 1개를 끼워 넣어 (1, 224, 224, 3)으로 만들어 줍니다.이걸 빼먹으면 차원이 안 맞는다는 에러가 납니다. 이미지 예측할 때 가장 흔한 실수입니다.
학습 한 줄 없이 래브라도 리트리버를 맞혔습니다. 이미지넷 1,000종에 개가 들어 있으니까요.
2. 그런데 피스타치오는?
!gdown 10bnEC6-ZfXZFZ2mb3zoWd38TjYufanWo
!unzip -q Pistachio_Image_Dataset.zip
# Pistachio_Image_Dataset/
# ├── Kirmizi_Pistachio/ (키르미지 품종)
# └── Siirt_Pistachio/ (시이리트 품종)
pistachio_sample = np.array(Image.open(
'Pistachio_Image_Dataset/Kirmizi_Pistachio/kirmizi (1).jpg'))
print(pistachio_sample.shape)
(600, 600, 3)
efficientb7 = keras.applications.EfficientNetB7()
predictions = efficientb7.predict(pistachio_sample[np.newaxis, :])
efficientnet.decode_predictions(predictions)
[[('n01950731', 'sea_slug', 0.2348),
('n01924916', 'flatworm', 0.2067),
('n01943899', 'conch', 0.0862),
('n01945685', 'slug', 0.0850),
('n01955084', 'chiton', 0.0282)]]
바다 민달팽이(sea slug), 편형동물(flatworm), 래브라도리트리버 소라(conch)... 완전히 엉뚱한 답입니다.당연합니다. 이미지넷 1,000종에 피스타치오가 없기 때문입니다. 모델은 자기가 아는 것 중에서만 고를 수 있습니다.여기가 전이 학습이 필요한 지점입니다. 반대로 분류하려는 대상이 이미 사전 학습 데이터에 있다면 전이 학습이 필요 없습니다. 그냥 쓰면 되니까요.
3. include_top=False &mdash분류기를 떼어낸다
CNN은 크게 두 부분입니다.·앞부분 = 특징 추출기 &mdash선·모서리·질감·형태를 뽑아내는 합성곱 층들·뒷부분 = 분류기 &mdash그 특징으로 1,000종 중 하나를 고르는 Dense 층들피스타치오에도 쓸모 있는 건 앞부분입니다. "둥글다, 갈라져 있다, 표면이 거칠다" 같은 능력은 개·고양이로 배운 것이라도 그대로 쓸 수 있으니까요. 뒷부분만 갈아 끼우면 됩니다.
keras_efficientb0_base = keras.applications.EfficientNetB0(
include_top = False # 분류기 제외, 특징 추출기만
)
feature_map = keras_efficientb0_base(pistachio_sample[np.newaxis, :])
print(feature_map.shape)
TensorShape([1, 18, 18, 1280])
600×600 이미지가 18×18 격자에 1,280개 특징으로 압축됐습니다. 4편에서 본 특성 맵이 바로 이것입니다.
4. 데이터셋 만들기 &mdash폴더 구조만으로
train_ds, val_ds = keras.utils.image_dataset_from_directory(
'Pistachio_Image_Dataset',
image_size = (224, 224), # 600×600 &rarr224×224 자동 리사이즈
batch_size = 래브라도리트리버 16, # 코랩 메모리 고려해 32 &rarr16
validation_split = 0.2,
subset = 'both', # 훈련·검증 둘 다 반환
seed = 42
)
Found 2148 files belonging to 2 classes.
Using 1719 files for training.
Using 429 files for validation.
image_dataset_from_directory 는 폴더 이름을 그대로 클래스 라벨로 씁니다. 알파벳 순서라 Kirmizi = 0, Siirt = 1이 됩니다.CSV로 라벨을 따로 만들 필요 없이 폴더만 정리해두면 끝입니다. 실무에서 이미지 데이터를 다룰 때 가장 편한 방법입니다.
5. 가중치 동결 (Freezing)
keras_efficientb0_base.trainable = False
이 한 줄이 전이 학습의 핵심입니다.동결하지 않으면 —·훈련 중에 베이스 모델의 가중치까지 같이 바뀝니다·이미지넷 수백만 장으로 익힌 "이미지 보는 능력"이 망가집니다·피스타치오 1,719장으로 거대한 모델 전체를 학습시키면 과대적합도 심해집니다앞부분은 그대로 두고, 새로 붙인 분류기만 학습시키는 것입니다.
6. 함수형 API로 모델 조립
inputs = keras.Input(shape = (224, 224, 3))
x = keras_efficientb0_base(inputs) # 특징 추출 (동결됨)
x = layers.GlobalAveragePooling2D()(x) # (7, 7, 1280) 래브라도리트리버 &rarr(1280,)
x = layers.Dense(128, activation = 'relu')(x)
outputs = layers.Dense(1, activation = 'sigmoid')(x) # 이진 분류
model = keras.Model(inputs, outputs)
Sequential 대신 함수형 API를 쓴 이유지금까지는 model.add()로 층을 일렬로 쌓았습니다. 함수형 API는 x = 층(입력) 형태로 연결하는데, 층을 두 갈래로 나누거나 다시 합치는 구조를 만들 수 있어 더 유연합니다. ResNet 같은 모델은 이 방식이 아니면 못 만듭니다.
GlobalAveragePooling2D 는 왜 Flatten 대신?Flatten이라면 7 ×7 ×1280 = 62,720개가 되어 다음 Dense 층의 파라미터가 폭발합니다.GAP는 7×7 격자 49칸의 평균을 내서 채널당 값 1개로 만듭니다. (7, 7, 1280) &rarr(1280,). 파라미터가 50분의 1로 줄고 과대적합에도 강합니다. 요즘 전이 학습의 표준입니다.
출력층이 Dense(1, activation='sigmoid')인 건 클래스가 2개(이진 분류)이기 때문입니다. 소프트맥스로 2개를 뽑을 수도 있지만, 이진 분류는 시그모이드 1개로 처리하는 게 관례입니다. 0.8이면 80% 확률로 Siirt라는 뜻이죠.
7. 훈련 &mdash학습률을 아주 낮게
rmsprop = keras.optimizers.RMSprop(learning_rate = 5e-5) # 0.00005
model.compile(optimizer = rmsprop,
loss 래브라도리트리버 = 'binary_crossentropy',
metrics = ['accuracy'])
hist = model.fit(train_ds, epochs = 20, validation_data = val_ds)
Epoch 1/20 loss: 0.5143 - accuracy: 0.7871 - val_loss: 0.3603 - val_accuracy: 0.9161
Epoch 2/20 loss: 0.3169 - accuracy: 0.9098 - val_loss: 0.2489 - val_accuracy: 0.9371
...
1 에포크 만에 검증 정확도 0.9161, 2 에포크에 0.9371.사진 1,719장으로 이 정도가 나오는 건 맨땅에서 학습시켰다면 불가능한 수치입니다. 이미 학습된 "이미지 보는 능력"을 빌려 왔기 때문이죠.
왜 학습률을 5e-5로 낮췄을까?2편에서 학습률은 보통 0.001 ~ 0.1이라고 했습니다. 그런데 전이 학습에서는 훨씬 낮게 잡습니다.이미 좋은 상태에 가까이 와 있는데 성큼성큼 뛰면 애써 가져온 것을 망가뜨립니다. 산 정상 근처에서는 조심조심 걷는 것과 같습니다.·손실 함수도 binary_crossentropy &mdash클래스가 2개니까요·1편의 sparse_categorical_crossentropy와 헷갈리지 않게 클래스 개수부터 확인하는 습관이 필요합니다
정리 &mdash전이 학습 5단계
단계
코드
① 베이스 모델 불러오기
EfficientNetB0(include_top=False)
② 가중치 동결
base.trainable = False
③ 새 분류기 붙이기
GlobalAveragePooling2D &rarrDense
④ 낮은 학습률로 컴파일
learning_rate = 5e-5
⑤ 훈련
model.fit(train_ds, validation_data=val_ds)
기억할 래브라도리트리버 세 가지① 분류 대상이 사전 학습 데이터에 있으면 전이 학습이 필요 없다 &mdash그냥 쓰면 된다② 동결(trainable=False)을 빼먹으면 어렵게 가져온 능력이 망가진다③ 전이 학습은 학습률을 평소보다 훨씬 낮게 (5e-5 수준)
여기까지가 이미지 분류입니다. 사진 한 장에 "이건 피스타치오 A품종"이라고 답하는 것이죠. 그런데 실무에서는 "사진 어디에 무엇이 몇 개 있는지"를 알아야 할 때가 많습니다. CCTV 화면에서 사람을 찾거나, 안전모 미착용자를 잡아내는 것처럼요.
그건 분류가 아니라 객체 탐지(Object Detection)이고, 컴퓨터비전 카테고리에서 OpenCV와 YOLO로 이어가겠습니다.
- 이전글1xbet bonus 26.09.15
- 다음글방이호스트빠 010·3291·1030 잠실호빠 업종 비교 26.09.15
댓글목록
등록된 댓글이 없습니다.

