Your team derives gradients by hand, and every derivation goes through a check before any model is trained with it. You are given a loss f(p) and a function grad(p) that claims to return its gradient (a list with one partial derivative per parameter), both taking a list of floats.
Write find_wrong_slopes(f, grad, points, h) that checks grad at every point in points and returns the list of pairs (i, j) for which the claimed partial derivative grad(points[i])[j] disagrees with the estimate
num = (f(p with p[j] + h) - f(p with p[j] - h)) / (2 * h) where p = points[i]
A claimed value a disagrees when |a - num| > 1e-4 · max(1, |a|, |num|). List the pairs in order of i, and for the same i in order of j. Return an empty list if every slope passes.
Examples
Input: f = lambda p: p[0] ** 2 * p[1]
grad = lambda p: [2 * p[0] * p[1], 2 * p[0]]
points = [[1.0, 1.0], [2.0, 3.0]], h = 1e-5
Output: [(0, 1)]
Explanation: the slope in p[1] should be p[0]². At [1, 1] the claim is 2 but the estimate is 1.
At [2, 3] the wrong formula happens to give the right value, 4, so that point hides the bug.
Input: f = lambda p: (p[0] - 3) ** 2 + (p[1] + 1) ** 2
grad = lambda p: [p[0] - 3, p[1] + 1]
points = [[3.0, -1.0], [3.0, 0.0], [5.0, -1.0]], h = 1e-5
Output: [(1, 1), (2, 0)]
Explanation: the factor 2 is missing. At the minimum both slopes are 0, so the mistake cannot show there.
Constraints
0 <= len(points) <= 20, every point has the same length, between 1 and 201e-6 <= h <= 1e-2;fandgradaccept lists and must not be trusted to leave them unchanged, so pass copies- the tests avoid cases where a claimed value is close to the tolerance
Goals
- Compare a hand-derived gradient with a numerical estimate at several points
- Use a tolerance that is relative for large slopes and absolute for small ones
- See that a single test point can hide a wrong derivative