14차시 전이학습 리포트 라인별 해설

작성일: 2026-01-29 · 목표: 코드 한 줄씩 이해 + 실험 비교까지

Transfer Freeze/Partial/Full Staged LR Aug strength

이번 차시에서 진짜 중요한 것

🧭
이 리포트 사용법

코드 아래 표를 보고 “한 줄 의미”를 먼저 이해한 뒤, ‘왜 핵심인지’ 문단을 읽으면 됩니다.

이 리포트는 “코드만 나열”이 아니라, **코드 한 줄마다 (1) 무엇을 하는지 (2) 옵션이 뭔지 (3) 왜 핵심인지**를 붙여서 초심자도 따라가게 만든 버전이다. 14차시의 핵심은 4가지다. 1) Freeze / Partial / Full: 어디까지 학습할지 결정 2) 전이학습 vs 파인튜닝: FC만 학습인지, backbone도 학습인지 3) 단계적 학습률: FC를 먼저 큰 LR로, backbone은 작은 LR로 4) 증강 강도 비교: 약/중/강 증강에서 성능이 어떻게 달라지는지

전이학습 vs 파인튜닝 (코드로 구분)

전이학습 기본형

pretrained 로드 → FC 교체 → (대개 Freeze로 시작)

from torchvision import models
import torch.nn as nn

net = models.resnet18(pretrained=True)

fc_in = net.fc.in_features
n_output = 10
net.fc = nn.Linear(fc_in, n_output)
코드 한 줄의미(초심자용)
net = models.resnet18(pretrained=True)ImageNet 등으로 미리 학습된 ResNet-18 가중치를 불러온다(=전이학습 시작).
fc_in = net.fc.in_features기존 FC가 받는 입력 차원을 확인한다(ResNet-18은 보통 512).
net.fc = nn.Linear(fc_in, n_output)우리 데이터의 클래스 수(n_output)에 맞게 ‘마지막 분류기(FC)’를 교체한다(필수).
💡
왜 핵심인지

왜 핵심인가? - backbone(특징추출부)은 이미 ‘일반적인 시각 특징(엣지/패턴/형태)’을 잘 배움 - 데이터가 적을 때 처음부터 학습보다 훨씬 빠르고 안정적 - 대신 “FC 교체 + 학습 범위(Freeze/Partial/Full) 선택”이 성능을 좌우함

Freeze / Partial / Full (어디까지 학습?)

🧊
결정 1: 학습 범위

가장 중요한 실험 축: 업데이트할 파라미터의 범위

# (1) Freeze: backbone 고정, FC만 학습
for p in net.parameters():
    p.requires_grad = False
for p in net.fc.parameters():
    p.requires_grad = True

# (2) Partial: 마지막 블록 + FC 학습 (예: ResNet layer4)
for p in net.layer4.parameters():
    p.requires_grad = True

# (3) Full: 전체 학습
for p in net.parameters():
    p.requires_grad = True
코드 한 줄의미(초심자용)
for p in net.parameters(): p.requires_grad = False모든 파라미터 업데이트를 막는다(=backbone 포함 전부 고정).
for p in net.fc.parameters(): p.requires_grad = TrueFC만 학습 가능하게 풀어준다(전이학습 기본 형태).
for p in net.layer4.parameters(): p.requires_grad = True마지막 블록(layer4)만 추가로 학습(=partial fine-tuning).
for p in net.parameters(): p.requires_grad = True전체를 학습 가능하게 만든다(=full fine-tuning).
왜 핵심인지

왜 핵심인가? - Freeze: 가장 안정적(데이터 적을 때 과적합↓), 빠름 - Partial: 성능/안정성 균형이 좋아 가장 많이 씀(고수준 특징만 우리 데이터에 맞게 조정) - Full: 표현력은 최대지만 LR 튜닝이 어렵고 과적합/불안정 가능

단계적 학습률 (왜 2단계로 나누나?)

📉
결정 2: 학습률 운영

FC는 빠르게, backbone은 조심스럽게

import torch.optim as optim

# Phase 1) Freeze 상태에서 FC만 빠르게 학습 (LR 크게)
for p in net.parameters(): p.requires_grad = False
for p in net.fc.parameters(): p.requires_grad = True

optimizer = optim.SGD(
    filter(lambda p: p.requires_grad, net.parameters()),
    lr=1e-2, momentum=0.9, weight_decay=1e-4
)

# Phase 2) Partial 또는 Full로 확장 (LR 작게)
for p in net.layer4.parameters(): p.requires_grad = True   # partial 예시
optimizer = optim.SGD(
    filter(lambda p: p.requires_grad, net.parameters()),
    lr=1e-3, momentum=0.9, weight_decay=1e-4
)
코드 한 줄의미(초심자용)
filter(lambda p: p.requires_grad, net.parameters())학습 가능한 파라미터만 optimizer에 넣는다(안 넣으면 freeze가 의미 없어짐).
lr=1e-2 (Phase1)FC만 학습하므로 비교적 큰 LR도 안정적으로 가능(빠르게 적응).
lr=1e-3 (Phase2)backbone을 건드리면 작은 LR 필수(기존 지식 파괴 방지).
왜 핵심인지

왜 핵심인가? - pretrained 가중치는 이미 좋은 위치에 있음 → 큰 LR로 건드리면 ‘기존 지식이 망가짐(망각)’ - 그래서 1단계(FC만)에서 빠르게 적응시키고, 2단계(backbone 일부/전체)에서 작은 LR로 조심스럽게 미세조정하는 게 안전하고 성능도 잘 나옴

증강 강도(약/중/강)와 성능 비교

🎛️
결정 3: 증강 강도

강할수록 좋다 X → “균형점”이 핵심

from torchvision import transforms

aug_weak = transforms.Compose([
    transforms.Resize((224,224)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.ToTensor(),
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),
])

aug_medium = transforms.Compose([
    transforms.Resize((256,256)),
    transforms.RandomResizedCrop((224,224), scale=(0.8,1.0)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(10),
    transforms.ToTensor(),
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),
])

aug_strong = transforms.Compose([
    transforms.Resize((256,256)),
    transforms.RandomResizedCrop((224,224), scale=(0.6,1.0)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(15),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.05),
    transforms.ToTensor(),
    transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),
    transforms.RandomErasing(p=0.25, scale=(0.02, 0.15)),
])
코드 한 줄의미(초심자용)
RandomHorizontalFlip(p=0.5)50% 확률로 좌우 반전(가장 기본 증강).
RandomResizedCrop(..., scale=(0.8,1.0))원본의 80~100% 영역을 랜덤 크롭 → medium 증강(너무 과하지 않게).
ColorJitter(...)밝기/대비/채도를 흔들어 색 변화에 강해지게 함(강증강 요소).
RandomErasing(p=0.25, ...)이미지 일부를 지워 가림(occlusion)에 강하게 하지만 과하면 학습이 어려워짐).
왜 핵심인지

왜 핵심인가? - 약한 증강: 학습은 쉽지만 과적합 위험 - 강한 증강: 일반화에 도움될 수 있지만, 과하면 ‘데이터가 너무 달라져’ 성능이 떨어질 수도 있음 - 그래서 약/중/강을 나눠서 “성능이 가장 좋은 균형점”을 찾는 것이 실험의 핵심

학습/평가 루프에서 ‘무엇이 계산되는가’

🧮
loss/acc가 어디서 나오나?

여기 흐름을 정확히 알면 실수가 확 줄어듭니다.

running_loss = 0.0
total = 0

net.train()
for images, labels in train_loader:
    outputs = net(images)
    loss = criterion(outputs, labels)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    running_loss += loss.item() * images.size(0)
    total += labels.size(0)

epoch_loss = running_loss / total

net.eval()
correct = 0
total = 0
with torch.no_grad():
    for images, labels in test_loader:
        outputs = net(images)
        predicted = torch.argmax(outputs, dim=1)
        correct += (predicted == labels).sum().item()
        total += labels.size(0)
acc = 100 * correct / total
코드 한 줄의미(초심자용)
net.train()드롭아웃/배치정규화가 ‘학습 모드’로 동작하게 함.
optimizer.zero_grad()이전 배치의 gradient를 초기화(안 하면 누적돼서 학습이 망가짐).
loss.backward()오차를 기준으로 gradient 계산(역전파).
optimizer.step()계산된 gradient로 파라미터 업데이트.
running_loss += loss.item() * images.size(0)배치 평균 loss × 배치 샘플 수로 누적(마지막 배치 크기가 달라도 공정한 평균).
with torch.no_grad()평가에서는 gradient 계산을 꺼서 더 빠르고 메모리 절약.
predicted = torch.argmax(outputs, dim=1)각 샘플에서 가장 큰 점수의 클래스 인덱스를 예측값으로 선택.
왜 핵심인지

왜 핵심인가? - loss/acc가 ‘어디서 계산되는지’ 정확히 이해하면 디버깅이 쉬워짐 - running_loss를 배치 크기로 가중 평균내는 방식은 매우 자주 쓰이는 정석

실험 결과 정리 템플릿

🧾
보고서 퀄리티를 올리는 1줄 규칙

“무엇을 바꿨는지”와 “결과(Acc)”를 표로 묶어라.

실험명Train ModeAugPhase1 LRPhase2 LRBest Acc해석(한 문장)
freeze + weakfreezeweak1e-2-__baseline(안정/빠름)
partial + mediumpartialmedium1e-21e-3__권장 조합 후보
full + mediumfullmedium1e-21e-3__튜닝 성공 시 최고점
partial + strongpartialstrong1e-21e-3__과증강이면 성능↓ 가능

각 실험에서 Best Acc를 채우고, 해석(한 문장)만 붙이면 보고서가 완성됩니다.