Python for AI, Embedded/Deep Learning: PyTorch & AI Modeling

PyTorch 파인 튜닝(Fine-Tuning) 완벽 가이드: ResNet 예제로 배우기

임베디드 친구 2026. 5. 26. 21:16
반응형

딥러닝 모델을 처음부터 끝까지 학습시키는 작업은 엄청난 시간과 고성능 연산 자원, 그리고 방대한 데이터셋이 필요합니다. 인프라가 한정된 환경이나 빠르게 프로토타입을 만들어야 하는 상황에서는 이것이 큰 진입 장벽이 되기도 합니다. 이러한 문제를 해결하기 위해 현업에서 가장 많이 사용하는 방식이 바로 사전 학습된 모델을 목적에 맞게 재학습시키는 파인 튜닝(Fine-Tuning)입니다. 이번 글에서는 파이토치(PyTorch)를 활용해 이미 검증된 성능을 가진 ResNet-50 모델을 우리가 원하는 데이터셋에 맞춰 미세 조정하는 방법을 구체적인 예제 코드와 함께 살펴보겠습니다.

Generated by Gemini AI.

핵심 요약 3줄

  • 파인 튜닝은 방대한 데이터로 미리 학습된 모델의 가중치를 가져와 새로운 특정 목적에 맞게 미세 조정하는 전이 학습 기법입니다.
  • 기존 모델의 초기 계층(특징 추출기) 가중치는 고정하고, 분류를 담당하는 마지막 계층(FC 레이어)만 교체하여 데이터가 부족한 상황에서도 높은 성능을 낼 수 있습니다.
  • 최신 PyTorch API 표준에 맞춰 pretrained=True 대신 weights 인자를 사용하는 안전한 구현 방식을 본문에서 공유합니다.

1. Fine-Tuning 개요

파인 튜닝은 이미 이미지넷(ImageNet)과 같은 거대 데이터셋에서 학습을 마친 컴퓨터 비전 모델의 지식을 새로운 도메인 문제로 이식하는 전이 학습(Transfer Learning)의 대표적인 형태입니다. 기본적으로 딥러닝 기반의 이미지 분류 모델은 앞쪽 레이어에서 선이나 면, 모서리 같은 일반적인 시각 특징을 추출하고, 뒤쪽 레이어로 갈수록 데이터셋에 특화된 고차원 정보를 학습합니다. 파인 튜닝은 이 원리를 활용합니다.

개발 환경의 조건에 따라 파인 튜닝을 적용하는 접근 방식은 크게 두 가지로 나뉩니다. 데이터셋의 규모와 도메인 유사성에 따라 전략을 선택해야 합니다.

파인 튜닝 전략 주요 특징 추천 시나리오
특징 추출기 고정 (Feature Extraction) 기존 가중치를 모두 얼리고(Freeze) 최종 출력 레이어만 새로 학습 데이터셋이 매우 적거나, 기존 학습 도메인과 유사할 때
전체 모델 미세 조정 (Fine-Tuning) 모든 레이어의 가중치를 업데이트하되, 매우 낮은 학습률 설정 데이터셋이 충분히 많거나, 도메인이 크게 다를 때

2. 사전 학습된 모델 불러오기

먼저 파이토치의 torchvision.models 모듈에서 이미지 분류의 표준으로 자주 쓰이는 ResNet-50 모델을 불러옵니다. 여기서 주의할 점이 있습니다. 과거에 자주 쓰이던 pretrained=True 옵션은 최신 PyTorch 버전에서 제거(Deprecated) 대상이므로, 최신 표준 API인 weights 인자를 사용해야 경고 메시지 없이 안전하게 코드가 동작합니다.

Python
 
import torch
import torch.nn as nn
import torchvision.models as models

# 최신 PyTorch 표준 방식으로 사전 학습된 ResNet-50 모델 불러오기
# weights=models.ResNet50_Weights.DEFAULT를 사용하여 최신 버전의 가중치를 자동 로드합니다.
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)

이렇게 가져온 모델은 기본적으로 1,000개의 클래스를 분류하도록 설정되어 있습니다. 우리가 사용할 데이터셋의 클래스 개수에 맞게 변형하는 작업이 다음 단계입니다.

3. 특정 계층을 고정하고 일부 계층만 학습하기

이번 예제에서는 가중치를 고정하는 '특징 추출기' 전략을 사용하겠습니다. 앞단의 컨볼루션 레이어들이 가진 파라미터는 업데이트되지 않도록 고정하고, 맨 마지막의 Fully Connected(FC) 레이어만 우리가 타깃으로 하는 새로운 데이터셋의 클래스 개수(예: CIFAR-10의 경우 10개)에 맞게 교체합니다.

Python
 
# 모델의 모든 파라미터 그라디언트 계산을 비활성화하여 가중치 고정
for param in model.parameters():
    param.requires_grad = False

# ResNet-50의 마지막 Fully Connected 레이어명은 'fc'입니다.
# 기존 FC 레이어의 입력 차원 수를 확인합니다.
num_ftrs = model.fc.in_features

# 최종 분류 목적지에 맞게 분류기 교체 (CIFAR-10 데이터셋 가정을 위해 10으로 설정)
model.fc = nn.Linear(num_ftrs, 10)

이렇게 설정하면 model.fc 레이어만 requires_grad=True 상태가 되므로, 역전파 연산 시 이 마지막 레이어의 파라미터만 계산되고 업데이트됩니다.

4. Fine-Tuning을 위한 모델 학습 설정

모델 수정이 끝났다면 학습을 위한 손실 함수와 최적화 알고리즘(Optimizer)을 정의해야 합니다. 여기서 가장 중요한 포인트는 옵티마이저에 모델 전체의 파라미터가 아닌, 우리가 새로 교체한 레이어의 파라미터만 전달해야 한다는 점입니다.

Python
 
import torch.optim as optim

# 다중 클래스 분류를 위한 교차 엔트로피 손실 함수 정의
criterion = nn.CrossEntropyLoss()

# 모델 전체가 아닌 수정된 최종 FC 레이어의 파라미터만 옵티마이저에 전달
optimizer = optim.Adam(model.fc.parameters(), lr=0.001)

만약 파라미터를 고정해 둔 상태에서 옵티마이저에 model.parameters()를 전부 넘겨주면, 연산 효율이 떨어지거나 불필요한 메모리 오버헤드가 발생할 수 있으므로 학습 대상만 명시하는 것이 깔끔합니다.

5. 모델 학습 과정

이제 데이터를 넣어 가중치를 갱신하는 실질적인 학습 루프 함수를 만듭니다. 일반적인 학습 과정과 동일하지만, 하드웨어 가속을 위해 데이터와 모델을 GPU 장치로 보내는 과정을 포함합니다.

Python
 
def train_model(model, dataloader, criterion, optimizer, num_epochs=5, device='cuda'):
    # 지정된 장치(GPU 또는 CPU)로 모델 이동
    model.to(device)
    
    for epoch in range(num_epochs):
        model.train() # 모델을 학습 모드로 전환
        running_loss = 0.0
        
        for inputs, labels in dataloader:
            # 데이터를 동일한 장치로 이동
            inputs, labels = inputs.to(device), labels.to(device)
            
            # 이전 배치에서 계산된 그라디언트 초기화
            optimizer.zero_grad()
            
            # 순전파 연산
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            
            # 역전파 연산 및 파라미터 업데이트
            loss.backward()
            optimizer.step()
            
            running_loss += loss.item()
            
        epoch_loss = running_loss / len(dataloader)
        print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}')
        
    print('Training complete')

6. 전체 Fine-Tuning 수행 예제

지금까지 작성한 요소들을 하나로 묶어 CIFAR-10 데이터셋을 다운로드하고 실제로 파인 튜닝을 실행하는 전체 스크립트입니다. ResNet 모델은 기본적으로 224x224 크기의 입력을 받도록 설계되었기 때문에 전처리 과정에서 이미지 크기를 조정하는 변환(Transform) 작업이 들어갑니다.

Python
 
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
import torchvision.datasets as datasets
from torch.utils.data import DataLoader
import torchvision.models as models

# 1. 데이터 변환 및 전처리 정의 (ResNet 입력 규격에 맞춤)
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 2. CIFAR-10 데이터셋 로드
train_dataset = datasets.CIFAR10(root='./data', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

# 3. 최신 표준 방식으로 사전 학습된 ResNet-50 모델 로드 및 레이어 고정
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
for param in model.parameters():
    param.requires_grad = False

# 4. 최종 출력 레이어를 타깃 클래스 수(10개)에 맞게 교체
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 10)

# 5. 손실 함수 및 학습 대상 지정 옵티마이저 설정
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=0.001)

# 6. GPU 가속 여부 확인 후 학습 프로세스 시작
device = 'cuda' if torch.cuda.is_available() else 'cpu'
train_model(model, train_loader, criterion, optimizer, num_epochs=5, device=device)

7. 개발을 위한 팁

  • 입력 데이터 정규화 값 매칭: 사전 학습된 모델을 사용할 때는 가중치가 학습될 당시 적용되었던 평균(mean)과 표준편차(std) 값으로 새로운 데이터셋도 정규화해 주어야 성능이 온전하게 나옵니다. ImageNet 가중치 기반 모델들은 대다수 mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]를 사용합니다.
  • 스케줄러 활용: 마지막 레이어만 학습할 때는 학습률 변화가 크지 않아도 되지만, 전 레이어를 미세 조정하는 단계로 넘어가면 학습이 진행됨에 따라 학습률을 점진적으로 낮춰주는 lr_scheduler를 결합하는 것이 수렴에 유리합니다.
  • 레이어별 차등 학습률 적용: 만약 앞쪽 특징 추출기 레이어도 일부 깨워서 학습시키고 싶다면, 앞쪽 레이어는 매우 작은 학습률($10^{-5}$)을 주고 새로 바꾼 최종 레이어는 기본 학습률($10^{-3}$)을 주는 방식으로 차등 설정하는 기법이 효과적입니다.

8. 흔히 하는 실수

  • model.eval()과 model.train()의 혼동: 파인 튜닝 시 가중치를 고정했다고 하더라도 학습 루프 내에서 배치 정규화(Batch Normalization)나 드롭아웃(Dropout) 레이어가 정상 동작하려면 학습 초기화 시 반드시 model.train() 상태여야 합니다. 검증 단계에서는 반대로 model.eval()을 빠뜨리지 않아야 평가 지표가 왜곡되지 않습니다.
  • 고정된 파라미터를 옵티마이저에 전부 등록: requires_grad=False 처리를 해놓고 정작 옵티마이저 선언 시 optim.Adam(model.parameters(), lr=0.001)과 같이 전체 파라미터를 넘기면 PyTorch 버전에 따라 업데이트가 불가능한 파라미터가 있다는 에러를 뿜거나 리소스를 낭비하게 됩니다. 업데이트할 대상만 슬라이싱해서 넘겨주어야 합니다.
  • 과도하게 큰 학습률 설정: 전이 학습 시 학습률을 일반적인 스크래치 학습처럼 높게 잡아버리면 이미 잘 구축되어 있던 앞단 레이어의 유용한 시각 특징 가중치들이 순식간에 파괴되는 가공할 망각(Catastrophic Forgetting) 현상이 일어납니다.

9. 결론

사전 학습된 거대 모델을 목적에 맞춰 미세 조정하는 파인 튜닝은 한정된 데이터와 자원 속에서 상용화 수준의 딥러닝 아웃풋을 내기 위한 핵심 엔지니어링 기법입니다. 본문에서 다룬 내용처럼 레이어 가중치를 동결하고 최종 분류기를 교체하는 핵심 메커니즘을 이해하고 구현할 수 있다면, 대다수의 비전 및 자연어 처리 태스크에 전이 학습을 유연하게 접목할 수 있습니다. 가지고 계신 고유한 데이터셋에 직접 적용해 보면서 비즈니스 요구사항에 맞는 효율적인 AI 파이프라인을 구축해 보시기 바랍니다.

반응형