한식 분류 88.21% — AI4Food 데이터셋 + EfficientNetV2
AI Hub 1.6TB 한식 데이터셋으로 EfficientNetV2를 학습. Top-1 88.21%. Xception 대비 어떤 게 나았는지 정리.
목차
식단 분석 앱에 들어갈 분류기를 만들고 싶었다. 한식 사진 한 장 → 음식 이름 → 영양정보 매칭.
문제는 한식 데이터셋. ImageNet엔 한식 클래스가 거의 없고, Food-101도 한식 비중 낮음. AI Hub의 AI4Food-NutritionDB(1.6TB)가 답이었다.
데이터셋 — AI4Food-NutritionDB
AI Hub에서 무료. 한식 위주 약 200개 클래스, 각 클래스당 1,000~5,000장.
| 카테고리 | 클래스 예시 |
|---|---|
| 밥/면 | 비빔밥, 김밥, 잔치국수, 칼국수, … |
| 국/찌개 | 김치찌개, 된장찌개, 부대찌개, … |
| 반찬 | 김치, 깍두기, 시금치무침, … |
| 주요리 | 불고기, 갈비, 삼겹살, … |
| 분식 | 떡볶이, 순대, 튀김, … |
용량 1.6TB. 다운로드만 4시간.
인프라
- GPU: 학교 연구실 RTX 3090 한 대 (24GB)
- 저장: 외장 SSD 2TB (1.6TB + 워크스페이스)
- 프레임워크: PyTorch 2.1 + timm
대학 GPU 서버라 시간제. 학습 한 번에 12시간 정도 잡고 돌렸다.
모델 비교
처음엔 Xception으로 시작. 한식 도메인 모델이 거의 없으니 ImageNet pretrained 위에 fine-tune.
| 모델 | 파라미터 | Top-1 | Top-5 | 학습 시간 |
|---|---|---|---|---|
| Xception | 22M | 84.3% | 96.1% | 8h |
| EfficientNetV2-S | 21M | 86.7% | 97.3% | 6h |
| EfficientNetV2-M | 54M | 88.21% | 97.8% | 11h |
| EfficientNetV2-L | 119M | 88.4% | 98.0% | 18h (메모리 부족) |
EfficientNetV2-M에서 멈춤 — 88.21%. Large는 마진이 작고 학습 비용 ↑↑.
학습 설정
import timm
import torch
from torch.utils.data import DataLoader
model = timm.create_model('efficientnetv2_m',
pretrained=True,
num_classes=200)
# 일반적인 augmentation
from timm.data import create_transform
train_tfm = create_transform(
input_size=384, is_training=True,
auto_augment='rand-m9-mstd0.5',
interpolation='bicubic',
re_prob=0.25
)
# AdamW + Cosine schedule
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-2)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)
# Mixup + CutMix (timm 내장)
mixup_fn = timm.data.Mixup(
mixup_alpha=0.2, cutmix_alpha=1.0, prob=0.5,
label_smoothing=0.1, num_classes=200
)
특이점: input_size 384 (기본 224 대신). 음식 사진은 디테일이 중요해서 큰 입력이 효과 ↑.
클래스별 정확도 — 잘 맞는 것 vs 못 맞는 것
| 클래스 | Top-1 |
|---|---|
| 김밥 | 99% |
| 김치찌개 | 96% |
| 비빔밥 | 95% |
| 떡볶이 | 94% |
| … | … |
| 시금치무침 | 71% |
| 가지무침 | 68% |
| 무생채 | 64% |
무침/나물류가 한결같이 약함. 시각적으로 비슷하고 (녹색/주황 야채), 클래스 간 구분이 어렵다. 사람도 사진만 보고 시금치/가지/도라지 헷갈림.
이건 클래스 합치기로 해결: “잎채소 무침” 통합 클래스 → 정확도 85% 이상으로 회복. 단, 영양 정보 계산엔 디테일이 필요하니 후처리 필요.
잡힌 함정
1) 데이터 라벨 노이즈
같은 음식이 다른 클래스 라벨로 들어간 경우 ~5%. “잡채” vs “당면” 같은 거. 학습엔 큰 영향 없지만 검증할 때 헷갈림.
2) 음식 사진의 다양성
같은 김치찌개라도 — 식당 / 가정 / 인스턴트 — 시각적으로 매우 다름. 모델이 “스타일”에 과적합되지 않게 augmentation 강하게.
3) 클래스 불균형
비빔밥 5,000장 vs 무생채 800장. 자주 나오는 음식이 dominant. WeightedRandomSampler로 minority class 샘플링 비율 ↑.
class_counts = [...] # 각 클래스 샘플 수
weights = 1.0 / torch.tensor(class_counts, dtype=torch.float)
sample_weights = weights[labels]
sampler = WeightedRandomSampler(sample_weights, num_samples=len(labels), replacement=True)
4) GPU 메모리
EfficientNetV2-L은 384×384 + batch 32에서 24GB가 넘는다. gradient checkpointing 켜야 들어감. 학습 속도 30% 떨어짐.
배포 — ONNX + 모바일
PyTorch 그대로 모바일에 못 올림. ONNX → CoreML / TFLite 변환.
import torch.onnx
model.eval()
dummy = torch.randn(1, 3, 384, 384)
torch.onnx.export(model, dummy, "food.onnx",
input_names=['input'],
output_names=['output'],
opset_version=17,
dynamic_axes={'input': {0: 'batch'}})
iOS는 ONNX → CoreML (coremltools), Android는 TFLite. 양자화(int8)로 모델 크기 50% ↓, 정확도 1% ↓ 정도.
만들면서 알게 된 것들
도메인 데이터셋의 가치. ImageNet pretrained으로 시작해도 한식 도메인 fine-tune 데이터가 있어야 88%. 데이터셋 자체가 한국 정부(AI Hub)에서 무료로 풀린 게 다행.
EfficientNetV2가 Xception 후속. 같은 파라미터 수에서 정확도 더 높고 학습도 빠름. 첫 선택지.
input_size 384가 음식엔 더 좋다. 디테일(질감, 색깔 미묘한 차이)이 분류 단서.
80~90% 영역에서 추가 1%는 비용 ↑↑. 모델 키우는 것보다 데이터 정제, 클래스 통합, 후처리가 효율적.
다음 단계
- 클래스 200 → 사용자가 자주 보는 50으로 축소 (long tail 무시)
- 영양 정보 매칭 DB 구축 (음식 → 칼로리 / 단백질 / 탄수화물)
- 모바일 추론 200ms 이하 (현재 양자화 후 350ms)
작은 모델로 잘 분류된 50개가 큰 모델 200개보다 사용자에겐 더 가치 있다.