이 장에서 배울 것
이번 장에서는 현대 딥러닝 실무에서 매우 자주 쓰이는 두 가지 흐름을 배운다. 하나는 전이학습이고, 다른 하나는 생성모델이다.
전이학습은 이미 큰 데이터로 학습된 모델을 가져와 내 문제에 맞게 다시 사용하는 방법이다. 예를 들어 ImageNet 같은 대규모 이미지 데이터로 학습된 CNN은 선, 모서리, 질감, 물체의 일부 같은 일반적인 시각 특징을 이미 배운 상태다. 이 모델을 작은 이미지 분류 문제에 가져오면 처음부터 학습하는 것보다 빠르고 안정적으로 출발할 수 있다.1
생성모델은 분류 모델처럼 "이것이 무엇인가?"만 답하는 모델이 아니다. 데이터의 분포를 배워서 "그럴듯한 새 샘플"을 만드는 모델이다. 오토인코더, GAN, 확산 모델은 모두 이 큰 흐름 안에 있다.

왜 이 주제가 중요한가
딥러닝을 처음 배울 때는 모델을 직접 만들고, 가중치를 무작위로 초기화한 뒤, 데이터로 학습시키는 과정을 익힌다. 이 과정은 기본기를 이해하는 데 좋다. 하지만 실무에서는 항상 처음부터 학습하지 않는다.
이유는 단순하다. 데이터와 시간이 부족하기 때문이다.
고성능 CNN이나 Transformer를 처음부터 학습하려면 많은 데이터, 긴 학습 시간, 큰 GPU 자원이 필요하다. 반면 사전 학습 모델을 사용하면 이미 학습된 표현을 재사용할 수 있다. 특히 데이터가 적은 이미지 분류 문제에서는 전이학습이 강력한 기본 전략이 된다.
생성모델도 중요하다. 최근의 이미지 생성, 음성 생성, 코드 생성, 문서 생성 시스템은 모두 "데이터 분포를 배워 새 출력을 만든다"는 아이디어와 연결된다. 이번 장에서는 수식보다 직관을 먼저 잡는다. 생성모델을 깊게 다루는 것은 다음 권 또는 심화 과정의 몫이다.
핵심 개념 설명
전이학습: 이미 배운 표현을 가져오기
전이학습은 한 문제에서 배운 지식을 다른 문제에 옮겨 쓰는 방법이다.
이미지 모델을 예로 들면, CNN의 앞쪽 층은 보통 비교적 일반적인 특징을 배운다. 선, 색 대비, 작은 무늬, 모서리 같은 특징이다. 중간 층은 눈, 바퀴, 잎, 털 같은 더 복잡한 패턴을 배울 수 있다. 마지막 층은 특정 데이터셋의 클래스에 맞춰 "개", "자동차", "벌", "개미"처럼 최종 판단을 한다.
따라서 새 데이터셋을 학습할 때는 마지막 분류층만 바꾸면 되는 경우가 많다. 기존 모델의 앞부분은 특징 추출기로 쓰고, 마지막 부분만 내 클래스 수에 맞게 교체한다.
특징 추출과 미세조정
전이학습에는 대표적으로 두 가지 방식이 있다.
특징 추출(feature extraction)은 사전 학습된 모델의 대부분 가중치를 고정하고 마지막 분류층만 학습하는 방식이다. PyTorch에서는 각 파라미터의 requires_grad를 False로 바꾸면 해당 파라미터의 기울기를 계산하지 않는다. 즉, 학습 중 업데이트하지 않는다.
이 방식은 빠르고 안정적이다. 데이터가 적을 때 특히 유용하다. 다만 새 데이터가 기존 사전 학습 데이터와 많이 다르면 성능이 제한될 수 있다.
미세조정(fine-tuning)은 일부 층 또는 전체 층을 다시 학습하는 방식이다. 보통 마지막 몇 개 층부터 풀고, 낮은 학습률로 천천히 업데이트한다. 이미 배운 표현을 완전히 망가뜨리지 않기 위해서다.
정리하면 다음과 같다.
| 방식 | 업데이트 대상 | 장점 | 주의점 |
|---|---|---|---|
| 특징 추출 | 마지막 분류층 중심 | 빠름, 데이터 적을 때 안정적 | 새 도메인 차이가 크면 한계 |
| 미세조정 | 일부 또는 전체 층 | 새 문제에 더 잘 적응 | 학습률이 크면 기존 표현이 망가짐 |
생성모델: 데이터 분포를 배우기
분류 모델은 입력을 보고 라벨을 예측한다. 예를 들어 고양이 사진을 넣으면 "고양이"라고 답한다.
생성모델은 방향이 다르다. 학습 데이터가 어떤 모양으로 분포하는지 배운 뒤, 그 분포에서 새 샘플을 만든다. 숫자 이미지 데이터로 학습한 모델은 새로운 숫자 이미지를 만들고, 얼굴 이미지 데이터로 학습한 모델은 새로운 얼굴처럼 보이는 이미지를 만들 수 있다.
여기서 중요한 말은 분포다. 생성모델은 단순히 학습 이미지를 복사하는 것이 목표가 아니다. 데이터들이 공통적으로 가진 구조를 배워, 기존 데이터와 비슷하지만 완전히 같은 것은 아닌 샘플을 만드는 것이 목표다.
오토인코더, GAN, 확산 모델
오토인코더(autoencoder)는 입력을 압축한 뒤 다시 복원하는 모델이다. 보통 두 부분으로 나뉜다.
인코더: 입력을 작은 잠재 표현으로 압축한다.
디코더: 잠재 표현에서 원래 입력을 복원한다.
잠재 표현은 데이터의 핵심 정보를 요약한 공간이다. 예를 들어 손글씨 숫자 이미지를 작은 벡터로 압축하면, 그 벡터에는 숫자의 굵기, 기울기, 모양 같은 정보가 담길 수 있다. 오토인코더는 차원 축소, 노이즈 제거, 표현 학습의 출발점으로 자주 설명된다.2
GAN은 생성자와 판별자가 경쟁하는 구조다. 생성자는 가짜 샘플을 만들고, 판별자는 그것이 진짜 데이터인지 생성된 데이터인지 구분한다. 생성자는 판별자를 속이도록 학습하고, 판별자는 더 잘 구분하도록 학습한다. 이 경쟁이 잘 진행되면 생성자는 점점 더 그럴듯한 샘플을 만든다.
확산 모델(diffusion model)은 노이즈에서 출발해 점진적으로 노이즈를 제거하며 샘플을 만드는 모델이다. 학습할 때는 데이터에 조금씩 노이즈를 섞는 과정을 배우고, 생성할 때는 반대로 노이즈를 깨끗한 데이터로 바꾸는 방향을 따른다. 최근에는 확산 모델의 내부 구조에 Transformer를 결합하는 연구도 활발하다.3
이번 장에서는 VAE의 수식, GAN 학습 안정화 기법, 확산 모델의 샘플링 세부 알고리즘까지 들어가지는 않는다. 지금은 세 모델의 관점 차이를 이해하면 충분하다.
쉬운 비유 또는 사례
전이학습은 이미 운전을 배운 사람이 새 차를 모는 것과 비슷하다. 차종이 바뀌어도 핸들, 브레이크, 차선, 속도 감각은 대부분 유지된다. 그래서 처음 운전하는 사람보다 훨씬 빨리 적응한다.
특징 추출은 운전 습관은 그대로 두고 내비게이션 목적지만 바꾸는 것에 가깝다. 미세조정은 새 차의 브레이크 감도와 핸들 반응에 맞춰 운전 습관까지 조금 바꾸는 것이다.
생성모델은 그림을 외우는 학생이 아니라, 그림의 규칙을 배운 학생에 가깝다. 고양이 그림 1,000장을 그대로 외우는 것이 아니라 고양이 얼굴의 위치, 귀의 모양, 털의 질감 같은 규칙을 익힌다. 그래서 새 고양이 그림을 그릴 수 있다.
직접 해보는 실습
이번 실습은 PyTorch의 전이학습 흐름을 간단히 재현한다. 데이터는 다음과 같은 폴더 구조를 가정한다.
data/my_images/
train/
cat/
dog/
val/
cat/
dog/
test/
cat/
dog/test 폴더가 아직 없다면, 처음에는 train과 val만으로 학습과 모델 선택을 연습해도 된다. 다만 최종 성능을 공정하게 보고하려면 별도의 test set을 남겨 두는 편이 좋다.
핵심 흐름은 다음 순서다.
사전 학습 CNN을 불러온다.
입력 전처리를 맞춘다.
마지막 분류층을 새 클래스 수에 맞게 교체한다.
특징 추출 방식이면 앞쪽 파라미터를 고정한다.
기존 학습 루프를 그대로 사용한다.
validation set으로 모델을 선택하고, 가능하면 test set으로 마지막 평가를 한다.
정확도뿐 아니라 오분류 사례를 확인한다.
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
DATA_DIR = "data/my_images"
BATCH_SIZE = 16
NUM_CLASSES = 2
train_tfms = transforms.Compose([
transforms.Resize((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225]),
])
val_test_tfms = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225]),
])
train_ds = datasets.ImageFolder(f"{DATA_DIR}/train", transform=train_tfms)
val_ds = datasets.ImageFolder(f"{DATA_DIR}/val", transform=val_test_tfms)
train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False)
if os.path.exists(f"{DATA_DIR}/test"):
test_ds = datasets.ImageFolder(f"{DATA_DIR}/test", transform=val_test_tfms)
test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False)
else:
test_loader = None
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
# 특징 추출 방식: 기존 가중치 고정
for param in model.parameters():
param.requires_grad = False
# 마지막 분류층만 새 문제에 맞게 교체
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, NUM_CLASSES)
model = model.to(DEVICE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=1e-3)
best_val_acc = 0.0
for epoch in range(5):
model.train()
total_loss, correct, total = 0.0, 0, 0
for images, labels in train_loader:
images, labels = images.to(DEVICE), labels.to(DEVICE)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item() * images.size(0)
preds = outputs.argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
train_loss = total_loss / total
train_acc = correct / total
model.eval()
val_correct, val_total = 0, 0
all_preds, all_labels = [], []
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(DEVICE), labels.to(DEVICE)
outputs = model(images)
preds = outputs.argmax(dim=1)
val_correct += (preds == labels).sum().item()
val_total += labels.size(0)
all_preds.extend(preds.cpu().tolist())
all_labels.extend(labels.cpu().tolist())
val_acc = val_correct / val_total
print(f"epoch={epoch}, loss={train_loss:.4f}, train_acc={train_acc:.3f}, val_acc={val_acc:.3f}")
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(), "best_transfer_model.pth")미세조정을 해보고 싶다면 마지막 몇 개 층의 고정을 풀고 학습률을 낮춘다.
for param in model.layer4.parameters():
param.requires_grad = True
optimizer = optim.Adam([
{"params": model.layer4.parameters(), "lr": 1e-5},
{"params": model.fc.parameters(), "lr": 1e-4},
])모델 선택이 끝났다면, 저장한 모델을 불러와 test set에서 마지막 평가를 수행한다. test set이 없다면 이 단계는 생략하되, 보고서에 "별도 test set 없이 validation 결과만 확인했다"고 명확히 적는다.
model.load_state_dict(torch.load("best_transfer_model.pth", map_location=DEVICE))
model.eval()
if test_loader is not None:
test_correct, test_total = 0, 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(DEVICE), labels.to(DEVICE)
outputs = model(images)
preds = outputs.argmax(dim=1)
test_correct += (preds == labels).sum().item()
test_total += labels.size(0)
print("test_acc:", test_correct / test_total)실습 결과를 볼 때는 전체 정확도만 보지 말자. 다음 세 가지를 함께 확인해야 한다.
클래스별 정확도: 특정 클래스만 계속 틀리는가?
혼동행렬: 어떤 클래스를 서로 헷갈리는가?
오분류 이미지: 배경, 조명, 크기, 잘림 문제가 있는가?
전이학습 모델은 강력하지만 만능은 아니다. 사전 학습 데이터와 내 데이터의 차이가 크면 실패할 수 있다. 예를 들어 ImageNet으로 학습된 모델은 일반 사진에는 강하지만, 의료 영상이나 위성 영상처럼 질감과 촬영 방식이 크게 다른 데이터에서는 추가 조정이 필요할 수 있다.
흔한 실수와 오류 분석
첫째, 전처리를 사전 학습 모델에 맞추지 않는 실수다. ImageNet 기반 모델은 보통 특정 평균과 표준편차로 정규화된 입력을 기대한다. 입력 크기와 정규화가 맞지 않으면 성능이 크게 흔들릴 수 있다.
둘째, 고정한 파라미터까지 옵티마이저에 넣는 실수다. 큰 문제 없이 돌아갈 수는 있지만 불필요하다. 특징 추출 방식에서는 학습할 파라미터만 옵티마이저에 넣는 편이 명확하다.
셋째, 미세조정에서 학습률을 너무 크게 잡는 실수다. 사전 학습 가중치는 이미 유용한 상태다. 너무 큰 학습률은 이 표현을 빠르게 망가뜨릴 수 있다.
넷째, 검증 데이터에도 데이터 증강을 적용하는 실수다. 검증 단계에서는 모델을 공정하게 평가해야 하므로 무작위 변환을 줄이고, 크기 조정과 정규화 중심으로 처리한다.
다섯째, test set으로 모델을 고르는 실수다. test set은 최종 확인용이다. 어떤 층을 풀지, 학습률을 얼마로 할지, 몇 epoch을 돌릴지는 validation set을 보고 결정해야 한다.
여섯째, 생성모델을 "무언가를 창조하는 마법"처럼 이해하는 실수다. 생성모델은 학습 데이터의 분포에서 패턴을 배운다. 따라서 학습 데이터의 편향, 품질, 저작권 문제도 함께 따라온다.
요약
전이학습은 현대 딥러닝 실무의 기본 도구다. 처음부터 모든 가중치를 학습하지 않고, 이미 큰 데이터로 학습된 모델의 표현을 가져와 새 문제에 맞게 조정한다.
특징 추출은 사전 학습된 층을 고정하고 마지막 분류층만 학습한다. 미세조정은 일부 또는 전체 층을 낮은 학습률로 함께 업데이트한다.
생성모델은 데이터의 분포를 학습해 새 샘플을 만든다. 오토인코더는 입력을 잠재 공간으로 압축하고 복원한다. GAN은 생성자와 판별자의 경쟁으로 샘플을 만든다. 확산 모델은 노이즈를 점진적으로 제거하며 샘플을 만든다.
퀴즈
전이학습이 데이터가 적은 문제에서 유리한 이유는 무엇인가?
특징 추출과 미세조정의 차이를
requires_grad관점에서 설명해 보자.사전 학습 CNN의 마지막 분류층을 교체해야 하는 이유는 무엇인가?
전체 정확도만 보고 모델을 평가하면 놓칠 수 있는 문제는 무엇인가?
오토인코더의 인코더와 디코더는 각각 어떤 역할을 하는가?
GAN과 확산 모델의 생성 방식 차이를 한 문장으로 설명해 보자.
전이학습 실습에서 validation set과 test set은 각각 어떤 용도로 사용해야 하는가?
체크리스트
전이학습의 목적을 설명할 수 있다.
특징 추출과 미세조정을 구분할 수 있다.
PyTorch에서 사전 학습 모델을 불러오고 마지막 분류층을 교체할 수 있다.
requires_grad가 학습 대상 파라미터를 정하는 데 어떻게 쓰이는지 이해했다.클래스별 성능, 혼동행렬, 오분류 이미지를 확인해야 하는 이유를 안다.
validation set으로 모델을 선택하고 test set으로 최종 평가해야 함을 이해했다.
오토인코더, GAN, 확산 모델의 큰 차이를 설명할 수 있다.
다음 장 연결
다음 장은 이 책의 마지막 장인 미니 프로젝트다. 지금까지 배운 학습 루프, CNN, Attention, Transformer, 전이학습을 하나의 작은 프로젝트로 묶는다.
중요한 것은 높은 정확도만 얻는 것이 아니다. 데이터를 어떻게 나누었는지, 어떤 모델을 선택했는지, 어떤 오류가 남았는지, AI 도구를 사용했다면 무엇을 검증했는지까지 기록해야 한다. 딥러닝 프로젝트는 모델을 학습시키는 일에서 끝나지 않는다. 결과를 해석하고, 실패를 분석하고, 다음 실험을 설계하는 과정까지 포함한다.
참고
1PyTorch Transfer Learning for Computer Vision Tutorial
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.