PoCs

실험은 모델이 아니라 분할이다

프로덕션에서 무너지는 결과의 거의 전부는 **모델이 아니라 분할에서** 무너집니다. 온체인 데이터는 **분할 규칙 두 개를 동시에 위반합니다** — 시간순이면서 동시에 주소로 묶여 있습니다 — 그리고 보고할 값어치가 있는 숫자는 **순진한 분할과 정직한 분할 사이의 격차**입니다.

아직 만들지 않았습니다

이 페이지는 카드에 적힌 내용을 펼쳐 보여줄 뿐입니다 — 돌아가는 코드도, 열어볼 데모도 아직 없습니다. 무엇을 왜 만들려는지가 아래에 있습니다.

어떻게 보나

아직 범위 미정이고, **결과물은 모델이 아니라 숫자 하나**입니다. 이 프로젝트의 실제 라벨 데이터 하나를 잡고 — **주소 단위 질문이 이상적입니다**, 규칙 두 개를 동시에 위반하니까요 — **같은 추정기를 세 방식으로** 평가합니다: 무작위 분할, 시간순 분할, 그리고 **주소로 그룹을 묶고 라벨 지평 길이만큼 embargo 를 둔** 시간순 분할. 세 점수를 나란히 보고합니다. **첫 번째와 세 번째의 격차가 곧 발견**입니다 — 그게 **자기 자신에게 거짓말한 양**이니까요. 그다음 **학습되는 전처리 단계를 전부**(스케일러·결측 대체·타깃 인코딩·어휘) **폴드 안으로 옮기고**, 남은 격차 중 얼마가 그것만으로 설명되는지 봅니다.

기술 노트

각 항목이 실제로 무엇을 보여주고 어떻게 동작하는지 — 위 카드보다 자세한 기술 설명입니다.

실험은 모델이 아니라 분할이다준비 중

목적: **응용 결과가 프로덕션에서 살아남지 못하는 이유는 거의 언제나 모델이 아니고**, 그걸 모델링 문제로 다루는 것이 팀이 몇 달을 엉뚱한 데 쓰는 방식입니다. **누수(leak)** 란 **예측 시점에는 존재하지 않을 정보가 학습 중에 추정기에 닿는 모든 경로**입니다. 누수는 **자기를 알리지 않습니다.** **너무 좋은 검증 점수**로 나타나는데, 그건 **아무도 조사하지 않는 유일한 신호**입니다 — 좋은 숫자는 버그가 아니라 **성공처럼 생겼기** 때문입니다. **대부분은 세 가지로 설명되고, 셋 다 기발한 게 아니라 구조적입니다.** 첫째 **시간**: 순서가 있는 데이터를 무작위로 나눠서, **모델이 과거를 예측하면서 미래를 볼 수 있게** 됐습니다. 둘째 **그룹**: 같은 개체가 분할 양쪽에 나타납니다 — 같은 사용자, 같은 문서, **같은 주소** — 그래서 모델이 패턴을 배우는 대신 **개체를 외우고**, 이미 만나본 개체에서 훌륭한 점수를 냅니다. 셋째 **전처리**: 스케일러·결측 대체·타깃 인코딩·어휘를 **분할 전에 전체 데이터로 학습**시켜서, **테스트셋의 통계가 학습 피처에 구워졌습니다.** 셋째가 **가장 흔하고 가장 덜 언급됩니다** — 모델링이 아니라 **데이터 준비처럼 생긴 코드 안에 숨기** 때문입니다. **온체인 데이터는 첫 둘을 동시에 켭니다. 그게 이 카드가 교과서가 아니라 이 카탈로그에 있는 이유입니다.** 모든 레코드에 블록 타임스탬프가 있으니 **순서가 있고**, 모든 레코드가 주소에 붙어 있으니 **그룹져 있습니다.** 무작위 분할은 **둘을 한 번에** 위반합니다. 게다가 여기 라벨은 대개 **미래 창(forward window)에 의존합니다** — *이 주소가 30일 안에 털렸는가*, *이 포지션이 만기 전에 청산됐는가* — 즉 **날짜를 깨끗이 잘라도 학습 라벨이 검증 구간과 시간상 겹칩니다.** 정확히 그 경우를 위해 **purged 교차검증 + embargo** 가 있습니다: **라벨 지평이 검증 구간에 닿는 학습 표본을 버리고**, 그 뒤에 **간격을 두고** 학습을 재개합니다. **이 모든 것을 정직하게 보고하는 방법은 점수가 아니라 격차입니다.** 누수된 분할에서 나온 숫자 하나는 **반증 불가능합니다** — 바깥에서는 그게 실력인지 오염인지 알 수 없습니다. **두 분할에서 나온 두 숫자는 측정이고, 그 사이의 거리만이 정보를 담습니다.** **격차는 동시에 가능한 가장 싼 실험**이기도 합니다 — 새 데이터도, 새 피처도, 튜닝도 필요 없이 **같은 추정기를 두 번, 정직하게** 평가하면 됩니다. **그리고 이 규율이 정확성 말고 무엇을 사주는지도 적어둘 값어치가 있습니다.** 제대로 분할하는 파이프라인은 **데이터가 바뀌어도 다시 돌릴 수 있는 파이프라인**입니다 — **무엇이 알려진 것이고 무엇이 예측 대상인지의 경계가 가정이 아니라 명시적으로 적혀 있기** 때문입니다. 그 경계는 **백테스트가 필요로 하는 것과 같은 것**이고, **감사가 요구하는 것과 같은 것**이며, **여섯 달 뒤 그 자리에 없던 사람이 결과를 재현할 수 있게 하는 것과 같은 것**입니다.

동작 방식: ### 누수 셋, 그리고 각각을 막는 분할 | 누수 | 어떻게 들어오나 | 점수가 어떻게 보이나 | 처방 | |---|---|---|---| | **시간** | 순서 있는 데이터를 무작위 분할 | 훌륭하다가 **라이브 되는 순간 무너짐** | 시간순 분할. **절대 섞지 않음** | | **그룹** | 같은 개체가 양쪽에 존재 | **본 개체에서 훌륭, 새 개체에서 찍기 수준** | 그룹(주소·사용자·문서) 단위 분할 | | **지평 겹침** | 라벨이 **컷 너머까지 닿는 미래 창**에 의존 | 살짝 너무 좋음. **날짜 분할로도 안 잡힘** | 겹치는 학습 표본을 **purge**, 그 뒤 **embargo** 간격 | | **전처리** | 스케일러·대체·인코더를 분할 전에 학습 | **모든 폴드에서 고르게 조금씩** 너무 좋음 | **항상 폴드 안에서** 학습 | **"지평 겹침" 줄이 뻔한 처방에도 살아남는 누수**라서 따로 이름 붙일 값어치가 있습니다. **이미 날짜로 나누는 법을 배운 팀도** 라벨이 30일 앞을 보고 학습 데이터가 컷까지 차 있으면 **여전히 샙니다** — **학습 라벨의 마지막 30일은 부분적으로 검증 구간을 이야기하고 있는 셈입니다.** ### 왜 답이 점수가 아니라 격차인가 | 분할 | 무엇을 재나 | 정직한 용도 | |---|---|---| | 무작위 | **오염이 만들어낸 상한** | **오직** 격차의 위쪽 끝으로만 | | 시간순 | 과거로 미래를 아는 정도 | 실제 추정치, **그룹이 있으면 여전히 낙관적** | | 시간 + 그룹 + embargo | **배포됐다면 받았을 점수** | **보고할 숫자** | **셋 다 보고하십시오.** 정직한 숫자 하나는 *"더 좋은 모델이면 더 되지 않나"* 를 부르지만, **세 숫자는 더 나은 질문에 답합니다** — **겉보기 성능 중 얼마가 애초에 실재하지 않았는가.** 그리고 포트폴리오나 글에서는 **격차가 점수보다 설득력이 있습니다.** 바깥에서 검증할 수 없는 것 하나를 보여주기 때문입니다 — **자기 오염을 자기가 찾아 나섰다는 것.** ### 다들 건너뛰는 단계, 구체적으로 **학습되는 것은 전부 폴드 안에 들어갑니다.** 모델만이 아니라 — 스케일러의 평균·분산, 대체값의 중앙값, 타깃 인코더의 범주별 평균, 코퍼스로 만든 어휘·토크나이저, **라벨을 본 피처 선택 단계**, 그리고 클래스 균형을 맞추려 한 **리샘플링 전부.** 안쪽인지 판별하는 시험은 간단합니다: **이 값이 예측하는 날, 그날까지 존재한 것만으로 계산되는가?** 아니라면 **전체 표에서 한 번 계산한 순간 이미 샜습니다.** ### 이 프로젝트에서의 자리 짝지은 건 의도적입니다. `the-70-has-to-be-wrong` 이 **확률 출력을 정직하게 채점하는 것**이라면, 이 카드는 **채점을 시작하기 전에 채점판이 오염됐는가**입니다. **두 실패는 나쁘게 합성됩니다** — 누수된 분할은 **사실상 이미 본 데이터에서 잘 캘리브레이션된 것처럼 보이는 모델**을 만들고, **캘리브레이션 그림은 배포 직전까지 멀쩡해 보입니다.** **분할을 먼저 고치십시오.** **누수 위에서 잰 캘리브레이션은 아무것도 잰 게 아니기** 때문입니다.