Skip to content
Phuc's Blog
Go back

Generalization and Model Selection

Edit page
📌Series:Artificial Intelligent
Part 4 of 6
View all articles in this series

In Machine Learning, getting 100% accuracy on your training data is rarely good news. In fact, it is often a warning sign. What really matters is how well your model performs on new, unseen data.

In this article, we break down what generalization really means, what factors control model complexity, how to safely split your data, and how to use K-Fold Cross-Validation.

Generalization and Model Selection

Table of contents

Open Table of contents

1. Memorization vs. Generalization: The Student Metaphor

Imagine two students preparing for an exam:

What happens on exam day?

ScenarioHarry’s ScoreJack’s Score
Old Questions (Repeated from past years)100% (Perfect recall)90% (A few small slips)
New Questions (Brand-new problems)< 50% (Struggles completely)90% (Solves them easily)

In machine learning, we want our models to be Jack, not Harry. We do not want models that just remember past examples; we want models that discover underlying rules so they can make accurate predictions on future data.


2. The Generalization Gap

When training any model, we track two types of errors:

  1. Training Error: The error on the data the model is currently learning from.
  2. Generalization Error: The error when the model is tested on new, unseen data (measured using a separate validation or test set).

The difference between these two errors is the Generalization Gap:

Generalization Gap=Validation Error−Training Error\text{Generalization Gap} = \text{Validation Error} - \text{Training Error}

Why Low Training Error Can Fool You

Deep neural networks have millions of parameters. With that much capacity, a network can easily memorize every single training sample with 0%0\% training error without understanding the true pattern.

Key takeaway: A low training error does not mean your model works. You must always verify its performance on a separate validation set.


3. Model Complexity

Finding the right model is all about balance — you want a model that is neither too simple nor too complex.

Model Complexity vs. Error: Bias-Variance Tradeoff and Generalization Gap

Karl Popper’s Principle of Falsifiability

Note

The philosopher Karl Popper argued that a scientific theory must be falsifiable — if an explanation can fit any outcome (even complete nonsense), it is not scientific.

In Machine Learning:

  • If your model is so flexible that it can achieve 0%0\% error even when labels are assigned completely at random, getting 0%0\% on real data proves nothing.
  • A reliable model is constrained enough that it fails on random noise, but succeeds on real-world patterns.

4. What Factors Define Model Complexity?

What actually makes a model “simple” or “complex”? It comes down to five key factors:

  1. Number of Parameters (Weights): More weights give the model more flexibility to curve and bend. A model with 2 weights (y=wx+by = wx + b) can only draw a line. A model with millions of weights can outline almost any shape.
  2. Size of the Weights (Parameter Range): Model complexity is not just about the number of weights — it is also about how big those weights are allowed to become.
    • If weights can grow huge, tiny changes in input cause wild swings in output (overfitting).
    • Restricting weights to remain small keeps the model smooth and well-behaved. This is the exact idea behind Weight Decay (L2L_2 Regularization):
Ltotal=LMSE+λ2∥w∥22\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{MSE}} + \frac{\lambda}{2} \|\mathbf{w}\|_2^2
  1. Number of Input Features: Feeding raw features (xx) keeps things simple. Adding polynomial features (x2,x3,…,x10x^2, x^3, \dots, x^{10}) dramatically increases model complexity.
  2. Network Depth and Architecture: Adding more layers and non-linear activations increases how complicated of a relationship the network can represent.
  3. Training Time (Epochs): Training for too many iterations gives the model extra time to start fitting noise in the training set.

5. How Dataset Size Changes the Game

A model’s complexity should always match the size of your dataset:

Golden Rule: Never increase model complexity faster than your dataset size.


6. Model Selection: The 3-Way Data Split

When developing a machine learning model, you have to make many choices: how many layers to use, which learning rate to set, how much weight decay to apply, and which features to keep. This trial-and-error process is called Model Selection.

To select the best model fairly, you must divide your data into three separate sets:

  1. Training Set (The Practice Homework): Used by the optimization algorithm (like SGD or Adam) to update weights w\mathbf{w} and biases bb.
  2. Validation Set (The Mock Exam): Used by you to compare different models, tune hyperparameters, and check for overfitting.
  3. Test Set (The Final Exam in a Sealed Vault): Kept completely untouched while you experiment. It is evaluated only once at the very end to give an honest, unbiased score of how the model will perform in the real world.

7. K-Fold Cross-Validation: Safe Validation for Small Data

When you have a small dataset, setting aside 20% to 30% for validation can feel wasteful — you lose precious training data that your model needs.

K-Fold Cross-Validation solves this problem by using all of your data for both training and validation without leaking information.

K-Fold Cross-Validation: Rotating validation folds across K iterations

How It Works

  1. Split: Divide the dataset into KK equal parts (folds), typically K=5K = 5 or K=10K = 10.
  2. Rotate: Train the model KK times. In each round, use 1 fold as the validation set and the remaining K−1K - 1 folds as the training set.
  3. Average: Calculate the average validation score across all KK rounds:
CV Score=1K∑k=1KScorek\text{CV Score} = \frac{1}{K} \sum_{k=1}^{K} \text{Score}_k

Code Implementation

In PyTorch, slicing and rotating the KK folds can be implemented in just a few lines:

def get_k_fold_data(k, i, X, y):
    fold_size = X.shape[0] // k
    start, end = i * fold_size, (i + 1) * fold_size

    X_val, y_val = X[start:end], y[start:end]
    X_train = torch.cat([X[:start], X[end:]], dim=0)
    y_train = torch.cat([y[:start], y[end:]], dim=0)

    return X_train, y_train, X_val, y_val

Then, run the loop across all KK folds:

models = []
all_train_losses, all_val_losses = [], []

for i in range(k):
    X_tr, y_tr, X_va, y_va = get_k_fold_data(k, i, X_train, y_train)

    # Must create a fresh model for every fold to avoid data leakage
    model = LinearRegression(num_features, lr=0.05)
    train_loss, val_loss = train(model, X_tr, y_tr, X_va, y_va, epochs=150)

    models.append(model)
    all_train_losses.append(train_loss)
    all_val_losses.append(val_loss)

avg_train_loss = sum(all_train_losses) / k
avg_val_loss = sum(all_val_losses) / k

Predicting with K-Fold

Using K models, for final predictions on the test set, pass X_test through each of the KK models and average their outputs:

with torch.no_grad():
    preds = [model(X_test) for model in models]
    y_test_pred = torch.stack(preds).mean(dim=0)
    real_prices = torch.exp(y_test_pred).numpy()
Tip

Averaging predictions across all KK models cancels out the individual errors of each fold model, resulting in smoother and more reliable test predictions.


8. Conclusion


Edit page
Share this post:

Previous Post
Classification and Softmax Regression
Next Post
Linear Regression from Scratch with PyTorch

Comments