Skip to content

Cost Functions - Mean Squared Error (MSE)

What you’ll learn

  • the difference between a cost function, a loss function and a metric
  • the definition of MSE, and why we train on it but report RMSE
  • how squaring changes what the model considers important
  • MAE and Huber — what they optimise instead, and when each wins
  • why the MSE surface is a convex bowl, and what that guarantees
  • the statistical reason MSE is the default: it is maximum likelihood under Gaussian noise

Intuition

A model needs a single number that says how wrong it currently is — one number, because optimisation can only push one thing downhill. That number is the cost.

Choosing the cost is not a technicality. It is a statement of values. Squaring the errors declares that one miss of 10 is worse than ten misses of 1 (100 against 10), so the model will contort itself to avoid big misses even at the cost of many small ones. Taking absolute values declares they are equally bad. Both are defensible; they simply produce different models from identical data.

diagram Diagram mermaid

Three words that get confused

TermScopeExample
LossOne instance(y^iyi)2(\hat{y}_i - y_i)^2
CostThe whole training set — what the optimiser minimises1m(y^iyi)2\frac{1}{m}\sum(\hat{y}_i - y_i)^2
MetricWhat you report to humans; need not be differentiableRMSE, MAE, R2R^2, “percentage within ±5%”

The cost and the metric are allowed to differ, and often should. You train on MSE because it has clean gradients; you report RMSE because it is in the units of the target.

The math

Definition

MSE(θ)=1mi=1m(y^(i)y(i))2=1mXθy22\text{MSE}(\boldsymbol{\theta}) = \frac{1}{m}\sum_{i=1}^{m}\left(\hat{y}^{(i)} - y^{(i)}\right)^2 = \frac{1}{m}\lVert \mathbf{X}\boldsymbol{\theta} - \mathbf{y}\rVert_2^2

Units are the square of the target’s units — squared dollars, squared degrees. Nobody has intuition for squared dollars, which is why we take the root:

RMSE=MSE\text{RMSE} = \sqrt{\text{MSE}}

Why train on MSE and report RMSE

The square root is monotonic, so whichever θ\boldsymbol{\theta} minimises MSE also minimises RMSE. The minimiser is identical. But the gradient is not:

θjMSE=2mi(y^(i)y(i))xj(i)\frac{\partial}{\partial \theta_j}\text{MSE} = \frac{2}{m}\sum_i (\hat{y}^{(i)} - y^{(i)})x_j^{(i)}
θjRMSE=12MSEMSEθj\frac{\partial}{\partial \theta_j}\text{RMSE} = \frac{1}{2\sqrt{\text{MSE}}}\cdot\frac{\partial \text{MSE}}{\partial \theta_j}

The RMSE gradient carries a 1/MSE1/\sqrt{\text{MSE}} factor that blows up as the cost approaches zero. MSE’s gradient is clean and linear in the residual. Same answer, better-behaved arithmetic — so train on MSE, report RMSE.

Why the surface is a bowl

MSE(θ)\text{MSE}(\boldsymbol{\theta}) is a quadratic form in θ\boldsymbol{\theta} with Hessian

H=2mXX\mathbf{H} = \frac{2}{m}\mathbf{X}^\top\mathbf{X}

XX\mathbf{X}^\top\mathbf{X} is positive semi-definite for any X\mathbf{X} — for any vector v\mathbf{v}, vXXv=Xv20\mathbf{v}^\top\mathbf{X}^\top\mathbf{X}\mathbf{v} = \lVert\mathbf{X}\mathbf{v}\rVert^2 \geq 0. So the cost is convex: no local minima, no saddle points, no bad starting position. Whatever optimiser you point at it will find the global minimum, and if the columns are linearly independent that minimum is unique.

figureMSE over the two parameters of a simple linear modelmatplotlib
Contour plot of MSE over slope and intercept for the five-point dataset, showing nested elliptical contours around a single starred minimum at slope 0.6, intercept 2.2.Contour plot of MSE over slope and intercept for the five-point dataset, showing nested elliptical contours around a single starred minimum at slope 0.6, intercept 2.2.
Nested ellipses closing on one point. Every direction leads downhill to the same place — this is what convexity looks like, and it is why linear regression never needs a restart.

The statistical justification

MSE is not an arbitrary choice. Assume the target is a linear function plus Gaussian noise:

y(i)=θx(i)+ε(i),ε(i)N(0,σ2)y^{(i)} = \boldsymbol{\theta}^\top\mathbf{x}^{(i)} + \varepsilon^{(i)}, \qquad \varepsilon^{(i)} \sim \mathcal{N}(0, \sigma^2)

The log-likelihood of the data is then

logL(θ)=m2log(2πσ2)12σ2i=1m(y(i)θx(i))2\log L(\boldsymbol{\theta}) = -\frac{m}{2}\log(2\pi\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^{m}\left(y^{(i)} - \boldsymbol{\theta}^\top\mathbf{x}^{(i)}\right)^2

Only the last term depends on θ\boldsymbol{\theta}, and it is 12σ2-\frac{1}{2\sigma^2} times the sum of squared errors. Maximising the likelihood is exactly minimising MSE. Choosing squared error is the same act as assuming your noise is Gaussian — which also explains why MSE handles outliers badly: Gaussians have thin tails and treat a large deviation as nearly impossible.

Worked example by hand

The Phase 3 five-point set with the fitted line y^=0.6x+2.2\hat{y} = 0.6x + 2.2:

xix_iyiy_iy^i\hat{y}_ieie_iei2e_i^2ei\lvert e_i \rvertHuber (δ=1\delta=1)
122.8−0.80.640.80.32
243.4+0.60.360.60.18
354.0+1.01.001.00.50
444.6−0.60.360.60.18
555.2−0.20.040.20.02
2.403.201.20
MSE=2.405=0.48,RMSE=0.480.693,MAE=3.205=0.64\text{MSE} = \frac{2.40}{5} = 0.48, \qquad \text{RMSE} = \sqrt{0.48} \approx 0.693, \qquad \text{MAE} = \frac{3.20}{5} = 0.64

Note that RMSE (0.693) exceeds MAE (0.64). That is not a coincidence: RMSE is always at least as large as MAE, and the gap widens as the errors become less uniform. A big RMSE-to-MAE ratio is a quick signal that a few large errors dominate.

Now break one point

Change the last observation from 5 to 25 and refit. The least-squares line becomes y^=4.6x5.8\hat{y} = 4.6x - 5.8:

xix_iyiy_iy^i\hat{y}_ieie_i
12−1.2+3.2
243.4+0.6
358.0−3.0
4412.6−8.6
52517.2+7.8

MSE has jumped from 0.48 to 30.88, and — far worse — every single point is now badly predicted. The line abandoned four good observations to chase one bad one. The MAE-optimal line for the same data is y^1.51x+0.49\hat{y} \approx 1.51x + 0.49, which stays close to the original four points and simply accepts a large error on the outlier.

Comparing the three losses

MSE=1mei2MAE=1meiHuberδ={12ei2eiδδ(ei12δ)otherwise\text{MSE} = \frac{1}{m}\sum e_i^2 \qquad \text{MAE} = \frac{1}{m}\sum \lvert e_i \rvert \qquad \text{Huber}_\delta = \begin{cases} \tfrac{1}{2}e_i^2 & \lvert e_i\rvert \le \delta \\[4pt] \delta\left(\lvert e_i\rvert - \tfrac{1}{2}\delta\right) & \text{otherwise} \end{cases}

Huber is quadratic near zero (so it has a smooth gradient where it matters) and linear in the tails (so one wild point cannot dominate). It is the pragmatic default when you suspect outliers but still want gradient-friendly behaviour.

figurePenalty as a function of a single residualmatplotlib
Three loss curves plotted against residual size: a steep parabola for squared error, a V shape for absolute error, and a Huber curve that is parabolic near zero and straight further out.Three loss curves plotted against residual size: a steep parabola for squared error, a V shape for absolute error, and a Huber curve that is parabolic near zero and straight further out.
At a residual of 3 the squared loss charges 9 while absolute error charges 3. Huber tracks the parabola until the residual passes delta, then goes straight.
figureDragging one point upward, and watching each loss reactmatplotlib
Line chart showing the fitted slope as one data point is dragged upward. The MSE slope climbs steadily away from the true value of 2; the MAE slope stays flat.Line chart showing the fitted slope as one data point is dragged upward. The MSE slope climbs steadily away from the true value of 2; the MAE slope stays flat.
The MSE fit follows the outlier almost linearly. The MAE fit does not move at all — the median is immune to how far away the far point is.

Reading the plots

  • The MAE line is flat. Not merely flatter — flat. Absolute-error fitting depends on the rank of the residuals, not their size, so once a point is above the line it does not matter whether it is above by 5 or by 500.
  • The MSE line rises without limit. Each unit of extra distance adds a proportional pull on the slope. There is no point at which MSE stops caring.
  • Huber sits between them, by construction. Below δ\delta it behaves like MSE, above it like MAE, and δ\delta is the dial.
MSEMAEHuber
Optimises toward themeanmedianmean, robustified
Outlier sensitivityHighLowTunable via δ\delta
Differentiable everywhereYesNo (corner at 0)Yes
Closed-form solutionYesNoNo
scikit-learnLinearRegressionLinearRegression, RidgeRidgeQuantileRegressorQuantileRegressorHuberRegressorHuberRegressor
Reach for it whenNoise is roughly GaussianOutliers are real dataYou are unsure

See it move

Each pink square’s area is one point’s squared error. Watch the total area collapse as the line improves — that shrinking area is literally the cost.

sketch MSE as shrinking squares p5.js
Each square's area equals one point's squared error; as the fitted line improves, the squares shrink and MSE drops. Click for a fresh dataset.

From scratch

All three losses, plus the invariant that RMSE never falls below MAE:

losses.py
import numpy as np
 
 
def mse(y, pred):
    return float(((pred - y) ** 2).mean())
 
 
def rmse(y, pred):
    return mse(y, pred) ** 0.5
 
 
def mae(y, pred):
    return float(np.abs(pred - y).mean())
 
 
def huber(y, pred, delta=1.0):
    e = np.abs(pred - y)
    quadratic = 0.5 * e**2
    linear = delta * (e - 0.5 * delta)
    return float(np.where(e <= delta, quadratic, linear).mean())
 
 
x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y = np.array([2.0, 4.0, 5.0, 4.0, 5.0])
pred = 0.6 * x + 2.2
 
print(f"MSE   = {mse(y, pred):.4f}")     # MSE   = 0.4800
print(f"RMSE  = {rmse(y, pred):.4f}")    # RMSE  = 0.6928
print(f"MAE   = {mae(y, pred):.4f}")     # MAE   = 0.6400
print(f"Huber = {huber(y, pred):.4f}")   # Huber = 0.2400
 
print(rmse(y, pred) >= mae(y, pred))     # True — always
losses.py
import numpy as np
 
 
def mse(y, pred):
    return float(((pred - y) ** 2).mean())
 
 
def rmse(y, pred):
    return mse(y, pred) ** 0.5
 
 
def mae(y, pred):
    return float(np.abs(pred - y).mean())
 
 
def huber(y, pred, delta=1.0):
    e = np.abs(pred - y)
    quadratic = 0.5 * e**2
    linear = delta * (e - 0.5 * delta)
    return float(np.where(e <= delta, quadratic, linear).mean())
 
 
x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y = np.array([2.0, 4.0, 5.0, 4.0, 5.0])
pred = 0.6 * x + 2.2
 
print(f"MSE   = {mse(y, pred):.4f}")     # MSE   = 0.4800
print(f"RMSE  = {rmse(y, pred):.4f}")    # RMSE  = 0.6928
print(f"MAE   = {mae(y, pred):.4f}")     # MAE   = 0.6400
print(f"Huber = {huber(y, pred):.4f}")   # Huber = 0.2400
 
print(rmse(y, pred) >= mae(y, pred))     # True — always

With scikit-learn

robust_vs_ols.py
import numpy as np
from sklearn.linear_model import LinearRegression, HuberRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
 
X = np.array([1.0, 2.0, 3.0, 4.0, 5.0]).reshape(-1, 1)
clean = np.array([2.0, 4.0, 5.0, 4.0, 5.0])
dirty = np.array([2.0, 4.0, 5.0, 4.0, 25.0])   # last point corrupted
 
ols_clean = LinearRegression().fit(X, clean)
ols_dirty = LinearRegression().fit(X, dirty)
huber_dirty = HuberRegressor(epsilon=1.35).fit(X, dirty)
 
print(f"OLS on clean data : slope {ols_clean.coef_[0]:.2f}")    # slope 0.60
print(f"OLS on dirty data : slope {ols_dirty.coef_[0]:.2f}")    # slope 4.60
print(f"Huber on dirty    : slope {huber_dirty.coef_[0]:.2f}")  # slope 1.91
 
# Same predictions, two different metrics
pred = ols_dirty.predict(X)
print(f"MSE = {mean_squared_error(dirty, pred):.2f}")           # MSE = 30.88
print(f"MAE = {mean_absolute_error(dirty, pred):.2f}")          # MAE = 4.64
robust_vs_ols.py
import numpy as np
from sklearn.linear_model import LinearRegression, HuberRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error
 
X = np.array([1.0, 2.0, 3.0, 4.0, 5.0]).reshape(-1, 1)
clean = np.array([2.0, 4.0, 5.0, 4.0, 5.0])
dirty = np.array([2.0, 4.0, 5.0, 4.0, 25.0])   # last point corrupted
 
ols_clean = LinearRegression().fit(X, clean)
ols_dirty = LinearRegression().fit(X, dirty)
huber_dirty = HuberRegressor(epsilon=1.35).fit(X, dirty)
 
print(f"OLS on clean data : slope {ols_clean.coef_[0]:.2f}")    # slope 0.60
print(f"OLS on dirty data : slope {ols_dirty.coef_[0]:.2f}")    # slope 4.60
print(f"Huber on dirty    : slope {huber_dirty.coef_[0]:.2f}")  # slope 1.91
 
# Same predictions, two different metrics
pred = ols_dirty.predict(X)
print(f"MSE = {mean_squared_error(dirty, pred):.2f}")           # MSE = 30.88
print(f"MAE = {mean_absolute_error(dirty, pred):.2f}")          # MAE = 4.64

One corrupted observation multiplies the OLS slope by more than seven, from 0.60 to 4.60. Huber still drifts — 1.91 — because five points is a brutal test and one of them is now an extreme value, but it absorbs less than half the damage OLS does.

Pitfalls

quizCheck yourself
  1. Why is MSE minimised during training while RMSE is reported afterwards?

    Show answer

    B — They share the same minimiser, but MSE has a cleaner gradient while RMSE is in the target's units — The square root is monotonic, so the optimal parameters are identical. MSE's gradient is linear in the residual; RMSE's carries a 1 over root-MSE factor that explodes near zero.

  2. Which constant value minimises the sum of absolute errors?

    Show answer

    B — The median — The median minimises absolute error; the mean minimises squared error. That single fact explains most of the behavioural difference between MAE and MSE.

  3. Why is the MSE cost surface guaranteed to have no local minima?

    Show answer

    B — Because its Hessian, 2/m times X transpose X, is positive semi-definite, making the cost convex — X transpose X is positive semi-definite for any X, so the quadratic cost is convex — a single global minimum with no traps along the way.

  4. Your model reports RMSE 40 and MAE 12. What does that gap suggest?

    Show answer

    B — A small number of very large errors are dominating the squared average — RMSE is always at least MAE, and the gap grows with the spread of the errors. A ratio above three points at a few extreme residuals rather than uniform mediocrity.

🧪 Try It Yourself

Exercise 1 – Compute MSE by hand

Exercise 2 – RMSE is never below MAE

Exercise 3 – Implement the Huber loss

Exercise 4 – Mean minimises MSE, median minimises MAE

Exercise 5 – Huber resists what OLS cannot

Recap

  • Loss is per instance, cost is over the training set, and the metric is what you report.
  • MSE=1mei2\text{MSE} = \frac{1}{m}\sum e_i^2; train on it, report RMSE because it is in target units.
  • The cost surface is convex because XX\mathbf{X}^\top\mathbf{X} is positive semi-definite — one global minimum, no traps.
  • Minimising MSE is maximum likelihood under Gaussian noise, which is also why it fears outliers.
  • MSE targets the mean, MAE the median, Huber the mean with a cap on how much any one point can pull.
  • On the five-point set: MSE 0.48, RMSE 0.693, MAE 0.64, Huber 0.24. Corrupt one point and MSE jumps to 30.88 while the Huber slope barely moves.

Exercise 6 – Which statistic is each loss estimating?

Next

Continue to Gradient Descent Explained — how to actually walk down the bowl when the closed-form solution is too expensive to compute.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did