들어가며
딥러닝을 처음 공부하면서 가장 실용적으로 느낀 부분이 사전학습 모델 활용이다. 수억 개의 데이터로 학습된 모델을 가져다가 내 문제에 맞게 조금만 수정하면 처음부터 학습하는 것보다 훨씬 좋은 성능이 나온다. 웹 개발로 비유하면 npm 패키지를 가져다 쓰는 것과 비슷하다. 바퀴를 다시 발명하지 않아도 된다.
Transfer Learning이란?
일반적인 학습:
랜덤 초기화 → 내 데이터로 처음부터 학습
Transfer Learning:
사전학습 모델 로드 → 내 데이터로 미세조정 (Fine-tuning)
효과:
- 적은 데이터로도 좋은 성능
- 학습 시간 대폭 감소
- 사전학습 모델의 일반적 지식 활용
사전학습 모델의 종류
이미지 (Vision)
ResNet-50/101/152 : 안정적, 범용적
EfficientNet : 정확도-효율 균형 좋음
VGG-16/19 : 단순한 구조, 특징 추출 용이
MobileNet : 경량화, 모바일/엣지 배포
ViT : 최신, 대용량 데이터에 강함
CLIP : 이미지-텍스트 멀티모달
텍스트 (NLP)
BERT 계열:
- BERT-base/large : 양방향, 이해 태스크
- RoBERTa : BERT 개선판
- klue/bert-base : 한국어 특화
GPT 계열:
- GPT-2/3/4 : 텍스트 생성
한국어:
- klue/roberta : 한국어
- snunlp/KR-ELECTRA : 한국어
이미지 Transfer Learning
Feature Extraction (특징 추출)
사전학습 모델의 가중치를 고정하고 마지막 레이어만 교체한다.
import torch
import torch.nn as nn
import torchvision.models as models
model = models.resnet50(pretrained=True)
# 모든 파라미터 고정
for param in model.parameters():
param.requires_grad = False
# 마지막 FC 레이어만 교체
num_features = model.fc.in_features # 2048
model.fc = nn.Sequential(
nn.Linear(num_features, 256),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(256, 10)
)
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"학습 파라미터: {trainable:,} / {total:,}")
# 약 2.2%만 학습
Fine-tuning (미세조정)
레이어별 다른 학습률을 사용하는 방식이 효과적이다.
model = models.resnet50(pretrained=True)
model.fc = nn.Linear(model.fc.in_features, 10)
# 레이어별 다른 학습률 (Discriminative Learning Rate)
optimizer = torch.optim.Adam([
{'params': model.layer1.parameters(), 'lr': 1e-5}, # 앞 레이어: 매우 작은 lr
{'params': model.layer2.parameters(), 'lr': 1e-5},
{'params': model.layer3.parameters(), 'lr': 1e-4},
{'params': model.layer4.parameters(), 'lr': 1e-4},
{'params': model.fc.parameters(), 'lr': 1e-3} # 새 레이어: 큰 lr
])
전체 학습 파이프라인
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
# ImageNet 통계로 정규화 (필수)
train_transform = transforms.Compose([
transforms.Resize(256),
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
val_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
# 폴더 구조: data/train/class1/..., data/val/class1/...
train_dataset = datasets.ImageFolder('data/train', transform=train_transform)
val_dataset = datasets.ImageFolder('data/val', transform=val_transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = models.resnet50(pretrained=True)
for param in model.parameters():
param.requires_grad = False
for param in model.layer4.parameters():
param.requires_grad = True
model.fc = nn.Linear(model.fc.in_features, len(train_dataset.classes))
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam([
{'params': model.layer4.parameters(), 'lr': 1e-4},
{'params': model.fc.parameters(), 'lr': 1e-3}
])
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)
# 최적 모델 저장
best_acc = 0
for epoch in range(20):
model.train()
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
optimizer.zero_grad()
loss = criterion(model(images), labels)
loss.backward()
optimizer.step()
# 검증
model.eval()
correct = total = 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
_, pred = model(images).max(1)
correct += pred.eq(labels).sum().item()
total += labels.size(0)
val_acc = 100. * correct / total
scheduler.step()
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), 'best_model.pth')
NLP Transfer Learning: HuggingFace
from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_name = "klue/bert-base" # 한국어 BERT
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
model_name, num_labels=5
)
# 토크나이징
texts = ["이 영화 정말 재미있었어요!", "별로였습니다."]
encoding = tokenizer(
texts,
max_length=128,
padding=True,
truncation=True,
return_tensors='pt'
)
# 추론
model.eval()
with torch.no_grad():
outputs = model(**encoding)
predictions = outputs.logits.argmax(dim=-1)
print(f"예측 클래스: {predictions}")
HuggingFace Trainer로 Fine-tuning
from transformers import TrainingArguments, Trainer, DataCollatorWithPadding
from datasets import load_dataset
# 데이터셋 로드 (NSMC 영화 리뷰)
dataset = load_dataset("nsmc")
def tokenize_function(examples):
return tokenizer(examples['document'], max_length=128, truncation=True)
tokenized_dataset = dataset.map(
tokenize_function, batched=True, remove_columns=['id', 'document']
)
training_args = TrainingArguments(
output_dir='./results',
num_train_epochs=3,
per_device_train_batch_size=32,
per_device_eval_batch_size=64,
learning_rate=2e-5,
weight_decay=0.01,
evaluation_strategy='epoch',
save_strategy='epoch',
load_best_model_at_end=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset['train'],
eval_dataset=tokenized_dataset['test'],
tokenizer=tokenizer,
data_collator=DataCollatorWithPadding(tokenizer),
)
trainer.train()
다양한 NLP 태스크
from transformers import pipeline
# 감성 분석
sentiment = pipeline("sentiment-analysis", model="snunlp/KR-FinBert-SC")
print(sentiment("이 제품 너무 좋아요!"))
# [{'label': 'positive', 'score': 0.99}]
# 질의응답 (QA)
qa = pipeline("question-answering",
model="bert-large-uncased-whole-word-masking-finetuned-squad")
print(qa(
question="What is the capital of France?",
context="Paris is the capital and most populous city of France."
))
# {'answer': 'Paris', 'score': 0.99}
# Named Entity Recognition (NER)
ner = pipeline("ner", model="klue/bert-base", aggregation_strategy="simple")
print(ner("삼성전자는 서울에 본사를 두고 있습니다."))
# [{'entity_group': 'ORG', 'word': '삼성전자'},
# {'entity_group': 'LOC', 'word': '서울'}]
모델 저장과 로드
# PyTorch 모델 저장
torch.save(model.state_dict(), 'model_weights.pth')
# 체크포인트 저장 (학습 재개 가능)
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'best_acc': best_acc
}, 'checkpoint.pth')
# 체크포인트에서 재개
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_epoch = checkpoint['epoch'] + 1
# HuggingFace 모델 저장/로드
model.save_pretrained('./my_model')
tokenizer.save_pretrained('./my_model')
loaded_model = AutoModelForSequenceClassification.from_pretrained('./my_model')
loaded_tokenizer = AutoTokenizer.from_pretrained('./my_model')
Transfer Learning 전략 선택
데이터 양에 따른 전략:
데이터 매우 적음 (< 1,000개):
→ Feature Extraction
→ 사전학습 모델 고정, 마지막 레이어만 학습
데이터 적음 (1,000 ~ 10,000개):
→ 부분 Fine-tuning
→ 마지막 몇 개 레이어만 학습 (lr: 1e-4)
데이터 보통 (10,000 ~ 100,000개):
→ 전체 Fine-tuning
→ 모든 레이어 학습, 다른 lr 사용 (앞: 1e-5, 뒤: 1e-3)
데이터 많음 (> 100,000개):
→ 처음부터 학습 또는 Fine-tuning 모두 가능
도메인 유사도에 따른 전략:
유사한 도메인 → 더 많은 레이어 고정
다른 도메인 → 더 많은 레이어 학습
정리
| 항목 | 설명 | 핵심 포인트 |
|---|---|---|
| Feature Extraction | 사전학습 모델 고정, 마지막 레이어만 교체 | 데이터 적을 때 효과적 |
| Fine-tuning | 일부 또는 전체 레이어 학습 | 도메인 유사할수록 적게 수정 |
| Discriminative LR | 레이어별 다른 학습률 | 앞 레이어 < 뒷 레이어 |
| HuggingFace | NLP 사전학습 모델 생태계 | AutoModel, Trainer, pipeline |
| ImageNet 정규화 | mean=[0.485, 0.456, 0.406] | Vision 모델 필수 전처리 |
| 모델 저장 | state_dict 또는 save_pretrained | 체크포인트로 학습 재개 가능 |
'딥러닝 > 딥러닝 아키텍쳐' 카테고리의 다른 글
| Transformer / Attention (0) | 2026.05.16 |
|---|---|
| RNN / LSTM (시퀀스, 텍스트) (0) | 2026.05.16 |
| CNN (합성곱 신경망) (0) | 2026.05.15 |