Hermes Wiki
Developer/MachineLearning/ModelTraining/Fundamentals/train-validation-test-splits-and-cross-validation

Train/Validation/Test Splits and Cross-Validation

Concept

A model is only as trustworthy as its evaluation, and evaluation requires holding out data the model never saw during training. The standard shape is three-way, not two-way: a training set the model actually learns from, a validation set used to tune hyperparameters and pick between candidate models, and a test set touched exactly once, at the very end, to report a final, unbiased performance number. The validation set is necessary specifically because repeatedly evaluating different model configurations against the test set and picking the best-scoring one implicitly fits the choice of hyperparameters to that test set — at that point it has silently become a second training set, and the "final" number it produces is optimistic, not honest.

A single train/validation/test split has a real weakness on small-to-medium datasets: which specific rows happened to land in each split introduces variance into the reported number, independent of whether the model itself is actually good. K-fold cross-validation addresses this by partitioning the training data into K equal folds, training K times with a different fold held out as validation each time, and averaging the results — every observation gets used for both training and validation across the K runs, which both reduces variance in the estimate and uses the data more efficiently than a single static split.

Two variants exist for data that violates the assumption plain K-fold makes (that observations are independent and interchangeable):

  • Stratified K-fold — for classification with class imbalance, preserves each class's proportion in every fold, so a rare-class fold doesn't end up with too few (or zero) examples to evaluate against.
  • Time-series (walk-forward) cross-validation — for temporally ordered data, keeps training folds strictly before validation folds in time; a random split would let the model train on rows chronologically after the ones it's validated against, which is a form of leakage — no real deployment ever gets to see the future before predicting the present.

Tradeoffs

Strategy Benefit Cost
Single train/val/test split Simple, fast, one training run High variance on small/medium datasets — the reported score depends partly on luck of which rows landed where
K-fold cross-validation Every row used for both training and validation; lower-variance performance estimate K× the training cost/time; still assumes exchangeable, independent rows
Stratified K-fold Preserves class balance in every fold — essential for imbalanced classification Only solves the imbalance problem, not temporal or grouping violations
Time-series (walk-forward) split Respects temporal order — no future-into-past leakage Fewer effective folds early in the series (little history to train on); cannot randomly shuffle to reduce variance the way plain K-fold can
Leave-one-out CV Maximum data efficiency, deterministic (no random fold assignment) Computationally expensive at scale (N training runs for N rows); high variance in the final estimate despite low bias

When to use / when not to

  • Use a plain single split when the dataset is large enough that sampling variance across different train/val/test partitions is negligible relative to the signal being measured, and iteration speed matters more than squeezing out the last bit of estimate precision.
  • Use K-fold cross-validation by default for small-to-medium datasets, or whenever a single split's reported score needs to be trusted enough to justify a real decision (choosing between two model architectures, deciding whether a change actually helped).
  • Use stratified K-fold specifically for classification tasks with meaningful class imbalance — plain K-fold on an imbalanced dataset can produce folds where the minority class barely appears, making per-fold metrics unreliable.
  • Use time-series cross-validation whenever the target has temporal structure at all, even loosely — this is not optional the way the strata choice is; a random split on time-ordered data produces a number that will not hold up once the model faces genuinely unseen future data.
  • Don't cross-validate as a substitute for a true held-out test set — cross-validation still uses every row for validation at some point during model/hyperparameter selection; the test set exists precisely to be the one thing that was never used for any decision along the way.

Common pitfall

Iterating on the test set. A team tunes a model, checks its score against what they call the "test set," doesn't like the number, adjusts a hyperparameter, and checks again — repeating this loop several times before shipping. Every one of those checks is a decision informed by that data, which means the set has functioned as a second validation set the whole time, not a true test set. The final reported number is then optimistic relative to what the model will actually achieve on real unseen data, sometimes substantially so if the tuning loop ran many iterations. The fix is procedural, not statistical: decide the validation strategy up front, iterate freely against validation (or cross-validation folds) as many times as needed, and touch the test set exactly once, after every other decision has already been locked in.

Engineering Lens

The value of a rigorous split strategy isn't methodological purity for its own sake — it's that a model shipped on an inflated offline number fails in a specific, expensive way: it looks fine in every review, passes every gate, and then underperforms in production in a way nobody can immediately explain, because the number everyone trusted was never honest in the first place. The strong answer to "how do you know this model is actually good" is naming the exact split or cross-validation strategy used, why it matches the data's actual structure (temporal, imbalanced, or neither), and confirming the test set was touched exactly once — not citing a single accuracy number in isolation.

Sources

Hermes Wiki