2.1.12Data Preprocessing & Feature Engineering

Train - validation - test splitting

3,137 words14 min readdifficulty · medium2 backlinks

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:

  1. Goal: Estimate performance on unseen data PunseenP_{\text{unseen}}
  2. Problem: We only have dataset DD
  3. 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)
  4. 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.

Figure — Train - validation - test splitting

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 cc with proportion pcp_c in dataset: nctrain=pc×ntrainn_c^{\text{train}} = \lfloor p_c \times n_{\text{train}} \rfloor ncval=pc×nvaln_c^{\text{val}} = \lfloor p_c \times n_{\text{val}} \rfloor nctest=pc×ntestn_c^{\text{test}} = \lfloor p_c \times n_{\text{test}} \rfloor

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 xx, standardization is: z=xμσz = \frac{x - \mu}{\sigma}

Where μ,σ\mu, \sigma must be computed from training set only: μtrain=1ntrainitrainxi\mu_{\text{train}} = \frac{1}{n_{\text{train}}} \sum_{i \in \text{train}} x_i σtrain=1ntrainitrain(xiμtrain)2\sigma_{\text{train}} = \sqrt{\frac{1}{n_{\text{train}}} \sum_{i \in \text{train}} (x_i - \mu_{\text{train}})^2}

Then apply these same μtrain,σtrain\mu_{\text{train}}, \sigma_{\text{train}} 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 (N<1000N < 1000), splitting removes too much training data. K-fold cross-validation solves this.

How it works:

  1. Split data into KK equal folds (typically K=5K=5 or K=10K=10)
  2. For each fold kk:
    • Train on K1K-1 folds
    • Validate on fold kk
  3. Average performance across all KK folds

Derivation of variance reduction: Single holdout uses nvaln_{\text{val}} samples for validation. K-fold cross-validation uses ALL nn samples for validation (each sample validated exactly once).

Variance of performance estimate: Var(holdout)σ2nval\text{Var}(\text{holdout}) \approx \frac{\sigma^2}{n_{\text{val}}} Var(k-fold)σ2n\text{Var}(\text{k-fold}) \approx \frac{\sigma^2}{n}

Since n>nvaln > n_{\text{val}}, k-fold gives more stable estimates.

Trade-off: Computational cost is K×K \times higher (must train KK 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


#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?
To prevent information leakage during model selection. If we try many models and pick the best test performer, we've implicitly trained on the test set through the selection process.
What is stratification and why is it important?
Stratification ensures each split has the same class distribution as the original dataset. Achieved by splitting each class separately then combining. Critical for imbalanced classes (e.g., 95%/5%) to prevent one split having no minority class samples.
What is the correct order for preprocessing: fit scaler on which set?
Fit preprocessing (scaler, imputer, encoder) ONLY on training set, then transform train/val/test using those fitted parameters. Never fit on all data before splitting.
Standard split ratios for different dataset sizes?
N<1k: use cross-validation; 1k<N<100k: 70/15/15 or 80/10/10; N>100k: 90:5:5 or 95:2.5:2.5 (10k test samples enough for good estimates)
Why must time series data NOT be split randomly?
Random splitting creates temporal leakage—model sees future to predict past, violating causality. Must split chronologically: Train→Validation→Test.
What is the fit-transform pattern?
Fit preprocessing on training data only (compute statistics like mean/std), then transform train/val/test using those same statistics. Prevents test data leakage through preprocessing parameters.
Does random shuffle-and-slice guarantee stratification?
No. Random shuffling only balances splits on average. For imbalanced/small datasets it can still misrepresent minority classes. True stratification splits each class separately.
What is the difference between the holdout method and three-way splitting?
"Holdout" strictly refers to a two-way train/test split. The three-way split extends it by adding a validation set (holdout validation with separate validation set), distinct from cross-validation.
How does stratified splitting preserve class distribution mathematically?
For class c with proportion p_c, allocate n_train^c = floor(p_c × n_train) samples to training set, similarly for val/test. Maintains proportions across splits.
Why use K-fold cross-validation instead of simple split?
For small datasets, single split "wastes" too much data in validation. K-fold uses all N samples for validation (each validated once), reducing variance of performance estimate from σ²/n_val to σ²/n.

Concept Map

partition

partition

partition

fits

tunes

estimates

prevents

kept hidden until

motivates

extends

balances

use instead

Dataset D

Training set

Validation set

Test set

Model parameters

Hyperparameters and model selection

Generalization performance

Information leakage bias

Final evaluation

Three-way split not two-way

Holdout method

Split ratios 60:20:20 etc.

Data trade-off tension

N less than 1000

Cross-validation

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.

Go deeper — visual, from zero

Test yourself — Data Preprocessing & Feature Engineering

Connections