Train - validation - test splitting
The Three-Way Split: Purpose & Philosophy
Training set is where the model learns patterns. Validation set is where we tune hyperparameters and make architecture decisions. Test set is the final exam—untouched until the very end, it measures real-world performance.
Why Three Sets Instead of Two?
The information leakage problem: If you use the same data to both select the best model AND evaluate it, your performance estimate is optimistically biased.
Derivation from first principles:
- Goal: Estimate performance on unseen data
- Problem: We only have dataset
- Naive approach: Split into train/test, evaluate on test → But if we try100 models and pick the best test score, we've implicitly trained on the test set (through model selection)
- Solution: Use validation set for model selection, keep test set completely hidden until final evaluation
This extends the holdout method (a two-way train/test split) by adding a validation set. Strictly speaking, "holdout" refers to holding out a single test set; the three-way version is often called holdout validation with a separate validation set to distinguish it from cross-validation.

The Splitting Process: Step-by-Step
Step 1: Shuffle (with stratification)
Why shuffle? Raw datasets often have temporal ordering or grouped structure. If first80% are all class A and last 20% are class B, a sequential split fails catastrophically.
Why stratify? For classification with imbalanced classes (e.g., 95% negative, 5% positive), random splitting might put all positives in training and none in validation.
Stratification math: For class with proportion in dataset:
This ensures each split has approximately the same class distribution as the original dataset.
Step 2: Assign indices (stratified)
To actually preserve class proportions, we must split within each class separately, then concatenate. A plain shuffle-and-slice does NOT guarantee stratification.
import numpy as np
def stratified_train_val_test_split(X, y, train_size=0.7, val_size=0.15,
random_state=42):
"""
Split data into train/val/test sets WITH true stratification.
We split each class's indices separately, then combine, so that
every split preserves the original class proportions.
"""
rng = np.random.default_rng(random_state)
train_idx, val_idx, test_idx = [], [], []
for cls in np.unique(y):
# Indices belonging to this class only
cls_indices = np.where(y == cls)[0]
rng.shuffle(cls_indices) # shuffle within the class
n_cls = len(cls_indices)
train_end = int(train_size * n_cls)
val_end = train_end + int(val_size * n_cls)
train_idx.extend(cls_indices[:train_end])
val_idx.extend(cls_indices[train_end:val_end])
test_idx.extend(cls_indices[val_end:])
# Shuffle the combined index lists so classes are interleaved
train_idx = rng.permutation(train_idx)
val_idx = rng.permutation(val_idx)
test_idx = rng.permutation(test_idx)
return (X[train_idx], y[train_idx],
X[val_idx], y[val_idx],
X[test_idx], y[test_idx])Why this step? Working with indices instead of copying data is memory-efficient and preserves the ability to trace back to original samples.
Step 3: Fit-Transform Pattern (Critical!)
Data preprocessing must use ONLY training set statistics.
The leakage scenario:
# WRONG! Test data leaks into normalization
scaler = StandardScaler()
X_scaled = scaler.fit(X_all) # Uses test set mean/std
X_train_scaled = X_scaled[:train_end]
X_test_scaled = X_scaled[train_end:]Why wrong? The model indirectly "sees" test data through the scaling parameters. If test set has unusual values, they influence the mean/std used for training.
Correct approach:
# RIGHT! Only train statistics
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # Compute μ, σ from train only
X_val_scaled = scaler.transform(X_val) # Apply train μ, σ
X_test_scaled = scaler.transform(X_test) # Apply train μ, σDerivation: For feature , standardization is:
Where must be computed from training set only:
Then apply these same to validation and test sets.
Recall Explain to a 12-Year-Old
Imagine you're learning to recognize different types of birds. You have 100 bird photos.
Bad way: Look at all 100 photos while learning. Then test yourself on... the same 100 photos. You'll do great! But did you really learn to recognize birds, or did you just memorize those specific photos?
Better way:
- Take 70 photos → Train set (these are your study materials)
- Hide 15 photos → Validation set (use these to quiz yourself and figure out if you need to study more or change your approach)
- Hide another 15 photos → Test set (the FINAL exam, only look at these once at the very end)
The validation set is like practice quizzes—you can retake them and adjust your study strategy. The test set is the real final exam—you only get one shot, and it tells you if you REALLY learned or just got lucky.
The magic trick: By hiding some photos completely until the end, you make sure you're learning "what makes a robin" instead of just memorizing "photo #23 is a robin."
Cross-Validation: When Simple Splitting Isn't Enough
For small datasets (), splitting removes too much training data. K-fold cross-validation solves this.
How it works:
- Split data into equal folds (typically or )
- For each fold :
- Train on folds
- Validate on fold
- Average performance across all folds
Derivation of variance reduction: Single holdout uses samples for validation. K-fold cross-validation uses ALL samples for validation (each sample validated exactly once).
Variance of performance estimate:
Since , k-fold gives more stable estimates.
Trade-off: Computational cost is higher (must train models instead of 1).
Time Series and Special Cases
Other special cases:
- Grouped data (multiple samples per patient/user): Use GroupKFold to ensure all samples from one group stay together
- Imbalanced classes (1% positive): Use StratifiedKFold to maintain class ratios
- Multiclass: Stratify on the target variable to balance all classes
Connections
- 2.1.11-Feature-scaling-normalization - Must be fit only on training set
- 2.1.13-Cross-validation - Advanced splitting strategy for small datasets
- 3.2.1-Overfitting-underfitting - Why we split: to detect overfitting
- 4.1.5-Early-stopping - Uses validation set, not test set
- 6.3.2-Data-leakage - What happens when splitting is done wrong
#flashcards/ai-ml
What are the three subsets in train/validation/test splitting and their purposes? :: Training set (learn patterns/fit parameters), Validation set (tune hyperparameters/select models), Test set (final performance evaluation on completely unseen data)
Why do we need a separate validation set instead of just train/test?
What is stratification and why is it important?
What is the correct order for preprocessing: fit scaler on which set?
Standard split ratios for different dataset sizes?
Why must time series data NOT be split randomly?
What is the fit-transform pattern?
Does random shuffle-and-slice guarantee stratification?
What is the difference between the holdout method and three-way splitting?
How does stratified splitting preserve class distribution mathematically?
Why use K-fold cross-validation instead of simple split?
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Chalo ise simple tareeke se samajhte hain. Machine learning model ko agar hum saara data seekhne ke liye de dein, toh woh cheating kar sakta hai—matlab woh practice problems ko ratta maar leta hai bina asli concept samjhe. Bilkul waise jaise exam se pehle sirf practice questions memorize kar lo, toh practice mein toh 100% aayega par naye questions pe fail ho jaoge. Isiliye hum data ko teen hisson mein baant dete hain: Training set (jahan model patterns seekhta hai), Validation set (jahan hum hyperparameters tune karte hain aur best model choose karte hain), aur Test set (jo bilkul chhupa ke rakhte hain, sirf final exam ke liye).
Ab sawaal ye hai ki teen sets kyun, do kyun nahi? Iska core reason hai information leakage. Agar hum ek hi data pe model select bhi karein aur usi pe evaluate bhi karein, toh performance ka number jhoota-optimistic ho jaata hai. Socho tum 100 models try karte ho aur test score dekh ke sabse best wala uthate ho—toh actually tumne test set ko indirectly training mein use kar liya! Isiliye validation set separate rakhte hain model chunne ke liye, aur test set ko end tak chhupa ke rakhte hain taaki uska number honest aur real-world jaisa mile.
Split ka ratio data size pe depend karta hai—chhote data (N<1000) ke liye cross-validation better hai, medium data ke liye 70:15:15 ya 80:10:10, aur bahut bade data (lakhon samples) mein 95:2.5:2.5 bhi chalta hai kyunki 10,000 test samples hi confident estimate dene ke liye kaafi hote hain. Ek important baat—splitting se pehle data ko shuffle aur stratify karna zaroori hai, warna agar data ordered ho ya classes imbalanced hon (jaise 95% negative, 5% positive), toh galat split se model theek se seekh hi nahi paayega. Ye poora concept isiliye matter karta hai kyunki real duniya mein tumhara model naye, anseen data pe kaam karega—aur ye splitting hi tumhe batati hai ki model ne genuinely seekha hai ya sirf ratta maara hai.