반려동물 품종 분류기 만들기 (4) Baseline: CNN을 처음부터 학습
conv 블록 3개짜리 CNN을 처음부터 학습해 baseline을 세웁니다. augmentation 유무에 따른 과적합 차이를 수치로 확인하고, 3천 장 규모에서 scratch 학습의 한계를 기록했습니다.
왜 scratch부터 하는가
바로 사전학습 모델을 쓰면 더 높은 점수가 나온다는 것은 이미 알고 있습니다. 그래도 scratch 학습을 먼저 하는 이유는, transfer learning의 효과를 숫자로 말하려면 비교 대상이 필요하기 때문입니다. 무작위 추측이 2.7%(1/37)인 문제에서 작은 CNN이 어디까지 가는지를 먼저 기록합니다.
모델: SmallCNN
딥러닝 기초 9편에서 다룬 conv, BatchNorm, ReLU, MaxPool 구성을 그대로 쌓은 3블록 CNN입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import torch.nn as nn
def conv_block(in_ch: int, out_ch: int) -> nn.Sequential:
return nn.Sequential(
nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1),
nn.BatchNorm2d(out_ch),
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
)
class SmallCNN(nn.Module):
def __init__(self, num_classes: int = 37):
super().__init__()
self.features = nn.Sequential(
conv_block(3, 32),
conv_block(32, 64),
conv_block(64, 128),
)
self.head = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Linear(128, num_classes),
)
def forward(self, x):
return self.head(self.features(x))
파라미터는 약 10만 개입니다. AdaptiveAvgPool2d(1)로 공간 차원을 평균 내기 때문에 입력 크기가 달라져도 동작하고, Flatten 뒤에 큰 Linear 층을 두는 것보다 파라미터가 훨씬 적습니다.
학습 루프
optimizer는 AdamW(learning rate 1e-3), 손실은 CrossEntropyLoss, 30 epoch 학습입니다. 루프 구조는 PyTorch 기초 3편에서 정리한 형태 그대로이고, epoch마다 validation accuracy를 재서 최고 기록일 때만 체크포인트를 저장합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
best_acc = 0.0
for epoch in range(30):
model.train()
for x, y in train_loader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
acc = evaluate(model, val_loader, device)
if acc > best_acc:
best_acc = acc
torch.save(model.state_dict(), "outputs/small_cnn/best.pt")
마지막 epoch의 모델이 아니라 validation 기준 최고 모델을 남기는 것이 핵심입니다. 과적합이 시작된 뒤의 epoch는 저장되지 않습니다.
결과: augmentation이 가른 것
같은 모델을 augmentation 없이(Resize와 CenterCrop만) 한 번, 3편의 train_tf로 한 번 학습했습니다.
| 실험 | train accuracy | validation accuracy |
|---|---|---|
| augmentation 없음 | 99.2% | 31.8% |
| RandomResizedCrop + Flip | 78.5% | 41.3% |
augmentation이 없으면 train은 사실상 전부 맞히는데 validation은 31.8%에 멈춥니다. 2,944장을 30번 반복해서 보는 동안 모델이 데이터를 외워버린 결과입니다. augmentation을 켜면 train accuracy는 오히려 내려가지만 validation은 9.5%p 올라갑니다. 딥러닝 기초 8편에서 정리한 과적합과 규제의 관계가 그대로 재현됩니다.
Baseline이 말해주는 것
최종 baseline은 validation accuracy 41.3%입니다. 무작위 추측 2.7%보다는 훨씬 높으니 모델이 뭔가 배우긴 했지만, 세 번에 두 번 가까이 틀리는 수준이라 분류기로 쓸 수는 없습니다.
원인은 모델 크기가 아니라 데이터 양입니다. 블록을 4개, 5개로 늘리고 dropout을 넣어봐도 validation은 45% 근처를 넘지 못했습니다. 품종 구분에 필요한 미세한 시각 특징(귀 모양, 털 결, 주둥이 길이)을 3천 장으로 처음부터 배우기에는 데이터가 부족합니다.
이 한계가 다음 편의 출발점입니다. ImageNet 120만 장으로 이미 시각 특징을 배워둔 모델을 가져오면 이 문제가 어떻게 달라지는지 확인합니다.
다음 글: 반려동물 품종 분류기 만들기 (5) Transfer learning: ResNet fine-tuning