모델이 낸 점수가 "실력"인지 "본 적 있는 문제를 다시 푼 것"인지는 데이터를 어떻게 나눴는가에서 결정된다. 이 강에서는 train/val/test 분할의 이유, 그룹 단위 분할, 그리고 test 세트를 단 한 번만 여는 규율을 다룬다.
모델 평가의 목표는 일반화 성능(generalization performance), 즉 "학습 때 본 적 없는 새 데이터에서 얼마나 잘하는가"를 추정하는 것이다. 학습에 쓴 데이터로 평가하면 이 추정이 불가능하다. 모델은 학습 데이터를 어느 정도 암기하기 마련이고, 암기한 문제를 다시 풀게 하면 점수는 실력이 아니라 기억력을 측정하게 된다.
그래서 데이터를 최소한 두 조각으로 나눈다. 그런데 실제 연구에서는 두 조각으로도 부족하다. 연구 과정에는 "모델을 학습시키는 일" 말고도 "어떤 설정이 좋은지 고르는 일"이 있기 때문이다. 프롬프트 문구, 하이퍼파라미터(hyperparameter), few-shot 예시 개수 같은 선택을 하려면 어딘가에서 점수를 봐야 하는데, 그 점수를 최종 평가용 데이터에서 보는 순간 선택 과정 자체가 그 데이터에 맞춰진다. 그래서 세 조각이 필요하다.
| 조각 | 역할 | 여는 횟수 |
|---|---|---|
| train (학습 세트) | 모델 학습, 프롬프트·예시 개발 | 제한 없음 |
| validation (검증 세트) | 설정 비교와 선택 (모델 선택, 프롬프트 선택, 조기 종료) | 여러 번, 단 선택에만 사용 |
| test (평가 세트) | 최종 성능 보고 | 원칙적으로 한 번 |
파인튜닝 없는 LLM 평가 연구에도 이 구조는 그대로 적용된다. 한국어 초성 줄임말을 LLM이 이해하는지 4지선다로 평가하는 실험을 예로 들면, "ㅇㅈ"을 어떤 지시문으로 물을지, 보기를 어떻게 배열할지, few-shot 예시를 몇 개 줄지를 정하는 동안 보는 문항이 train/validation이고, 모든 설정을 고정한 뒤 논문에 실을 숫자를 뽑는 문항이 test다. 학습이 없다고 해서 분할이 필요 없는 것이 아니다. 사람이 설정을 고르는 행위 자체가 일종의 학습이기 때문이다.
**데이터 누수(data leakage)**는 test 세트의 정보가 어떤 경로로든 학습·선택 과정에 흘러들어가는 것을 말한다. 누수가 있으면 test 점수는 일반화 성능의 추정치가 아니라 부풀려진 숫자가 된다. 누수는 대개 고의가 아니라 실수로 생기며, 흔한 경로는 다음과 같다.
같은 데이터가 train과 test 양쪽에 들어가는 가장 단순한 형태다. 크롤링한 데이터에 중복 문서가 있거나, 같은 문항이 표기만 살짝 다르게 두 번 수집된 경우다. 분할 전에 중복 제거(deduplication)를 하지 않으면 무작위 분할이 이 중복을 양쪽에 뿌려 놓는다.
행 자체는 겹치지 않지만, 강하게 연관된 행들이 양쪽에 나뉘어 들어가는 형태다. 이것이 가장 자주 놓치는 누수이며, 다음 절에서 자세히 다룬다.
정규화 통계량, 어휘 사전, 임베딩 클러스터 같은 것을 전체 데이터로 계산한 뒤 분할하는 경우다. 예컨대 문항 난이도를 전체 정답률로 계산해 층화 분할에 쓰면, test 문항의 정답 정보가 이미 분할 설계에 들어간 것이다. 전처리 파이프라인의 모든 "학습되는" 요소는 train에서만 적합(fit)하고 test에는 적용(transform)만 해야 한다.
시계열 데이터에서 미래 데이터로 학습하고 과거 데이터로 평가하는 경우다. 시간 순서가 의미 있는 데이터는 무작위 분할이 아니라 시점 기준 분할을 써야 한다.
주의: LLM 연구에는 여기에 한 층이 더 있다. 평가 문항이 모델의 사전 학습 데이터에 이미 들어 있었을 가능성, 즉 학습 데이터 오염이다. 이는 연구자가 분할을 아무리 잘해도 생기는 별개의 문제로, 18강에서 다룬다. 이번 강의 누수는 연구자 자신의 파이프라인 안에서 생기는 누수에 한정한다.
무작위 분할의 숨은 가정은 "모든 행이 서로 독립"이라는 것이다. 현실 데이터는 이 가정을 자주 어긴다. 같은 환자의 사진 여러 장, 같은 화자의 발화 여러 개, 같은 작성자의 글 여러 편이 그렇다. 이런 행들은 서로 강하게 닮아 있다. 이때 행 단위로 무작위 분할하면 같은 출처의 데이터가 train과 test에 나뉘어 들어가고, 모델은 과제 해결 능력 대신 출처의 고유 특징을 외워서 test 점수를 올릴 수 있다.
의료 영상 딥러닝 분야에서 실제로 널리 논의된 사례가 있다. 흉부 X-ray 공개 데이터셋으로 질병 분류기를 학습한 여러 초기 연구에서, 한 환자가 여러 장의 영상을 가진 데이터를 영상 단위로 무작위 분할한 문제가 지적되었다. 같은 환자의 영상이 train과 test 양쪽에 들어가면서, 모델이 질병의 특징이 아니라 그 환자의 체형·촬영 조건 같은 개인 고유 패턴으로 test 문항을 맞힐 수 있게 된 것이다. 이후 해당 데이터셋들은 환자 단위 분할(patient-wise split)을 공식 분할로 제공하는 방향으로 정리되었고, 환자 단위로 다시 나누면 보고된 성능이 떨어지는 경우가 확인되었다. 또한 기계학습을 쓴 여러 과학 분야의 논문을 체계적으로 조사해, 상당수 분야에서 누수가 재현 실패의 주요 원인으로 반복 발견되었다는 조사 연구도 발표된 바 있다. 누수는 특정 분야의 예외적 실수가 아니라 구조적으로 반복되는 오류다.
해법은 **그룹 분할(group split)**이다. 분할 단위를 행이 아니라 그룹(환자, 화자, 작성자, 문서 출처)으로 잡고, 한 그룹의 모든 행이 반드시 같은 조각에 들어가게 한다. 형식적으로 쓰면, 그룹 에 대해
가 모든 행 쌍에 대해 성립해야 한다. scikit-learn의 GroupShuffleSplit, GroupKFold가 이를 구현한다.
초성 줄임말 4지선다 실험이라면 그룹 후보는 다음과 같다.
어떤 그룹으로 나눌지는 연구 질문이 정한다. "처음 보는 줄임말도 이해하는가"를 묻는다면 줄임말 원형 단위 분할이 필수이고, "아는 줄임말을 새 문맥에서도 이해하는가"를 묻는다면 문맥 출처 단위 분할이 맞다. 분할 설계는 곧 연구 질문의 형식화다(연구 질문 만들기는 1강 참고).
세 조각의 비율에 정답 공식은 없지만 판단 기준은 있다.
무작위 분할은 우연히 조각 간 구성이 어긋날 수 있다. 문항 수가 수백 개 규모라면, 난이도·범주·길이 같은 주요 속성의 분포가 조각마다 달라지는 일이 드물지 않다. 이를 막는 것이 **층화 분할(stratified split)**로, 지정한 속성의 비율이 모든 조각에서 비슷하게 유지되도록 나눈다. 초성 줄임말 실험이라면 줄임말의 길이(2글자/3글자 이상)나 출처 커뮤니티별 비율을 층화 기준으로 삼을 수 있다.
주의: 층화 기준은 분할 전에 알 수 있는 속성이어야 한다. 모델의 정답률처럼 실험을 돌려야 알게 되는 값으로 층화하면 그 자체가 전처리 누수다.
그룹 분할과 층화를 동시에 하면 제약이 충돌할 수 있다. 예컨대 한 줄임말 그룹에 속한 문항들이 특정 범주에 몰려 있으면, 그룹을 통째로 움직여야 하므로 범주 비율을 정확히 맞출 수 없다. 이때 우선순위는 명확하다. 그룹 무결성이 층화보다 우선한다. 층화가 조금 어긋난 분할은 추정의 분산을 키울 뿐이지만, 그룹이 깨진 분할은 추정 자체를 편향시킨다.
test 세트를 실험 중에 반복해서 열어보면 무엇이 망가지는가. 열어볼 때마다 연구자는 그 결과를 보고 다음 결정을 내린다. 프롬프트를 고치고, 설정을 바꾸고, 잘 나온 변형을 채택한다. 이 과정이 반복되면 연구자라는 최적화 알고리즘이 test 세트에 과적합된다. 이를 적응적 과적합(adaptive overfitting) 또는 검증 세트 과적합이라 부른다. 모델 파라미터는 test를 본 적이 없어도, 모델을 둘러싼 모든 선택이 test에 맞춰졌으므로 결과는 같다.
직관적으로 보면 이렇다. 후보 설정 개를 모두 test에서 평가하고 최고점을 보고하면, 그 최고점은 진짜 성능에 "운 좋게 높게 나온 오차"가 더해진 값이다. 가 커질수록 최댓값의 기대치는 순수하게 우연만으로도 올라간다. 이는 15강에서 다룰 다중 비교 문제와 같은 구조다. Kaggle 대회가 리더보드를 public/private로 나누는 것도 같은 이유다. 참가자들이 public 점수에 반복 적합한 결과, 최종 private 점수에서 순위가 뒤집히는 일이 흔하다.
그래서 규율은 단순하다.
"한 번"은 물리적 횟수라기보다 의사결정 방향의 문제다. test 결과가 이후의 어떤 설계 결정에도 영향을 주지 않는다면 규율은 지켜진 것이고, 한 번이라도 영향을 줬다면 이미 어긴 것이다.
분할을 신뢰할 수 있게 만드는 절차를 순서대로 정리한다.
evaluate 스테이지만 test를 입력으로 받게 선언한다. 파이프라인 구성은 20강에서 다룬다.위 절차를 초성 줄임말 4지선다 실험에 적용하면 흐름은 다음과 같다.
abbrev_root(줄임말 원형) 컬럼을 부여한다. "ㅇㅈ"의 문맥 변형 5개는 모두 abbrev_root = "ㅇㅈ"이다.abbrev_root를 그룹 키로, 줄임말 길이를 층화 기준으로 삼아 시드 고정 그룹 분할을 한다.abbrev_root 교집합이 비어 있는지 확인하는 단정을 넣는다.이렇게 하면 "이 모델은 처음 보는 초성 줄임말을 문맥으로 추론할 수 있는가"라는 질문에 대해, test 점수가 실제로 그 질문에 답하는 숫자가 된다.