Pytorch #6 : 전이학습과 미세조정

2025. 3. 8. 00:51·Bootcamp_zerobase/Pytorch

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
'Bootcamp_zerobase/Pytorch' 카테고리의 다른 글
  • Pytorch #2 : 파이토치 MNIST
  • Pytorch #1 : 기초 알아보기
  • Pytorch #7 : 전이학습과 미세조정
  • Pytorch #5 : 전이학습과 Pretrained Model
Loft_mind
Loft_mind
건축 전공자의 전공 갈아타기
  • Loft_mind
    오늘의 설계
    Loft_mind
  • 공지사항

    • 분류 전체보기 (41)
      • Bootcamp_zerobase (40)
        • Pytorch (12)
        • Image Augmentation (2)
        • YOLO & RNN (4)
        • Git & GitHub (2)
        • Tensorflow (11)
        • OpenCV (9)
      • Architecture (0)
  • 블로그 메뉴

    • 홈
    • 태그
  • 태그

    rnn
    컴퓨터 비전
    ComputerVision
    PIL
    deeplearning
    정규표현식
    ResNet
    tensorflow
    subclass
    RE
    CNN
    컴퓨터비전
    제로베이스
    YOLO
    opencv
    image augmentation
    mnist
    github
    git
    bash
    zerobase
    zerobasel
    VGGNET
    autoencoders
    역전파
    pytorch
  • hELLO· Designed By정상우.v4.10.3
Loft_mind
Pytorch #6 : 전이학습과 미세조정
상단으로

티스토리툴바