Information Leakage in Cross-Validation
Information leakage happens when test-set information influences training, which makes performance estimates overly optimistic.
Try it: K-Fold Cross-Validation and Model Selection
How hold-out and k-fold cross-validation choose among candidate models: each fold takes a turn as the validation set, the fold errors are averaged, the lowest average picks the model, and only then is the untouched test set used once — while tuning on the test set would report an optimistic number.
How it works
- Set 10 test points aside; they take no part in any choice.
- Optionally shuffle the 20 development points (seeded), then cut them into k folds (hold-out: one chunk validates, the rest trains).
- For every round, train each candidate polynomial on the other folds and measure its validation MSE on the held-out fold.
- Average each candidate's fold errors (with repeated shuffling: average the p run scores; p × k fits per candidate).
- Select the candidate with the lowest average, retrain it on all development points, and evaluate it once on the test set.
- For contrast, show which candidate the test set itself would have picked — that figure is leaked and optimistic.
Default run (12 steps): 20 development points (sorted by x as they arrived) and 10 test points set aside. Candidates: polynomial degree 0, 1, 2, 3, 4, 5, 6. Plan: 5-fold cross-validation, shuffled with seed 1 → 5 fits per candidate (p × k = 1 × 5), 35 in total. … 5-fold cross-validation picks degree 3 (score 0.0768); its honest test MSE is 0.0889. Picking by test error would report degree 4 at 0.0868 — a leaked, optimistic figure.
Simplified: One fixed toy dataset (20 development + 10 test points from sin(2πx) with noise) and polynomial least squares of degree 0–6 as the candidate models. Shuffling uses a seeded mulberry32 Fisher–Yates; folds are contiguous chunks like scikit-learn's KFold. Hold-out uses the first of k chunks as the validation set.
Loading the simulation…
A Suspiciously Good Score on Random Data
Imagine a dataset in which every feature and the target are pure random noise. There is nothing real to learn. Now suppose you run a model on this data, score it with cross-validation, and the score comes back looking strong. A reasonable first reaction is that the model must have found something. This article is about why that reaction can be exactly wrong, and about the one habit that prevents it.
The cause is usually not a bug in the model or in the scoring code. It is a subtle ordering mistake in the workflow: a step that should have been repeated inside each cross-validation loop was performed once, on the full dataset, before the loop started. Understanding why that matters requires being precise about what cross-validation is supposed to measure.
Why Feature Selection Feels Harmless
A common belief is that feature selection is just a cleaning step that happens before modeling. It can therefore be done once on the full dataset, and cross-validation can then be run on the reduced data. The belief sounds tidy. Feature selection looks at inputs and the target, keeps the most useful columns, and passes the result along. Where would the harm be?
What Cross-Validation Is Supposed to Measure
Information leakage happens when test-set information influences training, which makes performance estimates overly optimistic.
Cross-validation is meant to estimate how a model performs on data it has never seen. The test fold is supposed to play the role of unseen data. If the test fold has already influenced which features were chosen, it is no longer unseen. The step that looked harmless has quietly used the answers from the test fold, and the score you read off at the end is no longer an honest forecast of performance on new data.
The important detail is that the leak does not require anyone to look at the test rows on purpose. It is enough that the selection procedure was fitted on the whole dataset. Selection keeps features that are correlated with the target. The test rows are part of the whole dataset, so the features that survive are tuned in part to the test rows.
Tracing the Leak Through a Leaky Workflow
The diagram below traces where test-fold information enters the workflow when selection runs on the full dataset before splitting. Follow the arrows from the raw data to the final score, and notice that the selector touches every row before any fold exists.
In this workflow the test rows have already shaped the set of features before any split is made. When cross-validation then evaluates the model, the held-out folds are no longer unseen. The features were chosen partly because they happened to correlate with those very rows, so the score is inflated.
Worked Example: Noise Data With a Leaky Selector
Selecting features on the full dataset
A dataset contains many features and a target, and every value is random noise. The workflow selects the 10 features most correlated with the target on all rows, then runs cross-validation on those 10 features. Why can the reported score look strong?
- 1Step 1: Note that there is no real signal
Every feature and the target are random, so no feature truly predicts the target on new data.
- 2Step 2: Run selection on all rows
The selector ranks features by correlation with the target using every row, including the rows that will later serve as test folds.
- 3Step 3: Keep the strongest correlators
With many random features, some will correlate with the target by chance. Selection keeps those chance correlations, and some of them are chance correlations that also hold in the test rows.
- 4Step 4: Cross-validate on the reduced data
Each test fold was part of the selection step, so it is no longer unseen. The model is scored on rows whose answers helped choose its features.
- 5Step 5: Read the score
The score looks strong because the features were tuned to the test rows, not because the model learned a real pattern.
The strong score is an overly optimistic estimate produced by information leakage from the test folds into feature selection.
Mistakes That Create the Leak
Common mistake Running feature selection on the full dataset before calling cross-validation
What it looks likeselector.fit(X, y) on all rows, then cross_val_score on X_selectedWhy it failsEvery test fold has already influenced which columns were kept, so it is no longer unseen data.
Do this insteadPlace the selector and the model in one Pipeline and pass the Pipeline to cross-validation.
Common mistake Assuming that a leak needs someone to look at the test rows on purpose
Why it failsThe leak comes from the selection procedure being fitted on the whole dataset, not from deliberate peeking.
Do this insteadCheck the order of operations, not your intentions.
Common mistake Dismissing a lower cross-validation score after fixing the workflow
Why it failsA lower score is often the honest result once the test folds are kept unseen.
Do this insteadTreat the more pessimistic score as the more trustworthy estimate.
Refitting Selection Inside Each Split
A Pipeline chains several steps, such as a feature selector followed by a model, into a single object. When that object is passed to cross-validation, each fold triggers its own fit. The selector is fitted on the training folds of that fold only, the model is fitted on the same reduced training data, and then both are applied to the test fold. The selector never sees the test fold during its fit, because the pipeline does not allow it to.
The loop then repeats for the next split with new training and test folds. Each repetition starts from the training portion of that split, so the set of selected features can differ from one split to another. That variation is part of the honest picture: the pipeline is measuring how the whole procedure behaves on unseen folds.
Leaky Versus Pipeline-Based Workflows Side by Side
The two workflows perform the same kinds of steps. What differs is the moment at which selection happens relative to the split, and therefore which rows each step touches. The comparison below lays them out in two columns.
Building a Pipeline That Keeps Folds Separate
The code below is an illustrative sketch of the Pipeline pattern described above. It places a feature selector and a model in one object and passes that object to cross-validation, so each split refits the selector on its own training folds.
- Pipeline chains the selector and the model into one object
- SelectKBest is the selection step, fitted per fold
cross_val_scorerefits the whole pipeline inside each split
The crucial point is not the specific selector or model. The key is that the selection step lives inside the object that cross-validation refits. Written this way, the test fold is only ever transformed by the selector, never used to decide which features it keeps.
Checking Your Understanding of the Leak
A colleague runs SelectKBest on the full feature matrix, then calls cross-validation on the reduced matrix. Before reading further, predict at which step test-fold information first enters the workflow, and say whether the reported score is likely to be too high or too low. Then explain how moving the selector into a Pipeline changes that answer.
Hints
- Ask which rows the selector was fitted on, not which model is used.
- Think about whether each test fold was already part of the selection decision.
- Consider what the Pipeline makes cross-validation refit on each split.
Key Takeaways
- Information leakage happens when test-set information influences training, which makes performance estimates overly optimistic.
- Feature selection fitted on the full dataset before cross-validation leaks test-fold information into the selected features, even without anyone deliberately examining the test rows.
- A Pipeline that includes the selector and the model, passed to cross-validation, refits the selection on the training folds of each split, so the test fold is only transformed and never used to choose features.
- A lower, more pessimistic cross-validation score can be the honest result and should not be dismissed as a failure.
- The main lesson concerns where a step is fitted, not which step is used.