Workspace IndexDev Notes › The model is not the experiment — the split is

#134PoC

The model is not the experiment — the split is

Almost every applied result that fails in production failed at the split, not at the model. On-chain data breaks two splitting rules at once — it is ordered in time and grouped by address — and the number worth reporting is the gap between the naive split and the honest one.

Not yet scoped, and the deliverable is one number rather than a model. Take a real labelled dataset from this project — an address-level question is ideal, since it violates two rules at once — and evaluate the same estimator three ways: a random split, a time-ordered split, and a time-ordered split that is also grouped by address with an embargo the length of the label horizon. Report the three scores side by side. The gap between the first and the third is the finding, because it is the amount by which you would have lied to yourself. Then move every fitted preprocessing step — scaler, imputer, target encoder, vocabulary — inside the fold and see how much of the remaining gap that alone accounts for.

Why

The reason applied results do not survive contact with production is almost never the model, and treating it as a modelling problem is how teams spend months on the wrong thing. A leak is any path by which information that would not exist at prediction time reaches the estimator during training. It does not announce itself. It shows up as a validation score that is too good, which is the one signal nobody investigates, because a good number looks like success rather than like a bug.

Three leaks account for most of it, and they are structural rather than clever. The first is temporal: data with an order was split at random, so the model was allowed to see the future while predicting the past. The second is grouped: the same entity appears on both sides of the split — the same user, the same document, the same address — so the model memorises the entity rather than learning the pattern, and scores brilliantly on entities it has already met. The third is preprocessing: a scaler, an imputer, a target encoder or a vocabulary was fitted on the whole dataset before splitting, so statistics from the test set were baked into the training features. The third is the most common and the least discussed, because it hides inside code that looks like data preparation rather than like modelling.

On-chain data triggers the first two simultaneously, which is why this belongs in this catalogue rather than in a textbook. Every record has a block timestamp, so it is ordered; every record is attached to an address, so it is grouped. A random split violates both at once. And the labels here usually depend on a forward window — did this address get drained within thirty days, did this position get liquidated before expiry — which means a training label overlaps in time with the validation period even after a clean date cut. That is exactly the case purged cross-validation with an embargo exists for: drop the training samples whose label horizon reaches into the validation window, and leave a gap after it before training resumes.

The honest way to report any of this is a gap, not a score. A single number from a leaky split is unfalsifiable — nobody can tell from the outside whether it is skill or contamination. Two numbers from two splits are a measurement, and the distance between them is the only part of it that carries information. The gap is also the cheapest possible experiment, because it needs no new data, no new features and no tuning: the same estimator, evaluated twice, honestly.

And it is worth naming what the discipline buys beyond correctness. A pipeline that splits properly is a pipeline that can be re-run when the data changes, because the boundary between what is known and what is being predicted has been written down explicitly instead of assumed. That boundary is the same thing a backtest needs, the same thing an audit asks for, and the same thing that makes a result reproducible six months later by somebody who was not there.

How it works

Three leaks, and the split that closes each

Leak How it gets in What the score looks like The fix
Temporal Random split on ordered data Excellent, and it degrades the moment it goes live Split by time; never shuffle
Grouped The same entity on both sides Excellent on seen entities, chance-level on new ones Split by group (address, user, document)
Horizon overlap Labels depend on a forward window that reaches past the cut Slightly too good; survives a naive date split Purge the overlapping training samples, then embargo a gap
Preprocessing Scaler / imputer / encoder fitted before the split Uniformly and mildly too good across all folds Fit inside the fold, always

The horizon overlap row is the one that survives the obvious fix, which is what makes it worth naming separately. A team that has already learned to split by date will still leak if the label looks thirty days ahead and the training data runs up to the cut — the last thirty days of training labels are partly about the validation period.

Why the answer is a gap and not a score

Split What it measures Honest use
Random An upper bound produced by contamination Only as the top of the gap
Time-ordered What the model knows about the future given the past A real estimate, still optimistic if grouped
Time + group + embargo What it would have scored deployed The number to report

Report all three. A single honest number invites the question "could a better model do more?"; three numbers answer a better question — how much of the apparent performance was never real. And in a portfolio or a write-up, the gap is more persuasive than the score, because it demonstrates the one thing a reader cannot verify from outside: that you went looking for your own contamination.

The step people skip, stated concretely

Anything fitted belongs inside the fold. Not just the model — the scaler's mean and variance, the imputer's median, a target encoder's per-category averages, a vocabulary or tokenizer built from the corpus, a feature-selection step that looked at the labels, and any resampling done to balance classes. The test for whether something belongs inside is simple: would this quantity be computable on the day of prediction, using only what existed then? If not, computing it once over the whole table has already leaked.

Where it lands in this project

The pairing is deliberate. the-70-has-to-be-wrong is about scoring a probabilistic output honestly; this card is about whether the scoreboard was contaminated before scoring began. The two failures compose badly — a leaked split produces a model that looks well-calibrated on data it has effectively already seen, and the calibration plot will look fine right up until deployment. Fix the split first, because a calibration measured on a leak is a measurement of nothing.

← All Dev Notes · Workspace Index · Top ↑

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

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

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

응용 결과가 프로덕션에서 살아남지 못하는 이유는 거의 언제나 모델이 아니고, 그걸 모델링 문제로 다루는 것이 팀이 몇 달을 엉뚱한 데 쓰는 방식입니다. 누수(leak)예측 시점에는 존재하지 않을 정보가 학습 중에 추정기에 닿는 모든 경로입니다. 누수는 자기를 알리지 않습니다. 너무 좋은 검증 점수로 나타나는데, 그건 아무도 조사하지 않는 유일한 신호입니다 — 좋은 숫자는 버그가 아니라 성공처럼 생겼기 때문입니다.

대부분은 세 가지로 설명되고, 셋 다 기발한 게 아니라 구조적입니다. 첫째 시간: 순서가 있는 데이터를 무작위로 나눠서, 모델이 과거를 예측하면서 미래를 볼 수 있게 됐습니다. 둘째 그룹: 같은 개체가 분할 양쪽에 나타납니다 — 같은 사용자, 같은 문서, 같은 주소 — 그래서 모델이 패턴을 배우는 대신 개체를 외우고, 이미 만나본 개체에서 훌륭한 점수를 냅니다. 셋째 전처리: 스케일러·결측 대체·타깃 인코딩·어휘를 분할 전에 전체 데이터로 학습시켜서, 테스트셋의 통계가 학습 피처에 구워졌습니다. 셋째가 가장 흔하고 가장 덜 언급됩니다 — 모델링이 아니라 데이터 준비처럼 생긴 코드 안에 숨기 때문입니다.

온체인 데이터는 첫 둘을 동시에 켭니다. 그게 이 카드가 교과서가 아니라 이 카탈로그에 있는 이유입니다. 모든 레코드에 블록 타임스탬프가 있으니 순서가 있고, 모든 레코드가 주소에 붙어 있으니 그룹져 있습니다. 무작위 분할은 둘을 한 번에 위반합니다. 게다가 여기 라벨은 대개 미래 창(forward window)에 의존합니다이 주소가 30일 안에 털렸는가, 이 포지션이 만기 전에 청산됐는가 — 즉 날짜를 깨끗이 잘라도 학습 라벨이 검증 구간과 시간상 겹칩니다. 정확히 그 경우를 위해 purged 교차검증 + embargo 가 있습니다: 라벨 지평이 검증 구간에 닿는 학습 표본을 버리고, 그 뒤에 간격을 두고 학습을 재개합니다.

이 모든 것을 정직하게 보고하는 방법은 점수가 아니라 격차입니다. 누수된 분할에서 나온 숫자 하나는 반증 불가능합니다 — 바깥에서는 그게 실력인지 오염인지 알 수 없습니다. 두 분할에서 나온 두 숫자는 측정이고, 그 사이의 거리만이 정보를 담습니다. 격차는 동시에 가능한 가장 싼 실험이기도 합니다 — 새 데이터도, 새 피처도, 튜닝도 필요 없이 같은 추정기를 두 번, 정직하게 평가하면 됩니다.

그리고 이 규율이 정확성 말고 무엇을 사주는지도 적어둘 값어치가 있습니다. 제대로 분할하는 파이프라인은 데이터가 바뀌어도 다시 돌릴 수 있는 파이프라인입니다 — 무엇이 알려진 것이고 무엇이 예측 대상인지의 경계가 가정이 아니라 명시적으로 적혀 있기 때문입니다. 그 경계는 백테스트가 필요로 하는 것과 같은 것이고, 감사가 요구하는 것과 같은 것이며, 여섯 달 뒤 그 자리에 없던 사람이 결과를 재현할 수 있게 하는 것과 같은 것입니다.

동작 방식

누수 셋, 그리고 각각을 막는 분할

누수 어떻게 들어오나 점수가 어떻게 보이나 처방
시간 순서 있는 데이터를 무작위 분할 훌륭하다가 라이브 되는 순간 무너짐 시간순 분할. 절대 섞지 않음
그룹 같은 개체가 양쪽에 존재 본 개체에서 훌륭, 새 개체에서 찍기 수준 그룹(주소·사용자·문서) 단위 분할
지평 겹침 라벨이 컷 너머까지 닿는 미래 창에 의존 살짝 너무 좋음. 날짜 분할로도 안 잡힘 겹치는 학습 표본을 purge, 그 뒤 embargo 간격
전처리 스케일러·대체·인코더를 분할 전에 학습 모든 폴드에서 고르게 조금씩 너무 좋음 항상 폴드 안에서 학습

"지평 겹침" 줄이 뻔한 처방에도 살아남는 누수라서 따로 이름 붙일 값어치가 있습니다. 이미 날짜로 나누는 법을 배운 팀도 라벨이 30일 앞을 보고 학습 데이터가 컷까지 차 있으면 여전히 샙니다학습 라벨의 마지막 30일은 부분적으로 검증 구간을 이야기하고 있는 셈입니다.

왜 답이 점수가 아니라 격차인가

분할 무엇을 재나 정직한 용도
무작위 오염이 만들어낸 상한 오직 격차의 위쪽 끝으로만
시간순 과거로 미래를 아는 정도 실제 추정치, 그룹이 있으면 여전히 낙관적
시간 + 그룹 + embargo 배포됐다면 받았을 점수 보고할 숫자

셋 다 보고하십시오. 정직한 숫자 하나는 "더 좋은 모델이면 더 되지 않나" 를 부르지만, 세 숫자는 더 나은 질문에 답합니다겉보기 성능 중 얼마가 애초에 실재하지 않았는가. 그리고 포트폴리오나 글에서는 격차가 점수보다 설득력이 있습니다. 바깥에서 검증할 수 없는 것 하나를 보여주기 때문입니다 — 자기 오염을 자기가 찾아 나섰다는 것.

다들 건너뛰는 단계, 구체적으로

학습되는 것은 전부 폴드 안에 들어갑니다. 모델만이 아니라 — 스케일러의 평균·분산, 대체값의 중앙값, 타깃 인코더의 범주별 평균, 코퍼스로 만든 어휘·토크나이저, 라벨을 본 피처 선택 단계, 그리고 클래스 균형을 맞추려 한 리샘플링 전부. 안쪽인지 판별하는 시험은 간단합니다: 이 값이 예측하는 날, 그날까지 존재한 것만으로 계산되는가? 아니라면 전체 표에서 한 번 계산한 순간 이미 샜습니다.

이 프로젝트에서의 자리

짝지은 건 의도적입니다. the-70-has-to-be-wrong확률 출력을 정직하게 채점하는 것이라면, 이 카드는 채점을 시작하기 전에 채점판이 오염됐는가입니다. 두 실패는 나쁘게 합성됩니다 — 누수된 분할은 사실상 이미 본 데이터에서 잘 캘리브레이션된 것처럼 보이는 모델을 만들고, 캘리브레이션 그림은 배포 직전까지 멀쩡해 보입니다. 분할을 먼저 고치십시오. 누수 위에서 잰 캘리브레이션은 아무것도 잰 게 아니기 때문입니다.

← 전체 개발 노트 · 워크스페이스 인덱스 · 맨 위 ↑