Task 5 of 5
Everything so far worked because the loss was a bowl: one minimum, and every
road leads to it. Most surfaces are not bowls. Here is one that is not — a single
parameter w, and a slope written out by hand, the way every gradient in
this module has been:
f'(w) = w⁵ − 5w³ + 4w
= w · (w² − 1) · (w² − 4)
Five roots: −2, −1, 0, 1, 2. Three are valleys (−2,
0, +2) and two are ridges (±1). Gradient descent
can see none of this. It reads the slope under its feet and steps, so
where it ends up is decided entirely by where it began — and the borders
between the three answers sit exactly at w = ±1.
Which is a question with 1,024 answers and 1,024 threads to answer it: one start
each, 200 steps each, one launch. The step size is fixed at 0.02, well
under the 2/24 ≈ 0.083 the curvature at w = ±2 allows. Watch
what comes back: starts[300] = −1.0352 finishes at −2 and
starts[320] = −0.9375 finishes at 0 — two runs a tenth of a
unit apart, ending two whole units apart.
308 threads should land
near −2, 409 near 0 and 307 near
+2.starts[this.thread.x]w⁵ − 5w³ + 4w — write it out with plain multiplicationsw = w - this.constants.rate * slopeNo Math.pow needed, and no autodiff either — the derivative of
a polynomial is a polynomial:
const slope = w * w * w * w * w - 5 * w * w * w + 4 * w;Identical to the line fit, with one parameter instead of two:
w = w - this.constants.rate * slope;. That is the entire algorithm; the
surface changed, not the method.
Every thread finishes within a rounding error of −2,
0 or +2, so a two-way split on the returned value is
enough:
if (ends[k] < -1) left++;
else if (ends[k] < 1) middle++;
else right++;This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.