MINERVA 도메인 분류기 04: 실험 설계와 학습 파이프라인

공정 비교의 요건 — 분할, Best Epoch, 재현성 표준화

8개 후보 모델을 공정하게 비교하기 위한 실험 파이프라인 설계를 다룬다. Train/Val/Test 분할 전략(60/20/20 vs 70/10/20)의 근거, Best Epoch 추적·복원 메커니즘, random_state·LabelEncoder·training_summary 표준화, 그리고 K-Fold CV가 드러낸 단일 split의 한계를 통합적으로 정리한다.

Data Science
Machine Learning
Deep Learning
NLP
MINERVA
저자

Kwangmin Kim

공개

2026년 04월 17일

1 이 편의 위치

03편에서 후보 8개 모델을 커버리지 매트릭스로 확정했다. 이제 이 8개를 공정하게 비교하는 실험 파이프라인이 필요하다. 모델마다 분할 전략·학습 루프·체크포인트 규칙이 제각각이면, 05편의 정확도 비교는 신뢰도를 잃는다. 이 편의 목적은 “모든 모델이 같은 조건에서 평가됐다”는 것을 방법론적으로 증명하는 것이다.

2 공정 비교의 요건

단일 모델 fine-tuning이라면 실험 설계의 엄밀성은 그리 중요하지 않다. 하지만 여러 모델을 한 평면에서 비교하는 순간 설계의 미세 차이가 결과의 차이로 증폭된다. 공정성을 위협하는 주요 축 네 가지가 있다.

  • 데이터 분할 차이 — 모델 A는 train 80%, 모델 B는 train 70%에 학습시키면 정확도 1~2%p는 분할의 영향이지 모델의 영향이 아니다.
  • 학습 종료 기준 차이 — 모델 A는 10 epoch 고정, 모델 B는 Early Stopping이면 overfitting 여부가 달라진다.
  • 최종 체크포인트 차이 — 모델 A는 마지막 epoch 가중치, 모델 B는 val_acc 최고 epoch 가중치를 쓰면 공정 비교가 아니다.
  • 라벨 매핑 차이 — 모델마다 14개 클래스의 정수 인덱스가 다르면 predictions.pkl 비교가 불가능해진다.

이 네 축을 각각 어떻게 표준화했는지가 이 편의 본론이다.

3 데이터 분할 전략 — 60/20/20 vs 70/10/20

분할 전략에서 먼저 결정해야 할 것은 test 비율이다. 본 프로젝트는 test_size=0.2, random_state=42, stratify=labels로 모든 모델이 동일한 2:0.2 비율의 test split을 공유한다. 여기서 stratify는 14개 그룹이 test에서도 같은 비율로 유지되도록 강제한다. stratify가 없으면 번호(463건)처럼 작은 그룹이 test에 4~5건만 포함되는 경우가 생기고, 그 그룹의 Test Acc 변동성이 극단적으로 커진다.

나머지 80%를 train과 validation으로 어떻게 쪼개는지에서 모델 간 차이가 생긴다. 사전학습 모델(KoBERT·mBERT·ALBERT·DistilKoBERT·KLUE·XLM-RoBERTa)은 70/10/20(= train 70%, val 10%, test 20%)을 쓴다. from-scratch BiLSTM은 60/20/20을 쓴다.

이 차이의 근거는 학습 종료 기준이다.

  • 사전학습 모델은 5 epoch 내외에서 수렴한다. val set은 Best Epoch 판정에만 쓰이고 Early Stopping까지는 필요 없다. val이 10%(약 770건)면 Best Epoch 판별에 충분하다.
  • BiLSTM은 사전학습 없이 출발하므로 수렴에 20~30 epoch이 필요하다. 이 구간에서 Early Stopping 판정이 중요해진다. val을 20%(약 1,540건)로 넉넉히 잡아야 val loss의 변동성이 낮아져 Early Stopping의 안정성이 올라간다.

이 분할 차이는 각 모델에 맞는 최적 설정이지만, train 크기가 다르다는 것 자체가 공정성의 잠재적 약점이다. BiLSTM은 train이 10%p 적다. 이것이 단일 split에서 BiLSTM이 불리하게 나올 수 있는 한 원인이고, 나중에 실험 6 K-Fold에서 BiLSTM이 +5%p 급상승하는 이유의 일부로 해석된다.

4 Best Epoch 추적과 복원

사전학습 모델의 fine-tuning은 5 epoch 근처에서 overfitting이 시작된다. 10 epoch까지 돌리면 val_acc는 일시적으로 떨어졌다가 train에만 맞춰지는 구간이 생긴다. 따라서 “마지막 epoch 가중치”가 아니라 “val_acc가 가장 높았던 epoch의 가중치”를 최종 모델로 삼아야 한다.

이 과정을 표준화한 Best Epoch 추적 로직은 개념적으로 간단하다.

best_val_acc = 0.0
best_state = None
best_epoch = -1

for epoch in range(num_epochs):
    train_one_epoch(model, train_loader)
    val_acc = evaluate(model, val_loader)

    if val_acc > best_val_acc:
        best_val_acc = val_acc
        best_state = copy.deepcopy(model.state_dict())
        best_epoch = epoch

    print(f"Epoch {epoch}: val_acc={val_acc:.4f}")

# 학습 종료 후 Best Epoch 가중치 복원
model.load_state_dict(best_state)
test_acc = evaluate(model, test_loader)

이 로직이 모든 모델(01 BiLSTM, 04 mBERT, 05 ALBERT, 06 DistilKoBERT, 07 KLUE, 08 XLM)에 동일한 방식으로 적용된다. 여기서 copy.deepcopy가 중요하다. 얕은 복사를 하면 이후 epoch의 학습이 best_state를 덮어쓴다.

4.1 03 KoBERT의 누락과 패치 사례

초기 설계에서 03 KoBERT 노트북만 Best Epoch 추적 로직이 누락되어 있었다. 그 결과 첫 학습에서 KoBERT는 마지막 epoch(epoch 4 근처)의 가중치로 평가되어 Test Acc 96.36%를 기록했다. 다른 모델들이 Best Epoch 기준으로 평가되는데 KoBERT만 “마지막 epoch 기준”이면 불공정 비교다.

패치 후 재학습 결과 KoBERT의 실제 Test Acc는 95.84%로 정정됐다. 0.52%p 차이는 숫자로는 작아 보이지만 05편의 McNemar Holm 보정에서 상위 5개 모델이 통계적 등급 하나로 묶이는 경계선 근처의 차이다. 이 차이 때문에 KoBERT가 “KLUE-RoBERTa보다 약간 낫다”로 잘못 읽힐 여지가 있었다.

패치·재학습 후에는 KoBERT와 KLUE가 통계적 동등(ns)으로 정리되어 “KLUE가 KoBERT의 후계자”라는 일관된 메시지가 유지됐다. 이 사례는 한 모델의 세팅 누락이 전체 비교 결론을 뒤집을 수 있다는 실제 사례로 기록되어 있다.

4.2 09 multilingual-e5의 예외

09 multilingual-e5는 임베딩 동결 + Logistic Regression 구조라 Best Epoch 개념이 없다. LogReg는 lbfgs optimizer로 convex 문제를 수렴까지 풀기 때문에 epoch별 체크포인트가 의미를 갖지 않는다. 예외로 처리되어 학습 요약에서도 epochs_trained가 N/A로 기록된다.

5 재현성 표준화

5.1 random_state 통일

모든 모델이 random_state=42, test_size=0.2, stratify=labels로 동일한 test split을 공유한다. 이 덕분에 trained_models/comparison/predictions.pkl에 저장된 각 모델의 예측이 동일한 test 샘플 1,540건에 대한 예측임이 보장된다. McNemar 검정의 전제 조건이 이 동일 test set이다. 만약 모델별로 test 구성이 달랐다면 페어드 비교 자체가 성립하지 않는다.

5.2 LabelEncoder 통일

14개 클래스에 정수 인덱스를 부여하는 방법이 여럿이다. 알파벳순·한글 순·등록순·빈도순 등 어떤 정렬이든 일관성만 있으면 된다. 본 프로젝트는 LabelEncoder(sorted=True)로 클래스 이름을 한글 유니코드 정렬 기준으로 매핑한다. 그 결과 모든 모델이 동일한 {0: 값, 1: 날짜, 2: 내용, ...} 매핑을 공유한다.

이 표준화가 중요한 이유는 20 비교 노트북이 predictions.pkl을 재사용할 때다. 모델별로 라벨 매핑이 다르면 정수 인덱스 5가 어떤 모델에서는 분류이고 다른 모델에서는 가 된다. 예측 비교가 그대로 어긋난다. LabelEncoder 통일은 inter-model comparison의 기반 인프라다.

5.3 training_summary.pkl 표준 필드

각 모델 학습 노트북은 학습 완료 시점에 training_summary.pkl을 저장한다. 표준 필드 7개는 다음과 같다.

필드 타입 용도
test_accuracy float 최종 Test Acc
total_params int 파라미터 수 (배포 의사결정의 기본 정보)
epochs_trained int 실제 학습된 epoch 수 (Best Epoch 기준)
history dict epoch별 train_loss·val_acc (Learning Curve 분석)
macro_f1 float 클래스별 평균 F1
weighted_f1 float 샘플 수 가중 F1
per_class_f1 dict 각 14 클래스의 F1

이 7개 필드가 통일되어 있기 때문에 20 비교 노트북이 8개 pkl을 루프로 로드해 하나의 DataFrame으로 병합할 수 있다. 필드가 모델마다 달랐다면 비교 코드가 각 모델별로 분기 처리되어 유지보수가 무너진다.

6 학습 파이프라인 표준 구조

각 모델 학습 노트북(01~09)은 다음과 같은 공통 구조를 따른다.

Cell 0: 환경 설정 (device, random_state, torch.manual_seed)
Cell 1-5: 데이터 로드 → 분할 → 토크나이징
Cell 6-10: 모델 정의 + DataLoader 구성
Cell 11-15: 학습 루프 (Best Epoch 추적 포함)
Cell 16-20: 평가 (Test Acc + Macro/Weighted F1 + per-class F1)
Cell 21-25: training_summary.pkl 저장
Cell 26-30: 리포트 셀 (수치 요약 + 시각화)

이 구조가 표준화된 덕분에 새 모델을 추가할 때(예: 07 KLUE, 08 XLM-RoBERTa) 03 KoBERT 노트북을 복제한 후 모델·토크나이저 부분만 교체하면 된다. 나머지 학습 루프·평가·저장 로직은 그대로 쓸 수 있다. 이는 단순한 코드 중복이 아니라 공정 비교의 핵심 보증 장치다.

모델 파일명도 통일되어 있다. 각 모델의 학습된 가중치는 trained_models/{folder}/{folder}_domain_classifier.pth에 저장되며, 예를 들어 KoBERT는 trained_models/kobert/kobert_domain_classifier.pth다. 이 명명 규칙 덕분에 20 비교 노트북이 MODELS 메타데이터 딕셔너리 하나로 8개 모델 경로를 일괄 관리한다.

7 단일 split의 한계 — K-Fold CV가 드러낸 것

표준화된 실험 파이프라인이 있다 해도 단일 train/test split은 본질적 한계를 가진다. 한 번의 분할 결과가 “운 좋은” 혹은 “운 나쁜” 결과일 가능성이 항상 있다. test 셋을 어떻게 뽑느냐에 따라 정확도가 1~2%p 흔들린다.

이 한계가 실험 6 K-Fold CV에서 정면으로 드러났다. BiLSTM의 단일 split Test Acc는 94.81%였고, 특히 5-Fold CV 중 fold 하나에서는 91.30%까지 떨어졌다. 반면 5-fold 평균은 96.18% ± 0.41%로 KLUE-RoBERTa(96.88%)와 사실상 동급이었다.

이 관찰은 두 가지를 동시에 말한다.

첫째, 단일 split 결과의 불안정성. 같은 모델·같은 파이프라인을 다른 random_state로 돌리면 정확도가 몇 %p 달라질 수 있다. BiLSTM처럼 파라미터가 적고 학습 데이터 크기에 민감한 모델일수록 이 변동성이 크다.

둘째, 단일 split의 과소평가 위험. BiLSTM은 단일 split에서 “6위”로 보였지만 K-Fold 기준으로는 상위 그룹에 속한다. 파이프라인의 표준화가 완벽해도 단일 split의 샘플링 편향은 표준화로 해결되지 않는다.

7.1 왜 처음부터 K-Fold를 안 했는가

이 질문은 설계 리뷰에서 반드시 나온다. 답은 실무적이다.

  • 학습 시간 부담 — 5-fold 기준으로 각 모델을 5번 학습해야 한다. 특히 mBERT(1회 ~25분)는 5-fold에 약 2시간이 필요하다. 8개 모델을 모두 5-fold 돌리면 총 3시간+ 학습에 다시 8배 시간이 들어간다.
  • 단일 split의 McNemar 비교가 먼저 필요 — 05편의 페어드 비교가 동일 test set을 전제로 하므로, 8개 모델이 같은 test set에서 어떻게 어긋나는지를 먼저 측정해야 했다.
  • K-Fold는 단일 split의 결론을 검증하는 후속 단계로 배치하는 것이 시간 예산 측면에서 합리적이다.

실험 6 K-Fold는 전체 모델이 아니라 BiLSTM·ALBERT 두 개만 돌렸다. 이 선택도 실무적이다 — 단일 split에서 통계적 동등이었던 두 모델이 K-Fold에서도 동등인지가 핵심 질문이었고, 다른 6개 모델의 K-Fold는 비슷한 범위일 것으로 추정되어 시간 예산에서 제외됐다. paired t-test 결과 p=0.73으로 두 모델은 K-Fold에서도 통계적 동등이 확인됐다.

8 파이프라인의 메타 교훈 세 가지

이 편의 실험 설계 과정에서 드러난 메타 교훈이 셋 있다.

첫째, 표준화는 “같은 코드 반복”이 아니라 “비교 가능성의 기반 인프라”다. LabelEncoder·random_state·training_summary 스키마 같은 세부 표준화가 없으면 20 비교 노트북의 8개 모델 통합 분석이 각 모델마다 특수 처리로 갈라진다. 표준화에 들인 시간이 이후 모든 비교 분석의 속도를 결정한다.

둘째, 한 모델의 누락이 전체 결론을 뒤집을 수 있다. 03 KoBERT의 Best Epoch 추적 누락은 초기 결과에서 KoBERT를 KLUE보다 높게 보이게 만들었다. 패치·재학습 후에야 두 모델이 통계적 동등으로 정리됐다. 8개 모델을 한 평면에서 비교하려면 모든 모델이 같은 세팅을 공유해야 하고, 예외(예: mE5의 convex 수렴)는 예외임을 명시해야 한다.

셋째, 단일 split의 결론은 잠정적이다. 표준화·Best Epoch·재현성이 완벽해도 단일 split은 한 번의 sampling 결과일 뿐이다. 중요한 결론(예: “A와 B가 통계적 동등”)은 K-Fold나 bootstrap으로 재검증해야 확정된다. 실험 6이 이 단계를 맡는다.

9 다음 편 예고

05편에서는 이 표준 파이프라인이 산출한 정확도·F1·McNemar 검정 결과를 통계적으로 해석한다. 95% 신뢰구간 계산, 페어드 McNemar 검정, Holm 다중비교 보정, 파레토 프론티어 시각화가 주제다. 표준화된 파이프라인 덕분에 이 통계적 분석이 비로소 의미를 가진다.

10 관련 주제

이 편의 선행

시리즈 내 다음 편들

관련 카테고리

Subscribe

Enjoy this blog? Get notified of new posts by email: