gradcheck.py 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. import numpy as np
  2. from tinygrad.tensor import Tensor
  3. def mask_like(like, mask_inx, mask_value = 1.0):
  4. mask = np.zeros_like(like).reshape(-1)
  5. mask[mask_inx] = mask_value
  6. return mask.reshape(like.shape)
  7. def jacobian(func, input):
  8. output = func(input)
  9. ji = input.numpy().reshape(-1).shape[-1]
  10. jo = output.numpy().reshape(-1).shape[-1]
  11. J = np.zeros((jo,ji), dtype=np.float32)
  12. for o in range(jo):
  13. input.grad = None
  14. output = func(input)
  15. # tinygrad doesn't support slicing, tiny-hack to select
  16. # the needed scalar an backpropagate only through it
  17. o_scalar = Tensor(mask_like(output.numpy(), o, 1.)).mul(output).sum()
  18. o_scalar.backward()
  19. for i, grad in enumerate(input.grad.numpy().reshape(-1)):
  20. J[o,i] = grad
  21. return J
  22. def numerical_jacobian(func, input, eps = 1e-3):
  23. output = func(input)
  24. ji = input.numpy().reshape(-1).shape[-1]
  25. jo = output.numpy().reshape(-1).shape[-1]
  26. NJ = np.zeros((jo, ji), dtype=np.float32)
  27. for i in range(ji):
  28. eps_perturb = mask_like(input.numpy(), i, mask_value = eps)
  29. output_perturb_add = func(Tensor(input.numpy() + eps_perturb)).numpy().reshape(-1)
  30. output_perturb_sub = func(Tensor(input.numpy() - eps_perturb)).numpy().reshape(-1)
  31. grad_approx = ((output_perturb_add) - (output_perturb_sub)) / (2*eps)
  32. NJ[:,i] = grad_approx
  33. return NJ
  34. def gradcheck(func, input, eps = 1e-3, atol = 1e-3, rtol = 1e-3):
  35. NJ = numerical_jacobian(func, input, eps)
  36. J = jacobian(func, input)
  37. return np.allclose(J, NJ, atol = atol, rtol = rtol)