Train, Test & Validation: The Honest Split
A model graded on the rows it memorised always looks brilliant. This essay shows why one dataset must become three — and how a single careless peek can quietly turn your final number into a fiction.
Never Grade The Exam You Taught
A model earns its keep by being right about data it has never seen. Train and evaluate it on the same rows and that question silently disappears — you are no longer asking whether it generalises, only whether it can recognise what it was already handed.
The degenerate extreme makes this obvious. A 1-nearest-neighbour classifier simply stores every training point. Ask it to predict a stored point and its nearest neighbour is itself, at distance zero:
A degree-12 polynomial threaded through 13 points is the same trick in a different costume: zero training error, and wild oscillation in the gaps between the points. Neither model has learned anything about cats, dogs, or digits. Each has only learned the answer key.
If your score keeps climbing every time you add capacity, that is not progress — it is the definition of memorisation. The only honest question is what happens off-book, on rows the model has never had the chance to store.
Three Jobs, Three Datasets
So we split the data. Not into two parts but three, each with a different job and a different rule for when it may be read. The discipline is deliberately asymmetric: training and validation are working sets, while the test set is a sealed envelope opened once.
The model fits its parameters here. Read it as often as you like — it is the only set whose feedback is allowed to change the model.
Used to compare models and choose hyperparameters. Consulted many times; after each look you may change your mind. This is the set that does model selection.
Locked away until the very end. Read exactly once, after every choice is frozen. The number you report comes from here.
validation flickers — it is consulted repeatedly · test stays dim until the final flash
Two Kinds Of Parameters
A model holds two kinds of parameters. The first — weights and biases — the optimiser fits for you by descending the loss on the training set. The second you choose by hand before training begins: tree depth, the regularisation strength , the number of neighbours , the number of layers. Call these hyperparameters.
Now ask how you pick them. If you compare candidates on the test set and keep the winner, the test set has been used as a training signal. Each comparison teaches you something about its noise, and the number you report becomes the best of many tries rather than an unbiased reading of one frozen model.
This is the core of the article: choosing hyperparameters by test performance quietly promotes the test set into a second training set. The final number then measures how well you fitted the test set, not how well the model will travel.
Weights and biases, learned by gradient descent. The training set exists to shape these.
Depth, , , learning rate. The validation set exists to rank these — never the test set.
Every configuration you try on the test set leaks a little information out of it. Try enough of them and the best test score is optimistic by construction.
Random — Until It Must Not Be
The common conventions are a 60 / 20 / 20 split for large datasets and 70 / 15 / 15 when data is tighter. The exact ratio matters less than keeping the test set big enough for a stable estimate and the validation set big enough to tell candidates apart. And the split must be random — except in the three cases below, where randomness is exactly the bug.
With a rare class — fraud, disease — a plain random split can leave almost none of it in validation. Sample each class in proportion within every split.
Several visits per patient, many sessions per user. Keep every group entirely inside one split, or the model recognises the patient instead of the pattern.
Shuffle nothing. Train on the past, validate and test on the future. A random split lets the model peek at tomorrow to predict yesterday.
The rule behind all three: your split must mimic how the model will meet the world in production. If the future never appears in training once deployed, it must not appear in training here either.
The Doors Information Sneaks Through
Even with three clean sets, information leaks. Leakage is any path by which knowledge of the test data reaches training. It almost always inflates the score, and it is almost always invisible until production.
Computing the mean and standard deviation over the whole dataset leaks test statistics into training. Fit the scaler on the training split, then apply that same transform to validation and test.
A feature that secretly encodes the label — account_closed_date predicting churn, or a field filled in only after the outcome was known.
The same record lands in both train and test, so the model has effectively already seen the test point. De-duplicate before you split.
Trying many hyperparameter settings and reporting the best test score. Each peek nudges the model toward the test set and the reported number upward.
and come from training rows only.
A useful audit for any pipeline: could a single test row have influenced anything in training — a mean, a feature, a hyperparameter, a duplicate? If yes, the estimate is optimistic.
Borrowing From Yourself
A single split carries a hidden cost: the model never trains on the validation rows, and you never learn how sensitive the score is to which rows landed where. On small datasets that waste is real, and the estimate is noisy.
Cross-validation answers this by rotating the job. Split the data into folds; train on and validate on the held-out fold; repeat times so every row takes a turn as validation. The average is a more stable estimate, and the spread across folds is one you can actually quote.
The test set still stays sealed — cross-validation replaces the validation role, not the final exam. Topic 05 works through the mechanics, the choice of , and the grouped and time-series variants that keep the folds honest.
Small data, many candidates → cross-validation. Large data, few candidates → a single 60 / 20 / 20 split is simpler and just as honest. Either way, the test set is read once.
Adapted from MLU-Explain (Amazon, CC BY-SA 4.0).
