Dictionary of Applied Machine Learning · training error

training error — Python demo

Numerical companion to the entry training error: it recomputes what the entry states and prints one line per check

The entry's weather narrative, carried out: a straight line fitted by ERM to 40 days of synthetic weather recordings (morning minimum and maximum daytime temperature), its training error as the average of the 40 squared misses, the minimality of that average over all lines, and the training error against the risk for polynomials of growing degree fitted to the same 40 days. The days are generated exactly as in valerr.py (same seed), so the numbers of the two entries agree. Self-contained (numpy/matplotlib only), deterministic.

Run it with python3 trainerr.py, from any directory — it writes its output files into the current directory. Requires NumPy and Matplotlib only, and uses fixed seeds, so the printed numbers reproduce exactly. Download trainerr.py · Notebook · Open in Colab

The script, block by block

One cell per block of the script: the code, and what that code printed when it last ran here

setup

"""
trainerr.py -- numerical companion to the entry 'training error'.

The entry's weather narrative, carried out: a straight line fitted by ERM
to 40 days of synthetic weather recordings (morning minimum and maximum
daytime temperature), its training error as the average of the 40 squared
misses, the minimality of that average over all lines, and the training
error against the risk for polynomials of growing degree fitted to the same
40 days.  The days are generated exactly as in valerr.py (same seed), so the
numbers of the two entries agree.  Self-contained (numpy/matplotlib only),
deterministic.

Blocks
------
[B-def]     Fit a line to 40 days and compute its training error: the
            average squared error over the same 40 days.  Check that it is
            the average of the per-day losses and that no other line has a
            smaller one (ERM delivers the minimum).
[B-degree]  Polynomials of degree 0 to 12 fitted by ERM to the same 40
            days: the training error never increases with the degree,
            while the risk (average loss on 200000 fresh days) falls and
            then rises.  Writes the CSV behind the entry's figure.

Outputs
-------
pythondemos/trainerr_degree.csv : degree, training error and risk of the
                                  polynomial fitted by ERM.
pythondemos/trainerr.png        : preview figure (checking only).
"""

import numpy as np
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

from pathlib import Path

OUT_DIR = Path(__file__).parent

report = []


def check(name, ok):
    report.append((name, bool(ok)))
    print(f"  [{'ok' if ok else 'FAIL'}] {name}")


def days(n, seed, noise=1.5):
    """n days: morning minimum x and maximum daytime temperature y."""
    gen = np.random.default_rng(seed)
    x = gen.uniform(-15.0, 5.0, n)
    y = 4.0 + 0.8 * x + gen.normal(0.0, noise, n)
    return x, y


def avg_sqerr(x, y, coef):
    return float(np.mean((y - np.polyval(coef, x)) ** 2))

B-def

Fit a line to 40 days and compute its training error: the average squared error over the same 40 days. Check that it is the average of the per-day losses and that no other line has a smaller one (ERM delivers the minimum).

print("[B-def] the training error of a line fitted by ERM")

x, y = days(60, seed=20260828)
tr = np.arange(40)                         # the same 40 days as in valerr.py
line = np.polyfit(x[tr], y[tr], 1)
losses = (y[tr] - np.polyval(line, x[tr])) ** 2
trainerr = avg_sqerr(x[tr], y[tr], line)
print(f"    line: slope {line[0]:.3f}, offset {line[1]:.3f}; training error "
      f"{trainerr:.3f} = average of {len(tr)} per-day squared errors "
      f"(smallest {losses.min():.3f}, largest {losses.max():.3f})")
check("the training error is the average of the per-day losses",
      np.isclose(trainerr, float(np.mean(losses)), atol=1e-12))
gen = np.random.default_rng(1)
others = line + gen.normal(0.0, [0.05, 0.5], size=(2000, 2))
worse = [avg_sqerr(x[tr], y[tr], c) for c in others]
print(f"    2000 other lines (slope and offset perturbed): smallest training "
      f"error {min(worse):.3f}")
check("no other line has a smaller training error: ERM delivers the minimum",
      min(worse) >= trainerr)
[B-def] the training error of a line fitted by ERM
    line: slope 0.903, offset 4.437; training error 2.452 = average of 40 per-day squared errors (smallest 0.000, largest 15.898)
  [ok] the training error is the average of the per-day losses
    2000 other lines (slope and offset perturbed): smallest training error 2.452
  [ok] no other line has a smaller training error: ERM delivers the minimum

B-degree

Polynomials of degree 0 to 12 fitted by ERM to the same 40 days: the training error never increases with the degree, while the risk (average loss on 200000 fresh days) falls and then rises. Writes the CSV behind the entry's figure.

print("\n[B-degree] training error and risk against the degree of the polynomial")

xf, yf = days(200_000, seed=99)
DEGREES = list(range(0, 13))
rows = []
for d in DEGREES:
    coef = np.polyfit(x[tr], y[tr], d)
    rows.append((d, avg_sqerr(x[tr], y[tr], coef), avg_sqerr(xf, yf, coef)))
    print(f"    degree {d:2d}: training error {rows[-1][1]:.3f}, risk {rows[-1][2]:.3f}")
train_curve = [r[1] for r in rows]
risk_curve = [r[2] for r in rows]
check("the training error never increases with the degree",
      all(a >= b - 1e-9 for a, b in zip(train_curve, train_curve[1:])))
check("the training error is below the risk for every degree from 1 on",
      all(r[1] < r[2] for r in rows[1:]))
best = int(np.argmin(risk_curve))
print(f"    smallest risk at degree {best}; at degree 12 the training error is "
      f"{train_curve[-1]:.3f} and the risk {risk_curve[-1]:.3f}")
check("the risk is smallest at degree 1 and larger at degree 12 than at degree 1",
      best == 1 and risk_curve[-1] > risk_curve[1])

with open(OUT_DIR / "trainerr_degree.csv", "w") as fh:
    fh.write("degree,train,risk\n")
    for d, t, r in rows:
        fh.write(f"{d},{t:.4f},{r:.4f}\n")
print("    wrote trainerr_degree.csv")


# --------------------------------------------------------------- preview
fig, ax = plt.subplots(figsize=(6.4, 3.8))
ax.semilogy(DEGREES, train_curve, "o-", color="black", ms=4, label="training error")
ax.semilogy(DEGREES, risk_curve, "s--", color="0.4", ms=4, label="risk (200000 fresh days)")
ax.set_xlabel("degree of the polynomial")
ax.set_ylabel("average squared error")
ax.set_title("[B-degree] training error and risk of the polynomial fitted by ERM",
             fontsize=9)
ax.legend(frameon=False, fontsize=8)
fig.tight_layout()
fig.savefig(OUT_DIR / "trainerr.png", dpi=150)
plt.close(fig)
print("wrote trainerr.png")

passed = sum(1 for _, ok in report if ok)
print(f"\n{passed}/{len(report)} checks pass")
[B-degree] training error and risk against the degree of the polynomial
    degree  0: training error 29.665, risk 23.661
    degree  1: training error 2.452, risk 2.614
    degree  2: training error 2.401, risk 2.733
    degree  3: training error 2.400, risk 2.734
    degree  4: training error 2.391, risk 2.709
    degree  5: training error 2.389, risk 2.685
    degree  6: training error 2.387, risk 2.682
    degree  7: training error 2.228, risk 3.099
    degree  8: training error 2.115, risk 5.659
    degree  9: training error 2.049, risk 3.172
    degree 10: training error 2.030, risk 4.650
    degree 11: training error 1.953, risk 47.404
    degree 12: training error 1.841, risk 24.768
  [ok] the training error never increases with the degree
  [ok] the training error is below the risk for every degree from 1 on
    smallest risk at degree 1; at degree 12 the training error is 1.841 and the risk 24.768
  [ok] the risk is smallest at degree 1 and larger at degree 12 than at degree 1
    wrote trainerr_degree.csv
wrote trainerr.png

5/5 checks pass
Preview figure produced by trainerr.py
The preview figure the block B-degree writes when the script runs