ML Systems Lab Open interactive version →
Intermediate 30 min read cross-validationk-foldtime seriesnested CV

Cross-Validation Strategies

k-fold, stratified, group k-fold, time-series CV, nested CV

You are training a random forest on 5,000 examples. You tune max_depth with a single 80/20 split. By luck, the test set landed on mostly *easy* examples — the ones any model gets right. You read 87% accuracy, pick the max_depth that scores best on this one split, and call it done. You deploy. Performance: 79%. What went wrong?

A single split is just one of the many ways you *could* have divided the data. You got a lucky draw, your estimate had high variance, and you staked the whole tuning decision on it.


k-fold: don't trust one split, average many

k-fold cross-validation runs several splits and averages them. With k=5 on 5,000 examples: cut the data into 5 groups of 1,000; train on 4,000 and test on the held-out 1,000; rotate so each group is the test set exactly once; average the 5 scores. Now every example is tested once, and your estimate is far steadier. Standard choices are k=5 (trains on 80% each time, tests on the other 20%) or k=10 (trains on 90%, tests on 10%, but slower). Leave-one-out (k = n) sounds ideal but is actually noisy — a single weird test example swings each fold's score.

Plain k-fold is right only when your rows are independent draws from the same distribution. Step outside that and you need a different flavour.


Stratified — when the classes are imbalanced

With a 5% positive rate, a careless random split can hand you one fold with 1% positives (too easy) and another with 9% (too hard); averaging scores from folds that are secretly different problems is misleading. Stratified k-fold forces every fold to hold roughly the same 5% positives. Stratify for any classification task where the positive class is under about 20%.


Group — when the same subject repeats

Suppose you have 50,000 hospital visits from 5,000 patients, and you will deploy on *new* patients. With plain k-fold, patient 142's January visit trains the model and their June visit tests it — so the model quietly memorises patient 142 and "predicts" a patient it already knows. Group k-fold keeps all of one patient's visits on the same side, so the test fold is always patients the model has never seen — which is the real deployment situation.


Time-series — walk forward, and mind the gap

On time-ordered data plain k-fold is not just rough, it is *wrong*: it lets the model train on month 10 to predict month 3. The fix is walk-forward validation — train on an early window, test on the next, then expand: train on [start … t], test on [t … t+1], and keep going, always keeping time in order. One subtlety most people miss: if any feature uses a rolling window (a 7-day average, say), a training row from day 89 and a test row from day 91 share raw data, because day 91's feature is built from days 85–91. So add a purge gap — drop the rows within one feature-window of the boundary — so the two sides never share ingredients.


One honest caveat

Cross-validation estimates performance on *new data from the same distribution, drawn the same way.* It does not save you if the world shifts before deployment, and it cannot undo leakage baked in before the split. And if you tune hyperparameters on the very folds you then report, you have quietly cheated: trying 200 configurations and reporting the best fold score is biased upward by the search itself. The clean fix is nested CV — an outer loop estimates quality on data the tuning never touched, while the inner loop does the full hyperparameter search. Slower, but it is one honest way to score your whole pipeline — a separate holdout test set the tuning never touches works too, if you can afford to set data aside instead of looping.


When the folds themselves are noisy: repeated k-fold

On small datasets a single 5-fold split is still one arbitrary partition — shuffle the rows differently and the estimate moves. Repeated k-fold runs the whole k-fold procedure several times with different random shuffles and averages, shrinking the variance of the estimate. It costs more compute but gives a steadier number when data is scarce or fold scores are unstable (like the [0.83, 0.84, 0.92, 0.85, 0.83] case where one fold is an outlier).


When you need both balance and isolation: stratified group k-fold

Stratified k-fold keeps the class ratio; group k-fold keeps entities together — but real problems often need *both*. Imbalanced medical data with multiple visits per patient wants every fold to hold the same rare-disease rate and never split a patient across folds. Stratified group k-fold does both at once. It's an approximation (you can't always satisfy both perfectly), but it's the right tool when you have imbalance and repeated subjects together — a very common combination.


Rolling versus expanding windows

Walk-forward comes in two flavours, and the choice is about drift. An expanding window always trains on *all* history up to time t — more data, better when the relationship is stable. A rolling (sliding) window trains only on the most *recent* fixed span, deliberately forgetting old data — better under strong concept drift, where ancient patterns actively mislead. Rule of thumb: expanding by default, rolling when the world changes fast enough that stale data hurts more than it helps.


Nested CV estimates the pipeline, not the final model

A subtlety people miss: nested CV gives you an unbiased estimate of your *model-selection procedure's* quality — but the model you actually ship is usually retrained on all available training data using the hyperparameters that procedure chose. Nested CV answers "how good is my process," not "here's the exact model." So report the nested-CV score as your honest performance estimate, then retrain on everything for deployment. Don't confuse the two.


Fold scores aren't independent — mean ± std isn't a confidence interval

Reporting mean ± std across folds is useful, but treat it carefully: the folds overlap in training data (each pair of 5-fold training sets shares most of their rows), so the fold scores are correlated, not independent draws. That means the naive standard error understates the true uncertainty, and the std across folds is *not* a valid 95% confidence interval. Use it as a rough spread and an instability flag, not as a rigorous statistical bound.


Stratifying regression and multi-label targets

Stratification isn't only for single-label classification. For regression, stratify on *binned* target values so each fold spans the full range (otherwise one fold can get all the cheap houses). For multi-label problems, use iterative/multi-label stratification that balances each label's distribution across folds. Naive random splitting can leave a rare label almost absent from some folds, making those folds unrepresentative.

Key points

Takeaway

Every CV strategy embeds an assumption about deployment — pick the wrong one and you are evaluating a scenario that does not exist, which is why walk-forward is mandatory for time-series and group k-fold is mandatory whenever new entities appear at inference time.

Recap

Check your understanding

Q1. You have 5 years of daily transaction data and want to build a fraud model. Which two of the following are correct parts of the right CV setup? Select two.

Q2. You run 5-fold CV and get AUC scores [0.83, 0.84, 0.92, 0.85, 0.83]. The mean is 0.854. Should you report this as your model's performance?

Q3. A colleague argues that since you are doing hyperparameter search with Optuna and reporting the best trial's validation score, you have a proper unbiased evaluation. Explain why this is wrong.

Q4. You have 3 years of daily sales data and strong seasonality/drift, and must forecast the next quarter. Between an expanding window and a rolling window for walk-forward CV, how do you choose?

Q5. You run nested CV and get an outer-loop estimate of 0.86 AUC. What model do you actually deploy, and what does the 0.86 represent?

Try it interactively

ML Systems Lab is a free interview-prep platform for ML engineers — work through the full interactive module, quizzes, and drills.

Open ML Systems Lab →