The problem
In this section you will write backward passes by hand: code that computes the gradient of a function. A backward pass can be wrong and still look plausible, so before writing any, build the tool that tells you whether one is right. You will use it, or the version in the tests, for every gradient in the rest of the roadmap.
The tool has two parts: a slow but reliable way to estimate a gradient, and a way to compare two gradients.
Part 1: estimating a gradient by nudging
The derivative of with respect to one entry says how much changes when changes a tiny bit. So measure it directly: nudge up by a small eps, compute ; nudge it down by eps, compute again; divide the change by the distance between the two points. This is the central difference:
where:
- is
eps, the size of the nudge; - is an array of zeros with a 1 at entry , so means "add
epsto entry only, leave the others alone"; - is the distance between the two nudged points.
Do this for every entry, one at a time, and you get an array of estimates shaped like x.
Example. at
x = [1.0, 2.0], witheps = 0.01. The true gradient is .
- Entry 0: with , ; with , . Estimate: .
- Entry 1: with , ; with , . Estimate: .
The result is
[2.0, 3.0], andxis back to[1.0, 2.0]at the end.
Why both directions? The one-sided version, , is much less accurate. For at (true derivative ) with , it gives , while the central difference gives .
Part 2: comparing two gradients
Your gradient and the estimate will never be exactly equal, so we need to measure how far apart they are. A plain difference is not enough: is a big error if the gradient is , and nothing if it is . The relative error divides by the size of the numbers:
where:
- is the difference at entry , and the top takes the largest one;
- is the size of the two numbers at entry , and the bottom takes the largest one;
- if both arrays are all zeros, the bottom is 0, and the answer is defined as 0: no error.
Example.
- , : the largest difference is , the largest size , so the relative error is about . Close.
- , : the largest difference is , the largest size , so the relative error is . A sign error: a bug.
As a rule of thumb, around or below is a pass and means a bug.
What to implement
numerical_grad(f, x, eps=1e-5):ftakes no arguments and returns a number computed from the arrayx, which can have any shape. Return an array shaped likexwith the central-difference estimate for every entry.rel_error(a, b): the relative error between two arrays of the same shape, as a number.
Requirements
freadsxitself, so changexin place, one entry at a time, and put every entry back to its original value. After the call,xmust be exactly unchanged.- Use the central difference, not the one-sided one: the tests check accuracy closely enough to tell them apart.
Hints
np.ndindex(x.shape)yields every index of an array of any shape:(0,), (1,), ...for a list,(0, 0), (0, 1), ...for a table. A loop is fine here: gradient checks run on small inputs only.- Save the original value before nudging and assign it back at the end. Adding
epsand subtracting it again does not always give back exactly the same number in floating point.