Create more descriptive error types
Instead of using `ValueError` and `IndexError` everywhere it is probably more informative to introduce more descriptive error types.

For example:
  - IncompatibleShapeError (for tensors that can't be multiplied together)
  - UnreachableError (for when/if elif construct that should cover all cases)
  - NotImplementedError (for missing features or for proc/method that must be overloaded, e.g. `forward` and `backward`)