AI 개발 공부 공간

AI, 머신러닝, 딥러닝, Python & PyTorch, 실전 프로젝트

딥러닝/딥러닝: 기초 개념

전이 학습(Transfer Learning) 기초

qordnswnd123 2025. 6. 9. 16:35

1. 전이 학습이란?

전이 학습(Transfer Learning)은 이전에 학습된 모델을 새로운 문제에 적용하는 학습 방법입니다. 이는 이미 학습된 모델을 사용 하여 유사한 문제에 적용하는 방식으로, 한 작업에서 얻은 학습 내용을 다른 작업에 전이함으로써 새로운 작업에서의 성능을 향상 시키는 것을 목표로 합니다.
사전학습된 모델은 대규모 데이터셋(예: ImageNet)에서 학습된 가중치를 갖고 있으며, 이를 그대로 사용하거나, 새로운 데이터셋에 맞춰 미세 조정(fine-tuning)하여 다시 학습할 수 있습니다. 이때, 모델의 가중치를 초기화하지 않고 유지하면서, 새로운 데이터셋에 맞는 마지막 레이어만 학습하거나 일부 레이어만 재학습합니다.
전이 학습은 주로 컴퓨터 비전과 자연어 처리 같은 대규모 데이터와 계산 자원이 필요한 분야에서 널리 사용됩니다.


1.1 처음부터 학습하기와 전이학습의 예시

※ 처음부터 학습하기의 예: 라자냐 만들기
요리에 대한 경험이 전혀 없는 상태에서 처음으로 라자냐를 만드는 상황을 생각해 봅시다. 요리를 전혀 해본 적이 없다고 가정하면, 라자냐의 기본 개념부터 시작해야겠죠. 재료를 어떻게 준비하고, 어떤 순서로 조합해야 하는지, 그리고 오븐을 어떻 게 사용하는지 등 모든 단계를 처음부터 배워야 합니다.
1) 재료 식별: 먼저, 라자냐를 만드는 데 필요한 모든 재료(면, 소스, 치즈 등)를 알아야 합니다.
2) 기술 습득 층을 쌓는 방법, 소스를 만드는 방법, 치즈를 올바르게 배치하는 방법 등을 배워야 합니다.
3) 오븐 사용법: 올바른 온도 설정과 요리 시간을 파악해야 합니다.

이 모든 것을 처음부터 배우고 익혀야 하므로, 과정이 느리고 어려울 수 있습니다. 하지만, 이런 방식으로 라자냐 만들기를 배움으로써 기본적인 요리 능력과 함께 특정 요리에 대한 깊은 이해를 얻게 됩니다.
이렇게 처음부터 라자냐를 배우는 것은 무작위 초기 가중치로 시작하여 데이터를 통해 처음부터 모든 패턴과 특징을 학습하는 신경망과 비슷합니다.

 

※ 전이 학습의 예: 요리하기
이번에는 이미 다양한 유형의 파스타를 만드는 법을 배웠다고 합시다. 이제 새로운 요리, 라자냐를 만들어야 한다고 가정해 보겠습니다. 파스타 요리의 기본 개념(예: 소스 만들기, 면 삶기)을 이미 알고 있기 때문에, 라자냐를 만드는데 이 기존 지식을 활용할 수 있습니다. 여기서 사전 학습된 지식(파스타 요리)은 라자냐라는 새로운 요리에 적용되며, 단지 몇 가지 새로운 스킬(예: 치즈층 만들기, 오븐 사용법)만 추가로 배우면 됩니다. 이것이 전이 학습의 원리입니다.

 

2.전통적인 머신러닝(Traditional ML) vs. 전이 학습(Transfer Learning)

 

 

2.1 Traditional ML (전통적인 머신러닝)

전통적인 머신러닝에서는 각 작업이 고립되어 개별적으로 모델을 구축하고, 학습합니다. 이는 전통적인 머신러닝 알고리즘이 학습 데이터와 테스트 데이터가 동일한 특징 공간에서 나온다고 가정하기 때문입니다.
하지만 데이터 분포가 변하거나, 학습된 모델을 새로운 데이터셋에 적용할 때는, 유사한 작업을 시도하더라도 새로운 모델을 처음 부터 다시 학습시켜야 합니다.

  • 데이터셋 1→ 학습 1: 첫 번째 데이터셋을 사용하여 특정 작업(학습 1)을 학습합니다.
  • 데이터셋 2→ 학습 2: 다른 데이터셋을 사용하여 별개의 두 번째 작업(학습 2)을 학습합니다.

이 방식에서는 첫 번째 작업(학습 1)에서 학습한 내용을 두 번째 작업(학습 2)에 활용하지 않기 때문에, 각 작업마다 새로운 학습이 필요합니다. 따라서 학습 속도가 느리고, 많은 데이터와 시간이 필요할 수 있습니다.


2.2.Transfer Learning (전이 학습)

반면, 전이 학습에서는 이전에 학습된 작업의 지식을 활용하여 새로운 작업을 학습합니다. 이 방식은 학습 과정을 더 빠르고, 더 정확하게 하거나, 적은 훈련 데이터로도 학습이 가능하게 만듭니다.

  • 데이터셋 1→ 학습 1: 첫 번째 데이터셋을 사용하여 작업(학습 1)을 학습합니다.
  • 지식 전이 첫 번째 작업에서 학습된 지식을 다음 작업에 전이합니다.
  • 데이터셋 2→ 학습 2: 이전 작업에서 전이된 지식을 활용하여 두 번째 작업(학습 2)을 학습합니다.

이 접근 방식에서는 이전 작업에서 얻은 지식을 사용하여 새로운 작업을 학습하므로, 학습 효율성이 크게 향상될 수 있습니다. 이는 특히 데이터가 적거나 학습 시간이 중요한 상황에서 매우 유용하겠죠.


즉, 전통적인 머신 러닝은 각 작업을 독립적으로 학습하며, 이전 작업의 지식이 다음 작업에 사용되지 않습니다.
전이 학습은 이전 작업에서 얻은 지식을 바탕으로 새로운 작업을 학습하여, 더 효율적이고 빠른 학습이 가능합니다.

 

3. 왜 전이 학습을 사용하는가?

  • 계산 비용 절감: 전이 학습은 새로운 문제를 해결하기 위한 모델 구축에 필요한 계산 비용을 줄여줍니다. 사전 학습된 모델을 재사용하여 다른 작업에 적용함으로써, 모델 훈련 시간, 훈련 데이터, 프로세서 자원 등을 줄일 수 있습니다. 예를 들어, 필요한 학습률에 도달하기 위해 데이터셋을 반복하는 횟수(에포크 수)가 줄어들 수 있습니다. 이처럼 전이 학습은 모델 훈련 과정을 가속화하고 단순화할 수 있습니다.
  • 데이터셋 크기: 전이 학습은 특히 대규모 데이터셋을 확보하는 데 어려움을 겪을 때 유용합니다. 대형 언어 모델(LLM)과 같은 모델은 최적의 성능을 얻기 위해 대량의 훈련 데이터가 필요합니다. 하지만 공개된 양질의 데이터셋은 제한적일 수 있으며, 수 작업으로 레이블링된 데이터를 충분히 생산하는 것은 시간과 비용이 많이 듭니다.
  • 일반화 가능성: 전이 학습은 모델 최적화를 돕는 동시에, 모델의 일반화 성능을 향상시킬 수 있습니다. 전이 학습은 기존 모델을 새로운 데이터셋으로 재학습하기 때문에, 여러 데이터셋에서 얻은 지식을 포함하게 됩니다. 이는 초기 모델보다 더 다양한 데이터에서 더 나은 성능을 발휘할 가능성이 있으며, 과적합을 방지하는 데 도움이 될 수 있습니다.

물론, 한 도메인에서 다른 도메인으로의 지식 전이가 낮은 품질의 데이터를 보완할 수는 없습니다. 따라서 전이 학습을 사용할 때에도 데이터 전처리 및 특징 공학, 예를 들어 데이터 증강과 특징 추출과 같은 작업이 여전히 필요합니다.


3.1 전이 학습이 적합한 상황

전이 학습은 다음 세 가지 조건이 충족될 때 최적의 성능을 발휘합니다.

  • 두 학습 작업이 유사할 때
  • 원본 데이터셋과 목표 데이터셋의 데이터 분포가 크게 다르지 않을 때
  • 두 작업 모두에 적용 가능한 유사한 모델이 있을 때

이 조건들이 충족되지 않으면 전이 학습은 모델 성능에 부정적인 영향을 미칠 수 있습니다. 이러한 현상을 '부정적 전이(Negative Transfer)'라고 합니다. 최근 연구에서는 이러한 조건이 충족되는지를 판단하기 위한 다양한 테스트 방법을 제안하고 있으며, 이는 부정적 전이를 방지하는 데 도움이 됩니다.


3.2 전이 학습의 주요 적용 사례

  • 이미지 분류: 사전 학습된 모델을 사용하여 새로운 이미지 데이터를 분류하는 작업에서 자주 사용됩니다. 
  • 자연어 처리: BERT나 GPT 같은 사전 학습된 모델을 사용해 새로운 텍스트 분류 작업을 수행합니다.
  • 의료 이미지 분석: 사전 학습된 모델을 사용해 X-ray, MRI 등 의료 이미지를 분석하는 데 활용됩니다.

4. 간단한 전이 학습 모델 실습

import torch
import torchvision.models as models

# ResNet18 모델을 사전 학습된 가중치와 함께 불러오기
model = models.resnet18(pretrained=True)

# 모델 구조 확인
print(model)

 

  • torch와 torchvision. models의 임포트:
    먼저 PyTorch 라이브러리인 torch와, 사전 학습된 모델을 제 공하는 torchvision.models를 임포트합니다.
  • resnet18 모델 불러오기:
    models.resnet18(pretrained=True)는 ResNet-18 모델을 불러옵니다. 이 모델은 ImageNet 데이터셋으로 사전 학습된 가중치(weight)를 사용합니다. 이 가중치를 재사용하여 새로운 데이터셋에서 훈련을 할 때 유용합니다. pretrained=True 옵션은 사전 학습된 가중치를 사용하겠다는 의미입니다.
  • 모델 구조 확인:
    print(model)은 ResNet-18의 전체 네트워크 구조를 출력합니다. 여기에는 각 레이어(컨볼루션, 배치 정규화, ReLU 활성화 함수 등)가 포함됩니다.
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
    (1): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

 

※ 결과 해석

출력된 결과는 ResNet-18 모델의 계층 구조를 자세히 보여줍니다. 주요 레이어들은 다음과 같습니다.
1) conv1: 첫 번째 컨볼루션 레이어는 (7x7) 크기의 커널과 64개의 출력 채널을 가지고 있으며, 입력은 3채널(RGB 이미지)입니다. 여기서 stride가 2이므로 이미지를 2배 축소합니다.
2) BatchNorm2d: 배치 정규화 레이어입니다. 이는 각 미니배치에서 평균과 분산을 정규화하여 학습을 안정화시킵니다.
3) ReLU(inplace=True): ReLU 활성화 함수는 음수 값을 0으로 바꿔주는 역할을 합니다. inplace=True는 메모리 사용량을 줄이기 위한 옵션입니다.
4) MaxPool2d: 최대 풀링 레이어로 (3x3) 크기의 풀링 커널과 stride 2를 사용하여 이미지 크기를 더욱 줄입니다.
5) layer1 ~ layer4: 각 layer는 ResNet의 기본 구조인 BasicBlock으로 구성되어 있습니다. 이 블록은 두 개의 (3x3) 컨볼루션과 배치 정규화, ReLU 활성화로 구성되며, 입력과 출력을 더해줍니다(Residual Connection). 각 레이어는 채널 수가 64에서 512로 증가하며, stride를 통해 이미지 크기를 계속해서 줄입니다.
6) AdaptiveAvgPool2d: 마지막에는 Adaptive Average Pooling 레이어가 있어, 입력의 크기에 상관없이 고정된 출력 크기(1x1)로 변환됩니다.
7) fc (Fully Connected Layer): 최종 레이어는 Fully Connected Layer로, 입력 크기 512에서 출력 크기 1000으로 변환됩니다. 이는 1000개의 클래스를 분류하기 위한 출력입니다.(ImageNet의 1000개 클래스)

 

즉, ResNet-18은 Residual Connection을 사용하여 더 깊은 신경망에서도 기울기 소실 문제를 해결할 수 있는 모델입니다. 이 네트워크는 이미 사전 학습된 가중치를 사용하므로, 전이 학습을 통해 새로운 이미지 분류 작업에 쉽게 적응할 수 있습니다.

 

5. 기본적인 이미지 분류 실습

import torchvision.models as models
import torchvision.transforms as transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader

# 데이터셋 전처리
transform = transforms.Compose([
    transforms.Resize(224),  # ResNet 입력 크기 맞추기
    transforms.ToTensor(),  # 텐서로 변환
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 사전 학습된 모델에 맞는 정규화
])

# ImageFolder를 사용하여 train 데이터셋 불러오기
train_dataset = ImageFolder(root='./train', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

# 학습 루프 간단 구현
for inputs, labels in train_loader:
    outputs = model(inputs)
    print(outputs)  # 예측 결과 출력
    print(f'\n출력 데이터 크기:', outputs.shape)
    print(f'\n32개 이미지에 대한 예측된 클래스:', outputs.argmax(axis=1))
    break  # 한 배치만 확인

 

1) 전처리 과정 정의

  • transforms.Compose는 여러 전처리 과정을 묶어서 순차적으로 적용할 수 있게 합니다.
  • Resize(224): CIFAR-10 이미지를 ResNet의 입력 크기인 224x224로 크기 조정합니다. ResNet은 기본적으로 224x224 크기의 이미지를 입력으로 받기 때문에 맞춰줘야 합니다.
  • ToTensor():이미지를 텐서(tensor)로 변환합니다. 이 변환은 [0, 255] 범위의 픽셀 값을 [0, 1] 범위로 정규화합니다.
  • Normalize(mean, std): 사전 학습된 ResNet 모델에서 사용한 데이터 정규화 값인 평균과 표준편차를 이용해 이미지를 정규화합니 다. 이를 통해 이미지의 값 범위를 맞춰줌으로써, 모델이 사전 학습된 환경과 유사한 데이터로 학습할 수 있게 합니다.


2) 이미지 데이터셋을 로드하기

  • ImageFolder는 PyTorch의 torchvision.datasets 모듈에 포함된 클래스입니다. 이 클래스는 특정 디렉토리 구조에 맞춰 이미지를 자동으로 로드하고, 해당 이미지의 클래스(레이블)를 추론합니다.
  • 디렉토리 구조에 기반한 데이터셋 로더로, 디렉토리 이름을 클래스로 사용하고, 그 안의 이미지를 자동으로 불러와 주어진 변환 (transform)을 적용하여 데이터셋을 생성합니다.
  • 즉, train 디렉토리 아래에 각각의 클래스(레이블) 이름을 가진 서브 디렉토리가 있으며, 그 안에 각 클래스에 속하는 이미지들이 있어야 합니다.
  • transform-transform: 앞서 정의한 전처리를 적용합니다.

 

3) DataLoader

  • DataLoader는 모델에 데이터를 공급하기 위한 도구입니다. 학습 데이터를 배치 단위로 나누어 모델에 전달하고, 데이터를 섞거나 (shuffle) 배치 크기를 지정할 수 있습니다.
  • batch_size=32: 한 번에 32개의 샘플을 학습에 사용합니다.
  • shuffle=True: 데이터셋을 무작위로 섞어서 학습합니다.

 

4) 학습 루프

  • for inputs, labels in train_loader :: CIFAR-10의 학습 데이터를 배치 단위로 가져옵니다. inputs는 입력 이미지, labels는 각 이미지에 대한 실제 라벨입니다.
  • outputs =model(inputs): 사전 학습된 ResNet 모델을 사용하여 입력 데이터를 예측합니다. 각 이미지에 대해 1000개의 클래스로 분류된 결과가 반환됩니다.
  • print(outputs): 예측 결과를 출력합니다.
  • break: 첫 번째 배치에서만 결과를 확인하기 위해 한 번만 루프를 실행하고 멈춥니다.

 

tensor([[ 0.9821, -1.5525,  1.0293,  ..., -1.4344, -0.2788,  0.5903],
        [ 1.7452, -0.0568, -0.8030,  ...,  2.2315,  1.8798, -1.1263],
        [-2.2867, -2.5841, -0.5504,  ...,  0.1290, -0.0725,  1.2060],
        ...,
        [ 0.5681, -1.0880,  0.8877,  ...,  3.3779,  3.2365, -0.2004],
        [ 1.3243,  1.7805,  1.6069,  ..., -0.2049,  0.9064,  2.5498],
        [ 0.1707,  0.3540,  4.7256,  ..., -4.7745, -0.7109,  2.0515]],
       grad_fn=<AddmmBackward0>)

출력 데이터 크기: torch.Size([32, 1000])

32개 이미지에 대한 예측된 클래스: tensor([660, 373, 536, 842, 727, 106, 703, 586, 884, 696, 678,  97, 391, 588,
        783, 674, 942, 657, 891,  11, 805, 996, 902, 491, 987, 261, 470, 592,
        206, 319, 107, 150])

 

※ 결과 해석

1) 출력 텐서 (tensor ([...]))
출력된 텐서는 torch.Size([32, 1000]) 크기로, 32개의 이미지에 대해 1000개의 클래스 점수를 나타냅니다. 각 이미지에 대해
모델이 1000개의 클래스 중 어느 클래스에 속하는지를 확률 점수 형태로 예측한 것입니다. 각 값은 해당 클래스에 대한 로짓 (logits) 점수이며, 이 값이 높을수록 모델은 해당 클래스에 속할 가능성이 높다고 판단한 것입니다.


2) outputs.argmax(dim=1) 결과
outputs.argmax(dim=1)은 각 이미지에 대해 가장 높은 점수를 가진 클래스를 찾아냅니다. 즉, 32개의 이미지 각각에 대해 모델 이 예측한 최종 클래스 인덱스를 반환합니다.
예를 들어, 첫 번째 이미지는 660번 클래스, 두 번째 이미지는 373번 클래스, 세 번째 이미지는 536번 클래스가 예측되었습니다.


3) 추가적인 설명
이 모델은 ResNet-18으로, 사전 학습된 가중치로 ImageNet 데이터를 기반으로 학습되었습니다. ImageNet 데이터는 1000개의 클래스로 이루어져 있기 때문에, 이 모델은 1000개의 클래스에 대한 확률 분포를 출력합니다. 그러나 CIFAR-10 데이터는 10
개의 클래스만 가지고 있으므로, 이 모델을 그대로 사용할 경우 결과가 기대와 맞지 않을 수 있습니다.
출력된 예측 클래스 인덱스들은 ImageNet의 1000개 클래스에 해당하는 것으로, CIFAR-10 데이터에 적합하지 않은 결과일 가 능성이 큽니다. 따라서, 이 모델을 CIFAR-10 데이터에 맞추기 위해서는 미세 조정(fine-tuning)이 필요합니다. 미세 조정을 통해 CIFAR-10 데이터셋의 10개의 클래스로 모델을 적응시킬 수 있습니다.


4) 미세 조정의 필요성
사전 학습된 ResNet-18 모델은 ImageNet에서 학습된 가중치를 가지고 있으며, CIFAR-10 데이터에 적용하려면 해당 가중치를 조정하는 과정이 필요합니다. 이를 위해서 CIFAR-10 데이터에 맞춘 새로운 최종 출력 레이어를 추가하거나, 기존 레이어의 가중 치를 일부 고정한 채로 나머지 레이어를 학습하는 방법을 사용할 수 있습니다.