2025.03.07
Chapter 6. Transfer Learning
- 47. 전이학습과 미세조정 - 전이학습
전이학습
Tensorflow_datasets : tfds
오늘의 학습코드는 TensorFlow 공식 페이제의 코드입니다.
기초 패키지, 라이브러리 import 진행
from __future__ import absolute_import, division, print_function, unicode_literals
import os
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
keras = tf.keras
import tensorflow_datasets as tfds
tfds.disable_progress_bar()
# ensorflow_datasets(TFDS)에서 데이터를 다운로드할 때 진행 표시줄을 비활성화하는 함수입니다.
다만, 여기서 tensorflow, tensorflow_datasets, tensorboard, protobuf 등 서로 충돌나고 version이 맞지 않아서...
난리도 아니였다.
현재 tesnorflow는 2.9.0 이며 tensorflow_datasets는 4.0, protobuf 는 3.20.3 이다. 당초에 tensorflow_datasets 와protobuf 가 충돌나서 모두 재설치하는 과정에서 서로 자동으로 호환되는 version이 맞추어졌다.
패키지에서 데이터 Load
# cats and dogs 데이터 load
# metadata 변수에는 클래스 개수, 특징 등 메타데이터 할당
# as_supervised=True : 데이터를 (image, label) tuple 형태로 반환하도록 설정.
# as_supervised=False : 데이터가 딕셔너리 형태 ({'image': img, 'label': lbl})로 반환됨.
(raw_train, raw_validation, raw_test), metadata = tfds.load(
'cats_vs_dogs',
split=['train[:80%]', 'train[80%:90%]', 'train[90%:]'],
with_info=True,
as_supervised=True
이제 데이터셋이 load된 줄 알고, raw_train을 호출하니...PrefetchDataset 이라는게 등장한다
raw_train
>> <PrefetchDataset element_spec=(TensorSpec(shape=(None, None, 3), dtype=tf.uint8, name=None), TensorSpec(shape=(), dtype=tf.int64, name=None))>
type(raw_train)
>> type(raw_train)
Prefetch Dataset
PrefetchDataset 이란 ?
TensorFlow의 tf.data API에서 사용되는 데이터셋의 한 유형, 데이터를 미리 로드하여 훈련 속도를 높이는 역할.
즉, 모델이 연산하는 동안 다음 데이터를 미리 준비해서 CPU와 GPU를 동시에 활용할 수 있도록 합니다.
Prefetch( ) 사용 예제
import tensorflow as tf
# 예제 데이터셋 생성
dataset = tf.data.Dataset.range(10) # 0부터 9까지 데이터
dataset = dataset.map(lambda x: x * 2) # 각 데이터를 2배로 변환
# Prefetch 적용 (최대 2개의 데이터 미리 로드)
dataset = dataset.prefetch(buffer_size=2)
# 데이터 출력
for data in dataset:
print(data.numpy())
- PrefetchDataset은 dataset.prefetch(buffer_size)를 호출하면 생성됩니다.
- buffer_size 는 미리 load할 데이터의 개수를 의미
- dataset = dataset.prefetch(buffer_size = tf.data.AUTOTUNE)
- 가장 추천 : buffer_size = tf.data.AUTOTUNE ▶ TensorFlow가 자동으로 최적의 값을 설정
사용하는 큰 이유는, 모델이 GPU에서 훈련하는 동안 CPU가 다음 배치를 미리 준비하여 연산 속도가 빨라진다.
load한 데이터 확인
for image, label in raw_train.take(1):
plt.imshow(image)
plt.title(f'label : {label}')
plt.axis(False)
plt.show()
메타데이터 (metadata)에서 라벨이름 가져와서 확인진행
일단 메타데이터에는 아래와 같은 내용이 있다.
print(metadata)
-----------------------------------------------------------------------------------------------------------------------------
tfds.core.DatasetInfo(
name='cats_vs_dogs',
full_name='cats_vs_dogs/4.0.0',
description="""
A large set of images of cats and dogs. There are 1738 corrupted images that are dropped.
""",
homepage='https://www.microsoft.com/en-us/download/details.aspx?id=54765',
data_path='~\\tensorflow_datasets\\cats_vs_dogs\\4.0.0',
file_format=tfrecord,
download_size=786.67 MiB,
dataset_size=689.64 MiB,
features=FeaturesDict({
'image': Image(shape=(None, None, 3), dtype=tf.uint8),
'image/filename': Text(shape=(), dtype=tf.string),
'label': ClassLabel(shape=(), dtype=tf.int64, num_classes=2),
}),
supervised_keys=('image', 'label'),
disable_shuffling=False,
splits={
'train': <SplitInfo num_examples=23262, num_shards=8>,
},
citation="""@Inproceedings (Conference){asirra-a-captcha-that-exploits-interest-aligned-manual-image-categorization,
author = {Elson, Jeremy and Douceur, John (JD) and Howell, Jon and Saul, Jared},
title = {Asirra: A CAPTCHA that Exploits Interest-Aligned Manual Image Categorization},
booktitle = {Proceedings of 14th ACM Conference on Computer and Communications Security (CCS)},
year = {2007},
month = {October},
publisher = {Association for Computing Machinery, Inc.},
url = {https://www.microsoft.com/en-us/research/publication/asirra-a-captcha-that-exploits-interest-aligned-manual-image-categorization/},
edition = {Proceedings of 14th ACM Conference on Computer and Communications Security (CCS)},
}""",
)
여기서 metadata.features['label'] 를 입력하면 ClassLabel 이라는 Type으로 나온다.
ClassLabel Type 은 아래의 속성을 통해 정보확인이 가능하다.
속성 | 설명 |
.num_classes | 클래스 개수 |
.names | 클래스 이름 목록 |
.dtype | 데이터 타입 (tf.int64) |
int2str(class_index) | 라벨(숫자)를 클래스명으로 변환 반대도 가능 (str2int) |
metadata.features['label']
>> ClassLabel(shape=(), dtype=tf.int64, num_classes=2)
metadata.features['label'].num_classes
>> 2
metadata.features['label'].names
>> ['cat', 'dog']
metadata.features['label'].dtype
>> tf.int64
metadata.features['label'].int2str(1)
>> 'dog'
Subplot 활용해서 9개 사진 확인해보면....
get_label_name = metadata.features['label'].int2str
plt.figure(figsize=(6,6))
i = 0
for image, label in raw_train.take(9):
plt.subplot(3, 3, i+1)
plt.imshow(image)
plt.axis('off')
plt.title(get_label_name(label))
i += 1
plt.tight_layout()
plt.show()
여기서 raw_train.take(9) 라고 하면, 9개의 데이터를 가져오는 것이다. index가 9인 데이터 하나를 가져오는것이 아니다.
이미지 데이터 전처리
기존 저장되어 있는 데이터의 Shape 확인하고, 적절하게 변경한다
n = 1
for img, label in raw_train.take(10):
print(f'img.shape : {img.shape} , n : {n}')
n += 1
-----------------------------------------------------------------------------
img.shape : (262, 350, 3) , n : 1
img.shape : (409, 336, 3) , n : 2
img.shape : (493, 500, 3) , n : 3
img.shape : (375, 500, 3) , n : 4
img.shape : (240, 320, 3) , n : 5
img.shape : (100, 100, 3) , n : 6
img.shape : (216, 182, 3) , n : 7
img.shape : (375, 500, 3) , n : 8
img.shape : (377, 500, 3) , n : 9
img.shape : (500, 375, 3) , n : 10
IMG_SIZE = 160 # 모든 이미지미지 160X160
def format_example(image, label):
image = tf.cast(image, tf.float32)
# image 데이터 tf.uint8 → tf.float32 타입변경
# uint8은 0~255 형식
# 딥러닝 모델에서는 부동소수점 연산 따라서 float 변경 필요요
image = (image/127.5) - 1
# 모든 RGB 값을 -1 ~ 1 사이로 변경
# 센터를 0으로 Scaling
image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
return image, label
map 합수를 이용해서 적용
train = raw_train.map(format_example)
validation = raw_validation.map(format_example)
test = raw_test.map(format_example)
n = 1
for img, label in train.take(10):
print(f'img.shape : {img.shape} , n : {n}')
n += 1
----------------------------------------------------------------------
img.shape : (160, 160, 3) , n : 1
img.shape : (160, 160, 3) , n : 2
img.shape : (160, 160, 3) , n : 3
img.shape : (160, 160, 3) , n : 4
img.shape : (160, 160, 3) , n : 5
img.shape : (160, 160, 3) , n : 6
img.shape : (160, 160, 3) , n : 7
img.shape : (160, 160, 3) , n : 8
img.shape : (160, 160, 3) , n : 9
img.shape : (160, 160, 3) , n : 10
map 함수
map( ) 함수는 데이터셋을 변환
각 데이터셋 항목에 전처리를 효율적으로 적용하고, 메모리와 속도 면에서 최적화할 수 있습니다.
dataset = dataset.map(function)
여기서 dataset은 TensorFlow 데이터셋 객체이고, function은 각 항목에 적용할 변환 함수입니다. dataset.map(function)은 데이터셋의 각 요소(이미지와 레이블)에 function을 적용하여 변환된 새로운 데이터셋을 반환.
batch_size 적용하고 shuffle
BATCH_SIZE = 32
SHUFFLE_BUFFER_SIZE = 1000
train_batches = train.shuffle(SHUFFLE_BUFFER_SIZE).batch(BATCH_SIZE)
validation_batches = validation.batch(BATCH_SIZE)
test_batches = test.batch(BATCH_SIZE)
for image_batch, label_batch in train_batches.take(1):
pass
image_batch.shape
>> TensorShape([32, 160, 160, 3])
batch-size를 32로 지정했기때문에 첫번째 채널(차원)에 32가 생성되었다.
MobileNet V2 모델
Base_Model 불러오기 (Pretrained Model)
1. 이 모델은 1.4M 이미지와 1000개의 클래스로 구성된 대규모 데이터셋인 ImageNet 데이터셋을 사용해 사전 훈련 (Pretrained) 된 모델
2. 기능추출에 사용할 MobileNet V2 층 선택
3. flatten 연산을 하기 전에 맨 아래 층(layer)을 가지고 진행 (병목층)
4. include_top = False → 지정하면 맨 위에 분류층(출력층)이 포함되지 않은 네트워크를 Load하므로 특징 추출에 이상적
→ Pretrained 된 모델의 클래스 숫자와 내가 학습할 데이터셋의 클래스 숫자가 다르기에 ..
# MobileNet V2 Load (Pretrained Model)
base_model = tf.keras.applications.MobileNetV2(
input_shape = (160, 160, 3),
include_top = False,
weights = 'imagenet' # 어떤 데이터로 학습된 모델이냐 지정정
)
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_160_no_top.h5
9406464/9406464 [==============================] - 1s 0us/step
Model - Shape 확인
# 샘플로 들고온 하나의 batch -> train_batch.take(1)
# image_batch는 32개 데이터 있음.
feature_batch = base_model(image_batch)
print(feature_batch.shape)
-------------------------------------------------------
(32, 5, 5, 1280)
기존에는 TensorShape([32, 160, 160, 3]) Shape이 특징 추출기 통과를 통해 5 X 5 X 1280 개의 특징 블록으로 변환.
※ 주 의 ※
- tensorFlow는 채널이 마지막에 있고, pytorch는 채널이 제일 앞에 있다. 항상 채널의 위치를 염두하여야 한다
base_model의 trainable = False
기존 summary( )
base_model.summary()
--------------------------------------------------------------------------------------------------
Model: "mobilenetv2_1.00_160"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) [(None, 160, 160, 3 0 []
)]
Conv1 (Conv2D) (None, 80, 80, 32) 864 ['input_1[0][0]']
.
.
.
==================================================================================================
Total params: 2,257,984
Trainable params: 2,223,872
Non-trainable params: 34,112
trainable = False → 가중치를 건드리지 않겠다는 뜻.
base_model.trainable = False
base_model.summary()
--------------------------------------------------------------------------------------------------
Model: "mobilenetv2_1.00_160"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) [(None, 160, 160, 3 0 []
)]
Conv1 (Conv2D) (None, 80, 80, 32) 864 ['input_1[0][0]']
.
.
.
.
==================================================================================================
Total params: 2,257,984
Trainable params: 0
Non-trainable params: 2,257,984
Global Average Pooling layer
GAP (Global Average Pooling Layer) : 각 채널의 평균값 추출
이전에 많이 사용했던 layer는 FC Layer (Fully Connected layer)로 feature map이 dense(Flatten)하게 쫙 펴지기 때문에 연산량이 매우 높고 파라미터가 많이 필요하고 또, 오버피팅 위험이 있다. 또한 FC layer는 필수적으로 입력 이미지 사이즈를 주어야하기 때문에 그에 맞춰서 사이즈를 고정해야한다.
이 단점을 고려하여 적용하는 Layer가 GAP 이다.
각 feature map에 있는 평균을 추출한다. 이 방식은 85%의 가중치를 없애므로 계산량과 모델의 무게가 줄어들고, 파라미터가 줄어드니까 덩달아 오버피팅 방지 효과도 따라옵니다. 그리고 이 방법이 정보량 손실이 많이 될 것 같고, 실제로도 그렇습니다. 이미지의 모든 값을 평균내버리는 건데, 정보를 잃지 않을 수 없습니다. 그러나 그럼에도 불구하고 성능이 좋다고 합니다.
GAP vs FC Layer 설명 참조
https://mole-starseeker.tistory.com/66
GAP (Global Average Pooling) : 전역 평균 풀링
위 그림처럼 Fully-Connected Layer(전결합층) 직전의 컨볼루션 레이어에서 채널 수, 즉 feature map의 수가 6개라고 합시다. 기존 FC Layer를 사용한 분류에서는 Flatten Layer를 이용해서 입력받은 값을 굉장히
mole-starseeker.tistory.com
global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
feature_batch_average = global_average_layer(feature_batch)
print(feature_batch_average.shape)
-------------------------------------------------------------------------
>> (32, 1280)
기존의 feature_batch는 (32, 5, 5, 1280) 이다. GAP를 통과하여, 5X5 이미지(채널) 한개 당 평균값 하나씩 추출.
총 1280개의 요소벡터로 변환된다.
Dense층 이용 단일예측
# Dense층을 사용하여 특징을 이미지당 단일 예측
prediction_layer = keras.layers.Dense(1)
prediction_batch = prediction_layer(feature_batch_average)
print(prediction_batch.shape)
--------------------------------------------------------------------------------
>> (32, 1)
prediction_batch
--------------------------------------------------------------------------------------
<tf.Tensor: shape=(32, 1), dtype=float32, numpy=
array([[ 0.40041605],
[-0.94317234],
[ 0.1729971 ],
[ 1.7414391 ],
[-0.4269511 ],
[-0.33442786],
[ 1.4650284 ],
[-0.08414298],
[ 1.6187767 ],
[-0.6071375 ],
.
.
.
.
[ 1.4417546 ],
[-0.11055341],
[-0.0496968 ],
[-0.3340631 ]], dtype=float32)>
여기서 양수는 클래스 1 (dog) 을 예측하고, 음수는 클래스 0 (cat) 을 예측.
모델 학습 진행
## 전체 모델 구성
model = tf.keras.Sequential([
base_model, # base Model
global_average_layer, # 마지막 layer에서 FC 대신 GAP 적용
prediction_layer # 예측
])
모델 컴파일 진행
base_learning_rate = 0.001
model.compile(
optimizer = tf.keras.optimizers.RMSprop(lr=base_learning_rate),
loss = tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics = ['accuracy']
)
model.summary()
--------------------------------------------------------------------------------------------
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
mobilenetv2_1.00_160 (Funct (None, 5, 5, 1280) 2257984
ional)
global_average_pooling2d (G (None, 1280) 0
lobalAveragePooling2D)
dense (Dense) (None, 1) 1281
=================================================================
Total params: 2,259,265
Trainable params: 1,281
Non-trainable params: 2,257,984
_________________________________________________________________
학습가능한 파라미터는 1,281개 이다. 기존 base_model에서는 학습 불가능하고, GAP, DENSE 에서만 가능하다
아무 학습을 하지 않은 현재 성능은?? (기존 base_model의 wts, bias 적용되겠지 뭐..)
## 학습하지 않은 모델의 성능
initial_epochs = 10
validation_steps = 20
loss0, accuracy0 = model.evaluate(validation_batches, steps = validation_steps)
---------------------------------------------------------------------------------------
20/20 [==============================] - 2s 17ms/step - loss: 0.6405 - accuracy: 0.6141
FIT (학습)
학습가능한 wts (파라미터)가 1280개 정도지만, 연산은 전체를 다해야하기 때문에 결코 짧은 시간은 아니나, MobileNetV2를 전체 학습하는 것 대비 훨씬 짧은 시간임.
history = model.fit(train_batches,
epochs = initial_epochs,
validation_data = validation_batches)
-------------------------------------------------------------
Epoch 10/10
582/582 [==============================] - 40s 69ms/step
- loss: 0.0270
- accuracy: 0.9910
- val_loss: 0.0473
- val_accuracy: 0.9837
뭐 대충 학습된 모델에 대해서.. 사진몇개 넣어보면..
# 사진저장된 폴더경로 및 파일이름
image_dir_path = './data/dog_and_cat/'
image_path = os.listdir(image_dir_path)
# 사진 Load 함수 만들기
def load_and_preprocess_iamge(image_path, image_dir_path):
img_path = os.path.join(image_dir_path, image_path)
image = tf.io.read_file(img_path)
image = tf.image.decode_jpeg(image, channels=3)
image = tf.image.resize(image, (160, 160))
image = tf.cast(image, tf.float32)
image = (image/127.5) - 1
return image
# 예측진행
images = [load_and_preprocess_iamge(img, image_dir_path) for img in image_path]
images_batch = tf.stack(images)
predictions = model.predict(images_batch)
print(predictions)
# 사진확인 및 결과확인
image_dir_path = './data/dog_and_cat/'
image_path = os.listdir(image_dir_path)
plt.figure(figsize=(6,6))
i = 0
for i, jpeg_name in enumerate(image_path):
img = load_and_preprocess_iamge(jpeg_name, image_dir_path)
plt.subplot(3, 2, i+1)
plt.imshow(img.numpy())
plt.axis('off')
plt.title(f'{jpeg_name}, {predictions[i]}')
i += 1
plt.tight_layout()
plt.show()
음수이면 cat으로 예측한 것이며, 양수이면 dog로 예측한 건데.. 고양이에서 1개, 강아지에서 1개씩 정도는 틀린 것을 알 수 있다. 너무 어려운 사진을 사용해서 그럴수도 있을거라 예측된다....
'Bootcamp_zerobase > Pytorch' 카테고리의 다른 글
Pytorch #3 : 파이토치 MNIST 실습 (0) | 2025.04.26 |
---|---|
Pytorch #2 : 파이토치 MNIST (1) | 2025.04.26 |
Pytorch #1 : 기초 알아보기 (0) | 2025.04.22 |
Pytorch #7 : 전이학습과 미세조정 (0) | 2025.03.08 |
Pytorch #5 : 전이학습과 Pretrained Model (0) | 2025.03.06 |