← ~/notes · 3 min read

한식 분류 88.21% — AI4Food 데이터셋 + EfficientNetV2

AI Hub 1.6TB 한식 데이터셋으로 EfficientNetV2를 학습. Top-1 88.21%. Xception 대비 어떤 게 나았는지 정리.

목차
  1. 데이터셋 — AI4Food-NutritionDB
  2. 인프라
  3. 모델 비교
  4. 학습 설정
  5. 클래스별 정확도 — 잘 맞는 것 vs 못 맞는 것
  6. 잡힌 함정
  7. 1) 데이터 라벨 노이즈
  8. 2) 음식 사진의 다양성
  9. 3) 클래스 불균형
  10. 4) GPU 메모리
  11. 배포 — ONNX + 모바일
  12. 만들면서 알게 된 것들
  13. 다음 단계

식단 분석 앱에 들어갈 분류기를 만들고 싶었다. 한식 사진 한 장 → 음식 이름 → 영양정보 매칭.

문제는 한식 데이터셋. 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-1Top-5학습 시간
Xception22M84.3%96.1%8h
EfficientNetV2-S21M86.7%97.3%6h
EfficientNetV2-M54M88.21%97.8%11h
EfficientNetV2-L119M88.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개보다 사용자에겐 더 가치 있다.