Skip to content

Underfitting vs Overfitting

What you’ll learn

  • the two-number diagnosis: training error and validation error, and what their gap means
  • the complexity curve, and why the training half of it is nearly useless on its own
  • the learning curve, and the one question only it can answer β€” will more data help?
  • the fixes, which are opposites, and why applying the wrong one makes things worse
  • how to read the measured numbers on a real curve rather than eyeballing a shape

Intuition

A model can fail in exactly two ways, and they look nothing alike.

Underfitting is a model too rigid to represent the pattern. A straight line through a curve misses low, then high, then low β€” and it will keep missing however much data you give it. The training error is high, and the validation error is barely worse.

Overfitting is a model flexible enough to memorise the training rows, including their noise. Training error goes to zero and validation error goes up, because the noise it learned is different noise from the noise in the new rows.

The diagnosis needs two numbers, never one:

Training errorValidation errorDiagnosis
HighHigh, similarUnderfitting β€” too rigid
LowMuch higherOverfitting β€” memorising
LowLow, similarGood fit
HighLower than trainingSomething is wrong: a leak, a bad split, or heavy regularisation only active at training time

That last row is the one people misread as good news.

diagram Diagram mermaid

Seeing both at once

figureFifty samples, fifty fits, three capacitiesmatplotlib
Three panels, each showing fifty faint fitted curves from fifty different training samples, plus their average and the true function. Degree 1 curves cluster tightly but miss the truth; degree 15 curves scatter wildly.Three panels, each showing fifty faint fitted curves from fifty different training samples, plus their average and the true function. Degree 1 curves cluster tightly but miss the truth; degree 15 curves scatter wildly.
Left: every fit is nearly the same line, and every one is wrong β€” that is underfitting. Right: the average is close to the truth but no individual fit is β€” that is overfitting.

Reading the plot

  • Degree 1 produces fifty almost identical lines, all of them missing the curve. Consistent and consistently wrong. More data changes nothing.
  • Degree 4 tracks the truth and the spread is modest. This is what a good fit looks like when you can see the whole distribution of fits rather than one.
  • Degree 15 has an average close to the truth, but individual fits swing far from it. Any single model you happen to train is a draw from that scatter.

The right panel is the crucial insight: an overfitted model is not systematically wrong, it is unreliable. Its average is fine; its individual predictions are not.

The complexity curve

figureBoth errors, against capacitymatplotlib
Training MSE and 5-fold cross-validated MSE plotted against polynomial degree from 1 to 15 on a log scale. Training error falls steadily; CV error drops to a minimum at degree 4 then rises sharply.Training MSE and 5-fold cross-validated MSE plotted against polynomial degree from 1 to 15 on a log scale. Training error falls steadily; CV error drops to a minimum at degree 4 then rises sharply.
Training error falls monotonically and tells you nothing on its own. Only the CV curve turns back up, and where it turns is the answer.

Measured on 120 points from the same generator:

DegreeTraining MSE5-fold CV MSEGapReading
10.6180.6340.016Underfitting badly
20.3470.3900.043Still underfitting
30.3190.3540.036Improving
40.1200.1390.019Best
60.1150.1440.029Marginally worse
100.1120.3370.225Overfitting
150.1009.6699.569Overfitting catastrophically

Three things worth noticing:

  1. Training MSE falls from 0.618 to 0.100 across the whole range and never once warns you. A model selected on training error alone would choose degree 15.
  2. Degrees 4 and 6 are indistinguishable β€” 0.139 against 0.144. Prefer the simpler one.
  3. Degree 15 is not slightly worse, it is 70 times worse. Overfitting does not degrade gracefully once the model has enough capacity to interpolate.

The learning curve

The complexity curve varies capacity at fixed data. The learning curve does the reverse, and it answers a different question: would collecting more data help?

figureFixed capacity, growing training setmatplotlib
Two panels of learning curves against training set size. Left, degree 1: both curves flatten at around 0.60 MSE almost immediately. Right, degree 15: both curves fall to around 0.29 and the validation curve is still declining.Two panels of learning curves against training set size. Left, degree 1: both curves flatten at around 0.60 MSE almost immediately. Right, degree 15: both curves fall to around 0.29 and the validation curve is still declining.
Degree 1's validation MSE improves 3% between 20 and 200 samples β€” it plateaued long ago. Degree 15's improves 20% and is still falling. That difference is the whole answer.

The two panels answer the same question differently:

ShapeDiagnosisWill more data help?
Both curves flat at a high errorUnderfittingNo. Add capacity instead.
Validation still falling as data growsVariance still being reducedYes. Keep collecting.
Gap persists and validation is flatOverfitting, saturatedNo. Simplify or regularise.
Both converge at a low errorGood fitNothing to do.

Measured on the plot: degree 1 goes from 0.614 to 0.596 β€” a 3% gain from ten times the data, which is another way of saying no gain. Degree 15 goes from 0.368 to 0.294, a 20% gain and still descending. Notice also that degree 15 has stopped overfitting by 200 samples: the gap that would have been enormous at 20 samples has closed. That is data doing the work regularisation would otherwise have to do.

This is a genuinely useful business answer. β€œShould we spend three months labelling more data?” has a defensible answer, and it is on this plot.

Worked example by hand

Four models of the same problem, each measured on the same held-out set:

ModelTrain RMSEValidation RMSEGapGap as % of train
A: linear69,05169,2181670.2%
B: depth-4 tree58,20061,4003,2005.5%
C: unpruned tree070,67670,676∞
D: random forest18,47650,01031,534171%

Step 1 β€” is the training error acceptable? Against an expert baseline of about 35,530, models A and B are already failing on the training set. That settles it: they are underfitting, and no amount of regularisation will help.

Step 2 β€” for the rest, how big is the gap? Model C’s training error is exactly zero, which is not an achievement but a description of what an unpruned tree does. Model D has a large gap and still the best validation score.

Step 3 β€” choose the fix.

  • A and B: add capacity. More features, a deeper model, less regularisation.
  • C: reduce capacity. max_depthmax_depth, min_samples_leafmin_samples_leaf, or pruning.
  • D: more data, or mild regularisation. The gap says there is headroom; the validation score says it is still the best option available.

The trap: models A and D both need attention, and the correct actions are opposites. Applying regularisation to A β€” the intuitive response to β€œthe model is not good enough” β€” makes it worse.

Fixing each

Underfitting

FixMechanism
More features, or better onesGives the model something to work with
Higher-capacity modelDegree, depth, layers, kernel
Reduce regularisationRaise CC, lower alphaalpha, raise max_depthmax_depth
Train longerMore epochs, more estimators
Remove aggressive feature selectionYou may have discarded the signal

More data does not fix underfitting. A straight line fitted to a million curved points is the same straight line.

Overfitting

FixMechanism
More training dataThe most reliable fix, and usually the most expensive
Simpler modelLower degree, shallower tree, fewer parameters
Stronger regularisationLower CC, raise alphaalpha, raise min_samples_leafmin_samples_leaf
Early stoppingHalt before the model starts memorising
Feature selectionFewer columns, less to memorise
EnsemblingAveraging cancels variance β€” the whole of Phase 5
Data augmentationSynthetic variety where collecting real data is impossible
diagnose.py
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
 
rng = np.random.default_rng(4)
x = rng.uniform(0, 6, 120)
y = np.sin(1.5 * x) + 0.35 * x + rng.normal(0, 0.35, 120)
 
folds = list(KFold(5, shuffle=True, random_state=0).split(x))
 
for degree in (1, 2, 4, 10, 15):
    train = ((y - np.polyval(np.polyfit(x, y, degree), x)) ** 2).mean()
    cv = np.mean([
        ((y[va] - np.polyval(np.polyfit(x[tr], y[tr], degree), x[va])) ** 2).mean()
        for tr, va in folds
    ])
    verdict = ("underfitting" if cv > 0.3 and cv / train < 1.5
               else "overfitting" if cv / train > 2
               else "good fit")
    print(f"degree {degree:2d}  train {train:.4f}  cv {cv:8.4f}  {verdict}")
 
# degree  1  train 0.6182  cv   0.6338  underfitting
# degree  2  train 0.3472  cv   0.3896  underfitting
# degree  4  train 0.1201  cv   0.1387  good fit
# degree 10  train 0.1117  cv   0.3368  overfitting
# degree 15  train 0.1004  cv   9.6689  overfitting
diagnose.py
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
 
rng = np.random.default_rng(4)
x = rng.uniform(0, 6, 120)
y = np.sin(1.5 * x) + 0.35 * x + rng.normal(0, 0.35, 120)
 
folds = list(KFold(5, shuffle=True, random_state=0).split(x))
 
for degree in (1, 2, 4, 10, 15):
    train = ((y - np.polyval(np.polyfit(x, y, degree), x)) ** 2).mean()
    cv = np.mean([
        ((y[va] - np.polyval(np.polyfit(x[tr], y[tr], degree), x[va])) ** 2).mean()
        for tr, va in folds
    ])
    verdict = ("underfitting" if cv > 0.3 and cv / train < 1.5
               else "overfitting" if cv / train > 2
               else "good fit")
    print(f"degree {degree:2d}  train {train:.4f}  cv {cv:8.4f}  {verdict}")
 
# degree  1  train 0.6182  cv   0.6338  underfitting
# degree  2  train 0.3472  cv   0.3896  underfitting
# degree  4  train 0.1201  cv   0.1387  good fit
# degree 10  train 0.1117  cv   0.3368  overfitting
# degree 15  train 0.1004  cv   9.6689  overfitting

See it move

sketch Watching capacity overshoot p5.js
A model's capacity increases step by step. The training error keeps falling while the validation error bottoms out and climbs β€” the gap between the two bars is the overfitting.

Pitfalls

quizCheck yourself
  1. Training RMSE 69,051 and validation RMSE 69,218. What is the diagnosis?

    Show answer

    B β€” Underfitting β€” the tiny gap means the model is not flexible enough to fit even the training data β€” A gap of 167 on 69,000 is essentially zero. The model is not memorising anything because it cannot; both errors are high for the same reason.

  2. Your learning curves converge at a high error and flatten. Should you collect more data?

    Show answer

    B β€” No β€” converged-and-high means underfitting, and more of the same data cannot help a model that cannot represent the pattern β€” Converged curves mean the model has extracted everything it can. Add capacity or better features instead. A wide, still-narrowing gap is the shape that justifies more data.

  3. Training MSE falls from 0.618 at degree 1 to 0.100 at degree 15, while CV MSE goes from 0.634 to 9.669. What does the training curve tell you?

    Show answer

    B β€” Essentially nothing β€” training error falls monotonically with capacity by construction, so only the CV curve carries information β€” More capacity can always fit the training set at least as well. A model chosen on training error alone will always pick the most complex option available.

  4. Your validation error is lower than your training error. What should you do?

    Show answer

    B β€” Investigate β€” this usually indicates a leak, training-only regularisation such as dropout, or an unusually easy validation split β€” There are benign explanations, and there are leaks. Both are worth identifying before the result is reported.

πŸ§ͺ Try It Yourself

Exercise 1 – Diagnose from two numbers

Exercise 2 – Build the complexity curve

Exercise 3 – Fix an underfitting model

Exercise 4 – Read a learning curve

Exercise 5 – Regularisation on the wrong patient

Recap

  • Two numbers, never one. High-and-close is underfitting; low-with-a-gap is overfitting.
  • Training error falls monotonically with capacity, so it carries no information on its own. On the measured curve it went 0.618 β†’ 0.100 while CV error went 0.634 β†’ 9.669.
  • The complexity curve locates the best capacity; the learning curve says whether more data would help.
  • An overfitted model is not systematically wrong β€” its average is fine and its individual predictions are unreliable.
  • The fixes are opposites. Diagnose before acting: regularising an underfitting model makes it worse.
  • Validation error below training error is a symptom to investigate, not a success.

Exercise 6 – Turn two numbers into a next action

Next

Continue to Bias vs Variance Tradeoff β€” the same two failures, derived as an exact decomposition of expected error into three terms.

If this helped you, consider buying me a coffee β˜•

Buy me a coffee

Was this page helpful?

Let us know how we did