실험 결과가 논문 그림이 되기까지의 모든 단계를 손으로 잇는 순간, 재현성은 사람의 기억력에 의존하게 된다. 이 강에서는 원본 데이터에서 논문 그림까지를 하나의 실행 가능한 그래프로 묶는 이유와, 그것을 실제로 구현하는 도구인 DVC(Data Version Control)를 다룬다.
한국어 초성 줄임말 이해도를 4지선다로 평가하는 LLM 실험을 예로 들자. 손으로 운영하면 대체로 다음처럼 진행된다.
data_v2_final_진짜최종.csv로 저장한다4번에서 문제가 터진다. 석 달 전의 정확도 0.72가 어떤 데이터 버전, 어떤 프롬프트 템플릿, 어떤 시드에서 나왔는지 아무도 모른다. 새 조건을 추가해 다시 돌리면 기존 숫자도 미묘하게 달라지는데, 논문에 붙여넣은 그림은 옛 숫자 그대로다. 이제 논문에는 서로 다른 실행에서 나온 숫자가 섞여 있고, 그 사실을 저자 본인도 알아채지 못한다.
이 붕괴의 공통 원인은 하나다. 단계 사이의 연결이 사람의 손과 기억 속에만 존재한다는 것이다. 전처리→평가→집계→그림의 순서, 각 단계의 입력 버전, 실행 당시의 설정. 이 중 어느 것도 기계가 검증할 수 있는 형태로 남아 있지 않다.
파이프라인(pipeline)은 연구의 각 단계를 노드로, 단계 간 입출력 의존성을 간선으로 표현한 유향 비순환 그래프(DAG, Directed Acyclic Graph) 다. 초성 줄임말 실험이라면 이런 그래프가 된다.
원본 데이터 → 전처리 → 분할 → [모델별 평가] → 집계·통계 → 논문 그림/표
핵심 규칙은 두 가지다.
이것이 "데이터에서 논문 그림까지 한 그래프로 잇는다"의 뜻이다. 재현성을 사람의 성실함이 아니라 그래프의 구조가 보장하게 만드는 것이다.
run_all.sh에 모든 명령을 순서대로 적어두는 것도 그래프의 일종 아닌가? 절반만 맞다. 셸 스크립트는 순서는 기록하지만 다음을 못 한다.
| 필요한 능력 | 셸 스크립트 | 파이프라인 도구 |
|---|---|---|
| 실행 순서 기록 | 가능 | 가능 |
| 입력이 바뀐 단계만 골라 재실행 | 불가(전부 재실행) | 가능(해시 기반 캐시) |
| 데이터·산출물의 버전 추적 | 불가 | 가능 |
| "이 그림은 어떤 입력에서 나왔나" 역추적 | 불가 | 가능 |
| 오래된 산출물 감지 | 불가 | 가능 |
LLM 평가는 API 비용과 시간이 큰 작업이라 "바뀐 단계만 재실행"이 특히 중요하다. 프롬프트 템플릿만 고쳤는데 전처리부터 다시 도는 것도 낭비이고, 반대로 데이터를 고쳤는데 평가를 다시 안 도는 것은 사고다. 도구는 이 판단을 파일 내용의 해시(hash)로 자동화한다.
git은 코드의 버전을 관리하지만 수 GB의 데이터나 모델 산출물에는 맞지 않다. DVC는 git 위에 얹혀 이 빈틈을 메우는 도구로, 두 가지 역할을 한다.
DVC는 큰 파일 자체 대신 그 파일의 해시를 담은 작은 메타파일(.dvc)을 git에 커밋한다. 실제 데이터는 별도 저장소(원격 스토리지)에 해시 이름으로 보관된다. 결과적으로 "커밋 abc123 시점의 데이터"를 언제든 정확히 되찾을 수 있다. 데이터가 코드와 함께 버전이 박히는 것이다.
dvc.yaml 파일에 각 단계(stage)의 명령·의존성(deps)·출력(outs)을 선언한다. 초성 줄임말 실험을 단순화하면 다음과 같다.
stages:
preprocess:
cmd: python scripts/preprocess.py
deps: [data/raw/chosung.csv, scripts/preprocess.py]
outs: [data/processed/items.parquet]
evaluate:
cmd: python scripts/evaluate.py --config conf/eval.yaml
deps: [data/processed/items.parquet, scripts/evaluate.py, conf/eval.yaml]
outs: [results/scores.json]
figures:
cmd: python scripts/make_figures.py
deps: [results/scores.json]
outs: [paper/figures/accuracy.pdf]
dvc repro(또는 실험 관리 기능을 쓰면 dvc exp run)를 실행하면 DVC가 각 의존성의 해시를 지난 실행과 비교해, 바뀐 단계와 그 하류만 다시 실행한다. conf/eval.yaml의 프롬프트 템플릿을 고치면 evaluate와 figures만 다시 돌고 preprocess는 캐시가 재사용된다.
주목할 점은 paper/figures/accuracy.pdf가 파이프라인의 출력이라는 것이다. 이 그림은 사람이 만들지 않는다. 데이터가 바뀌면 그림도 자동으로 무효화되므로, 논문의 그림이 항상 현재 데이터·코드와 일치한다는 것을 도구가 보증한다.
연구는 파이프라인을 한 번 돌리고 끝나지 않는다. 프롬프트 템플릿을 바꾸고, 모델을 추가하고, 시드를 바꿔가며 수십 번 실행하게 된다. DVC의 실험 기능(dvc exp run)은 실행 하나하나를 "당시의 코드 커밋 + 설정 + 데이터 해시 + 산출 지표"의 묶음으로 기록한다. 이렇게 하면 실험 간 비교가 기억이 아니라 기록으로 이뤄진다.
dvc exp show로 실험들의 설정 차이와 지표를 표로 나란히 본다조건 여러 개를 비교하는 설계 자체(arm과 baseline)는 4강에서, 비교의 통계는 11강에서 다뤘다. 이 강의 관심사는 그 비교의 각 팔이 같은 파이프라인, 같은 데이터 버전 위에서 돌았음을 도구가 증명할 수 있는가다.
모델 이름, 프롬프트 템플릿, 온도, 시드 같은 값은 코드에 하드코딩하지 않고 설정 파일(conf/)로 빼서 의존성으로 선언한다. 그래야 "설정을 바꿨는데 결과가 옛것"인 사고가 막히고, 실험 조건 간 비교(19강에서 다룬 템플릿 통제)도 설정 파일의 diff로 명시된다.
그래프를 아무리 잘 짜도 같은 입력에서 다른 출력이 나오면 재현이 안 된다. 그래서 각 단계는 난수 시드(random seed)를 고정해 결정론적으로 만든다. 데이터 분할의 셔플, 4지선다 보기 순서의 무작위 배치, 샘플링 디코딩처럼 무작위성이 개입하는 모든 지점의 시드가 설정 파일에 기록되고 파이프라인의 의존성이 된다.
주의: 시드 고정은 재현성의 필요조건이지 결과의 신뢰성을 보장하지 않는다. 단일 시드로만 보고하면 그 시드에 유리하게 나온 우연을 실력으로 착각할 수 있다. 시드의 의미, 단일 시드 보고의 위험, 다중 시드 실행과 결정론 설정은 13강에서 다뤘다. 이 강의 관점에서 중요한 것은 다중 시드 실행조차 파이프라인 안에서 이뤄져야 한다는 점이다. 시드별 실행을 손으로 반복하면 "시드 3만 다른 데이터 버전으로 돌린" 사고가 다시 생긴다.
경제학에서 널리 알려진 사례가 있다. 국가 부채와 경제 성장의 관계를 다룬 유명 논문이 각국 정부의 긴축 정책 근거로 자주 인용되었는데, 수년 뒤 한 대학원생이 저자들의 엑셀 스프레드시트를 받아 재현을 시도하는 과정에서 수식의 셀 범위가 일부 국가의 행을 누락하고 있었음이 드러났다. 분석이 스프레드시트 안에서 손으로 이뤄졌기 때문에 원자료에서 결론까지의 경로를 기계적으로 검증할 방법이 없었고, 오류는 논문이 정책 담론에 큰 영향을 미친 뒤에야 발견되었다.
생명과학에서도 비슷한 유형의 문제가 반복적으로 보고되었다. 유전자 이름을 엑셀에서 다루는 과정에서 일부 유전자명이 날짜로 자동 변환된 채 게재 논문의 보충 자료에 남아 있는 사례가 대규모로 발견되었고, 결국 문제가 된 유전자들의 공식 명칭이 변경되기에 이르렀다. 두 사례의 교훈은 같다. 사람이 손으로 잇는 단계는 반드시 언젠가 어긋나며, 어긋난 사실조차 감지되지 않는다. 파이프라인은 이 감지 불가능성을 없애는 장치다.
처음 연구를 설계하는 입장에서의 최소 절차는 다음과 같다.
dvc.yaml에 deps/outs를 선언한다. 설정 파일과 프롬프트 템플릿도 deps에 넣는다. dvc dag 명령으로 그래프가 의도대로 이어졌는지 확인한다.dvc.yaml에서 프롬프트 템플릿 파일을 evaluate 단계의 deps에 넣지 않았다면, 템플릿을 수정했을 때 어떤 일이 벌어지는가?