Try first
You have gradient and you have the update rule. Write the training loop yourself before reading on. It should start from some (m, b), repeat the update, and return the result.
The whole algorithm
function train(data, lr = 0.01, steps = 5000) {
let m = 0, b = 0;
for (let i = 0; i < steps; i++) {
const [dm, db] = gradient(m, b, data);
m -= lr * dm;
b -= lr * db;
}
return [m, b];
}
const [m, b] = train(study);
log("m", m.toFixed(3), " b", b.toFixed(3), " loss", loss(m, b, study).toFixed(3));
Six lines inside the function. That is the whole of it, and it is the thing that spent this entire section being built up to.
Start at zero. Ask which way is downhill. Take a step. Repeat. Everything else in machine learning is a better gradient, a better step, or a better model to apply it to.
Watch it work
function trainVerbose(data, lr = 0.01, steps = 5000, every = 500) {
let m = 0, b = 0;
for (let i = 0; i <= steps; i++) {
if (i % every === 0) {
log(String(i).padStart(5), " m", m.toFixed(3),
" b", b.toFixed(3), " loss", loss(m, b, data).toFixed(4));
}
const [dm, db] = gradient(m, b, data);
m -= lr * dm;
b -= lr * db;
}
return [m, b];
}
trainVerbose(study);
Read the log carefully, because the shape of it is the point.
The loss collapses in the first few hundred steps, then improves slowly for thousands more. That curve, steep then flat, is what training looks like for nearly every model you will ever run. Most of the gain arrives early. Most of the time is spent on the last little bit.
Watch the two parameters separately. m settles quickly. b drifts upward for a very long time. That is the diagonal valley from lesson 3050 showing up in the numbers: once the walker reaches the valley floor, the floor itself slopes gently, and progress along it is slow.
Compare against your hand tuning
const flat = study.reduce((s, r) => s + r.y, 0) / study.length;
log("always average", loss(0, flat, study).toFixed(3));
log("your best ", loss(8, 24, study).toFixed(3));
log("trained ", loss(m, b, study).toFixed(3));
The trained line should beat both. It took no judgment, no sliders, and about a hundredth of a second.
Knowing when to stop
Running a fixed 5000 steps is crude. It might be far too many or not enough. The usual fix is to stop when progress stalls.
let last = Infinity;
for (let i = 0; i < steps; i++) {
const [dm, db] = gradient(m, b, data);
m -= lr * dm;
b -= lr * db;
const now = loss(m, b, data);
if (Math.abs(last - now) < 1e-9) { log("stopped at step", i); break; }
last = now;
}
Stop when the loss stops moving. Be careful with the threshold. Too loose and you quit while still improving. Too tight and you never trigger it, because floating point noise keeps the difference above zero forever.
What to watch
Starting at m = 0, b = 0 is a choice, and here it does not matter, because the bowl has one bottom and every path leads to it. For a neural network the starting point changes the answer you get, and lesson 3100 is about picking it properly.
Exercises
- Run with 100 steps, then 1000, then 50000. Plot the final loss against the step count.
- Start from
m = 30, b = -50. Does it still reach the same answer? How many more steps did it need? - Add the stopping rule and find the smallest threshold that still triggers.
- Time 5000 steps with
performance.now(). Then work out how long the grid search from lesson 3050 took for a worse answer.