defcheck_gradient(f,inputs,eps=1e-5,atol=1e-4,rtol=1e-3):"""Compare analytic gradients from backward() against numerical ones. Raises AssertionError if any gradient is off beyond the tolerance. """forinpininputs:inp.zero_grad()loss=f()loss.backward()analytic=[inp.grad.copy()forinpininputs]numerical=numerical_gradient(f,inputs,eps)fori,(a,n)inenumerate(zip(analytic,numerical)):ifnotnp.allclose(a,n,atol=atol,rtol=rtol):max_diff=np.max(np.abs(a-n))raiseAssertionError(f"Gradient check failed for input {i}: max diff = {max_diff}\n"f"Analytic:\n{a}\nNumerical:\n{n}")returnTrue
defnumerical_gradient(f,inputs,eps=1e-5):"""Estimate gradients of `f` w.r.t. each input by central differences."""grads=[]# temporarily convert all inputs to float64 for precisionorig_data=[inp.data.copy()forinpininputs]forinpininputs:inp.data=inp.data.astype(np.float64)# also stash float64 copies for perturbationf64_data=[inp.data.copy()forinpininputs]fork,inpinenumerate(inputs):grad=np.zeros(inp.data.shape,dtype=np.float64)it=np.nditer(f64_data[k],flags=['multi_index'])whilenotit.finished:idx=it.multi_indexold_val=f64_data[k][idx]inp.data=f64_data[k].copy()inp.data[idx]=old_val+epsloss_plus=float(f().data)inp.data=f64_data[k].copy()inp.data[idx]=old_val-epsloss_minus=float(f().data)grad[idx]=(loss_plus-loss_minus)/(2*eps)it.iternext()inp.data=f64_data[k].copy()grads.append(grad.astype(np.float32))# restore original float32 dataforinp,odinzip(inputs,orig_data):inp.data=odreturngrads