Fixing Overfitting in Machine Learning: Diagnosis, Regularization, and Fixes That Actually Work

Overfitting means your model memorised the training data: training accuracy climbs while validation stalls or drops. Fix it by confirming the diagnosis from learning curves, cleaning split leakage and label errors, adding augmentation, using early stopping with highest-score weight restoration, tuning regularization or dropout one knob at a time, and simplifying the model if the gap persists. This guide gives the decision procedure, worked examples, and per-algorithm fixes.

Written by Projectech16 min readPublished
For B.E./B.Tech Computer Science, IT and AI/ML final-year students training ML and deep-learning models Topics: Python, Scikit-learn, TensorFlow, Keras, PyTorch, Cross-validation
Editorial illustration of overfitting: two chart panels comparing a smooth curve fitting data cleanly against an overcomplicated wiggly curve memorising every noisy point, with a neural network silhouette.
Illustration generated for this guide.
In this guide

Your model scores 98% on the training data and 61% on new data. It has not learned your problem — it has memorised your dataset. This is overfitting, and it is the single most common reason student machine-learning projects collapse between the notebook and the demo: everything looks excellent in training, and then the model meets data it has never seen.

Overfitting is not a mysterious curse. It is a diagnosable condition with a standard set of fixes, and this guide treats it that way: how to recognise it from your learning curves, a decision procedure for choosing the right fix, each fix explained with the math you need for your report, and the mistakes that make it worse. The examples use the kind of image and tabular projects students actually build — classifiers like brain tumor detection with CNNs and waste classification with CNNs — but the principles apply to every supervised model.

Diagnosis first: reading the learning curves

Before fixing anything, confirm the diagnosis. Train your model while recording training loss and validation loss (or accuracy) per epoch, and plot both. There are exactly three patterns:

Pattern 1 — the overfitting signature: training loss keeps falling while validation loss falls, bottoms out, then starts rising. The gap between them widens. Training accuracy climbs toward its ceiling while validation accuracy plateaus or drops. This is overfitting: the model is memorising training examples, including their noise.

Pattern 2 — underfitting: both training and validation loss stay high and flat, or validation tracks training closely at a poor level. The model is too simple, the features are wrong, or there is a bug. The fixes below will not help — you need a more expressive model or better features first.

Pattern 3 — healthy training: both losses fall together and converge with a small, stable gap. Validation loss may wiggle but does not climb. Nothing to fix; this is what you are aiming for.

Golden rule: never apply an overfitting fix until you have seen Pattern 1 on your own curves. Applying dropout to an underfit model makes a bad model worse, and students do this constantly because "dropout fixes overfitting" became a reflex.

A second diagnostic, free of charge: compare against a dumb baseline. If a logistic regression or a tiny decision tree gets 70% and your 50-layer network gets 72% on validation, the network is mostly memorising. Baselines keep you honest.

Why overfitting happens: the three ingredients

Overfitting needs all three of these to some degree:

  1. Model capacity exceeds what the data supports. A CNN with millions of parameters trained on 800 images has far more freedom than the data can constrain — memorisation is the path of least resistance.
  2. Too little (or too repetitive) data. 800 near-identical photos from one afternoon of shooting are effectively far fewer than 800 independent examples.
  3. Training runs too long. Given enough epochs, most models will eventually memorise. The validation curve's turning point is the signal to stop.

Every fix below attacks one of these three.

The fixes, in the order to try them

Fix 1: More (and more varied) data — attacks ingredient 2

The most effective fix is also the least glamorous. Before touching hyperparameters:

  • Collect more real data, prioritising variation: different lighting, angles, backgrounds, devices. One hundred varied images beat one thousand near-duplicates.
  • Check for leakage and duplication between train and validation — duplicates across the split fake good validation scores and hide overfitting. Hash your images (or rows) and drop cross-split duplicates.
  • Clean label errors. A mislabelled 5–10% of training data is common in student datasets and acts as noise the model memorises. Spot-check a few hundred labels.

If more real data is genuinely impossible, augmentation is the next most effective option.

Fix 2: Data augmentation — cheap, effective, and standard

Augmentation creates modified copies of training examples: rotations, flips, crops, brightness/colour shifts, and for tabular data, small noise injection. The model sees the same object under many appearances and learns the invariant features instead of memorising pixels.

For image projects (the CNN classifiers above), the standard student augmentation set:

from tensorflow.keras.preprocessing.image import ImageDataGenerator

augmenter = ImageDataGenerator(
    rotation_range=20,       # rotate up to 20 degrees
    width_shift_range=0.15,   # horizontal shift up to 15%
    height_shift_range=0.15,
    horizontal_flip=True,     # mirror images
    zoom_range=0.15,
    brightness_range=[0.8, 1.2],
    fill_mode="nearest",
)

For PyTorch, the equivalent is torchvision.transforms (RandomHorizontalFlip, RandomRotation, ColorJitter, RandomResizedCrop). Practical notes:

  • Augment training data only — never validation or test. Validation must reflect real, unaugmented data, or your metrics are meaningless.
  • Keep augmentation plausible. Vertical flips of street scenes, 180° rotations of text, or colour shifts that change the class signal (stain colours in medical images) create examples that will never occur and can hurt. Augmentation should mimic the variation your deployment will actually see.
  • Augmentation is not a substitute for real variation. It multiplies what you have; it cannot invent new backgrounds, new object poses, or new lighting regimes.

Fix 3: Cross-validation — measuring honestly on small data

With a small dataset, a single train/validation split is noisy: your validation score depends on which examples happened to land in the split. K-fold cross-validation trains k times, each time holding out a different fold as validation, and averages the scores. With k=5, every example is validated exactly once, and the spread of the five scores tells you how stable your model is — a wide spread is itself an overfitting warning.

from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
import numpy as np

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(RandomForestClassifier(n_estimators=200), X, y, cv=cv, scoring="f1")
print("F1 per fold:", scores)
print("Mean F1: {:.3f} (+/- {:.3f})".format(scores.mean(), scores.std()))

Use stratified folds for classification so each fold keeps the class ratios. A worked reading: folds scoring [0.81, 0.79, 0.83, 0.62, 0.80] — the 0.62 fold is telling you the model is brittle on some slice of the data. Investigate that fold's examples before celebrating the 0.80 mean. (Cross-validation measures overfitting; it does not cure it — pair it with the fixes below.)

Fix 4: Regularization (L1/L2) — penalising complexity

Regularization adds a penalty to the loss for large weights, pushing the model toward simpler solutions that generalise. L2 (ridge) penalty, the common choice:

total_loss = data_loss + λ × Σ(w²)

λ (lambda) controls the strength: larger λ → smaller weights → simpler model. In scikit-learn's logistic regression the same idea appears inverted as C (smaller C = stronger regularization). In Keras:

from tensorflow.keras import regularizers

Dense(128, activation="relu",
      kernel_regularizer=regularizers.l2(0.001))

Start λ around 1e-3 to 1e-4 and tune on validation. L1 (lasso) additionally drives some weights to exactly zero, which performs feature selection — useful when you suspect many features are irrelevant. For tree models, the equivalents are max_depth, min_samples_leaf, and min_samples_split: a random forest with unlimited depth on 500 rows will memorise; capping depth is regularization by another name.

Fix 5: Dropout — for neural networks

Dropout randomly deactivates a fraction of neurons during each training step (typically 0.2–0.5 for dense layers, lower for convolutional layers), forcing the network to learn redundant representations instead of relying on a few memorised pathways. It is disabled automatically at inference.

from tensorflow.keras.layers import Dropout

model.add(Dense(128, activation="relu"))
model.add(Dropout(0.4))   # drop 40% of activations during training

Rules: apply dropout to dense/fully-connected layers, not to the input layer at high rates; 0.5 is the historical default for dense layers but 0.2–0.4 often works better on small student datasets; if training accuracy collapses when you add dropout, the rate is too high or the model was underfitting (see the golden rule above).

Fix 6: Early stopping — free and underused

Stop training when validation performance stops improving, and keep the highest-scoring checkpoint — not the last one. This directly attacks ingredient 3 (training too long):

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

callbacks = [
    EarlyStopping(monitor="val_loss", patience=15,
                  restore_best_weights=True, verbose=1),
    ModelCheckpoint("best_model.h5", monitor="val_loss",
                    save_best_only=True, verbose=1),
]
model.fit(X_train, y_train, validation_data=(X_val, y_val),
          epochs=200, callbacks=callbacks)

patience=15 tolerates 15 epochs of no improvement before stopping — validation curves wiggle, and patience 3 stops at the first wiggle. restore_best_weights=True is the part students forget: without it, early stopping halts training but leaves you with the overfit final weights instead of the highest-scoring ones. In scikit-learn's MLPClassifier, the equivalent is early_stopping=True with n_iter_no_change.

Fix 7: Simplify the model — attacks ingredient 1

If a 50-layer network overfits your 2,000-image dataset, the honest fix may be a smaller network, not stronger regularization. Try halving the dense-layer sizes or dropping a convolutional block and compare validation curves. For tabular data, prefer fewer, shallower trees. A smaller model that generalises beats a large model that memorises — and it trains faster, which compounds across every experiment you run.

Fix 8: Transfer learning — borrow someone else's generalization

For image projects, starting from ImageNet-pretrained weights (freezing the early layers) is one of the strongest anti-overfitting moves available: the backbone's features were learned from millions of images and are far less prone to memorising your small dataset than randomly initialised weights. The companion guide CNN Transfer Learning: A Student's Guide covers this in depth, including how much to freeze.

Choosing the fix: a decision procedure

Do not apply all fixes at once — you will never learn which one worked, and your report's methodology section will be fiction. Change one thing at a time, in this order:

  1. Plot the curves. Confirm Pattern 1 (overfitting), not Pattern 2 (underfitting).
  2. Check the data first: duplicates across splits? label errors? If yes, fix and re-train before anything else — no hyperparameter survives bad data.
  3. Add augmentation (for images) or collect more varied data. Re-train, compare validation curves.
  4. Add early stopping if not already present — it costs nothing and only helps.
  5. Add regularization or dropout, tuned on validation — one at a time.
  6. Simplify the model if the gap persists.
  7. Re-measure with cross-validation and report the mean and spread.

Keep a one-line experiment log: date, change, validation score, notes. "2026-09-10: added dropout 0.3 → val F1 0.71→0.76" is the raw material your report's methodology section is written from. Students who skip the log end up reconstructing it from memory the night before submission, badly.

The mistakes that make overfitting worse

Mistake Why it hurts
Tuning on the test set The test set becomes a second validation set; your reported numbers are optimistic and your demo still fails
Augmenting validation/test data Metrics no longer reflect real data; you are grading your own homework
Applying every fix simultaneously No attribution — you cannot say what worked, and the report's methodology is guesswork
Dropout/regularization on an underfit model Suppresses the capacity the model actually needs; both curves get worse
Judging by training accuracy Training accuracy saturating near its maximum alongside a wide validation gap is the disease; it is never the cure
Ignoring class imbalance A model can hit 95% accuracy by always predicting the majority class — read the metrics guide below
More epochs as the answer to everything Training longer past the validation minimum is literally the mechanism of overfitting

On that last-but-one point: if your classes are imbalanced, fix your metrics before your model. The companion guide ML Evaluation Metrics Explained shows why accuracy lies and what to report instead — precision, recall, and F1 per class.

Worked example: a waste-classifier that memorised

A concrete composite from real student builds, similar to the waste classification CNN: 1,200 phone photos across 4 waste categories, trained with a from-scratch CNN. Epoch 30: training accuracy 99%, validation accuracy 64%, validation loss rising since epoch 12. Classic Pattern 1.

The fix sequence and what each step did (validation accuracy):

  1. Removed 140 near-duplicate images leaking across the train/val split → 64% → 68% (the original score was inflated).
  2. Added augmentation (rotation, flip, brightness, zoom) → 68% → 76%.
  3. Switched to a pretrained MobileNetV2 backbone, frozen → 76% → 86%.
  4. Added dropout 0.3 on the classifier head + early stopping (patience 12) → 86% → 88%, and training stopped at epoch 41 instead of running to 100.

No single step was dramatic; the stack was. Note what was not done: the dataset was never enlarged beyond the original 1,200 images. Better use of the data beat more data.

Worked example: reading a 5-fold cross-validation result

Suppose your waste classifier's 5-fold CV gives per-fold F1 scores of [0.78, 0.81, 0.75, 0.62, 0.80]. Here is how to read this like an engineer:

  • Mean 0.752 is your honest performance estimate — report this, not the 0.81 from the luckiest fold.
  • Standard deviation ≈ 0.066 is large relative to the mean. The model is unstable: small changes in which data it trains on cause large swings.
  • Fold 4 (0.62) is the diagnostic gold. Pull the validation examples from that fold and look at them. In real builds, the weak fold almost always has a story: images from one phone camera, one lighting condition, one class underrepresented, or a cluster of mislabelled examples. Fix the story, not the hyperparameter.

Now apply one fix — say, stronger augmentation — and re-run the same 5 folds with the same random seed. New scores: [0.82, 0.84, 0.81, 0.74, 0.83]. Mean 0.808, std ≈ 0.036. Two improvements at once: higher mean and tighter spread. That tighter spread is the signature of reduced overfitting, and it belongs in your report as a before/after table. Same seed matters — without it, you cannot tell whether the improvement came from your fix or from a luckier split.

Learning rate and batch size: the quiet overfitting levers

Two training settings influence overfitting without being labelled as regularization:

  • Learning rate too high → training loss oscillates or diverges; the model never settles. Too low → training crawls, and given enough epochs the model still memorises, just slowly. The practical student approach: start at the framework default (Adam 1e-3), and if validation plateaus early while training loss is still falling steeply, the rate is fine and the problem is capacity/data — reach for the fixes above, not the LR dial.
  • Batch size has a mild regularization effect: smaller batches (16–32) add gradient noise that slightly discourages memorisation; very large batches can converge to sharper minima that generalise worse. On student hardware you are usually forced into small batches by VRAM anyway — treat it as a free mild regularizer, not a tuning knob worth days of experiments.
  • Learning-rate schedules (reduce on plateau, cosine decay) help the model settle into better minima late in training. In Keras, ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=5) is a one-line addition that pairs well with early stopping: when progress stalls, halve the rate; if it still stalls, early stopping ends the run. Cheap, effective, and one line in the methodology section.

Tuning regularization without guessing: a walkthrough

Regularization strength (λ for L2, dropout rate) is tuned on validation, one knob at a time. A concrete walkthrough for L2 on a dense network:

  1. Baseline (no regularization): train accuracy 0.99, validation 0.71. Gap 0.28 — clear overfitting.
  2. λ = 1e-5: train 0.97, val 0.74. Gap shrinking, both moving the right way.
  3. λ = 1e-4: train 0.93, val 0.78. The sweet spot region — validation still rising.
  4. λ = 1e-3: train 0.88, val 0.79. Marginal gain; watch closely.
  5. λ = 1e-2: train 0.80, val 0.75. Now regularization is hurting — the model is underfitting, starved of capacity.

The curve of validation score vs λ is an inverted U; your job is to land near the top. Note the pattern: as λ rises, training accuracy falls while validation rises — that is regularization working as intended, and students who panic at falling training accuracy ("the model is getting worse!") misunderstand what they are seeing. Report the λ you chose and the two or three values around it; the table proves the choice was experimental, not decorative.

The same inverted-U logic applies to dropout rates (try 0.2, 0.3, 0.4, 0.5 on the dense layers) and to tree depth caps. One knob, a few values, validation scores in a table, pick the peak. Methodology sections are built from exactly this kind of table.

The one-page overfitting diagnosis worksheet

When a model underperforms, run this worksheet before changing anything. Answer in writing — in your lab notebook or a markdown file — because written answers prevent the "I think I tried that" loop:

  1. What are the numbers? Training accuracy/loss, validation accuracy/loss, at the final epoch and at the validation minimum. Write all four.
  2. Which pattern? Overfitting (gap widening, val loss rising), underfitting (both bad), or healthy (small stable gap)? Sketch the curves or paste the plot.
  3. When did validation peak? Epoch N of total M. If N < M/3, training ran far too long — early stopping alone may close most of the gap.
  4. How big is the dataset, really? Count images/rows after removing near-duplicates. "1,200 images" that are 400 scenes × 3 near-identical frames is a 400-example dataset wearing a costume.
  5. Any leakage? List exactly how the split was made (script name, seed, split level: image/row/scene/patient). If you cannot answer precisely, assume leakage until proven otherwise.
  6. What did the last change do? One line from the experiment log. If there is no log entry, the last change does not count as an experiment.

Worksheets like this feel slow, but they convert a week of random hyperparameter thrashing into an afternoon of directed fixes. Attach the filled worksheet as a report appendix if your guide allows it — it is unusually convincing evidence of engineering process.

Overfitting in non-neural models: it is not just a deep-learning problem

Students associate overfitting with neural networks, but every model family has its version:

  • Decision trees / random forests: a single deep tree memorises training rows perfectly. Cap max_depth (start 5–10), require min_samples_leaf ≥ 5–20, and compare against a depth-limited baseline. Random forests reduce variance by averaging, but 500 deep trees on 300 rows still overfit — they just overfit more smoothly.
  • Gradient boosting (XGBoost/LightGBM): the most common student tabular model and an enthusiastic overfitter. The controls are max_depth, min_child_weight, subsample and colsample_bytree (both < 1.0 inject randomness), reg_lambda, and crucially early_stopping_rounds with a validation set — the exact analogue of neural early stopping.
  • k-NN: k=1 memorises by construction (training accuracy saturates, always). Small k overfits, large k underfits; tune k by cross-validation and say so.
  • Linear/logistic regression: rarely overfits on its own, but with hundreds of engineered features on dozens of rows it will. L1/L2 regularization (C in scikit-learn) is the fix, tuned as in the walkthrough above.

The unifying principle: every algorithm has a capacity knob, and "more capacity" is never the default answer on a small dataset. When your viva examiner asks "why did you set max_depth=6?", the answer is "cross-validation showed deeper trees increased the train-validation gap without improving validation F1" — a sentence this guide has now equipped you to say truthfully.

Pre-submission checklist

  • Learning curves plotted and included in the report: training vs validation loss per epoch.
  • Diagnosis stated explicitly (overfitting / underfitting / healthy) with the curve as evidence.
  • No duplicates or leakage between train, validation, and test — verified, not assumed.
  • Validation/test data never augmented; test set used exactly once for final numbers.
  • Each fix applied one at a time with the validation score logged.
  • Early stopping with highest-score weight restoration in every neural-network run.
  • Final metrics reported per class (precision/recall/F1), not just accuracy — see the evaluation metrics guide.
  • The debugging discipline from how to test and debug your final-year project applied to the whole pipeline, not just the model.

Overfitting feels like a model problem, but it is almost always a data-and-discipline problem: a clean split, inspected labels, augmentation, early stopping, and one-change-at-a-time experiments. Build those habits and the curves take care of themselves.