def test_auto_grad():
    """
    **Overview**:
        Test auto-grad mechanism, compare numpy hand-crafed version and PyTorch auto-grad version.
    """
    # Generate data
    B, D = 3, 4
    # Numpy version.
    x = np.random.randn(B, D)
    y = np.random.randn(B, D)
    z = np.random.randn(B, D)
    # Forward.
    a = x * y
    b = a + z
    c = np.sum(b)
    # Backward.
    grad_c = 1.0
    grad_b = grad_c * np.ones((B, D))
    grad_a = grad_b.copy()
    grad_z = grad_b.copy()
    grad_x = grad_a * y
    grad_y = grad_a * x
    # PyTorch version.
    x = nn.Parameter(torch.from_numpy(x)).requires_grad_(True)
    y = nn.Parameter(torch.from_numpy(y)).requires_grad_(True)
    z = nn.Parameter(torch.from_numpy(z)).requires_grad_(True)
    # Forward.
    a = x * y
    b = a + z
    c = torch.sum(b)
    # Backward.
    c.backward()
    # Check whether the results are correct.
    assert torch.sum(torch.abs(torch.from_numpy(grad_x) - x.grad)) < 1e-6
    assert torch.sum(torch.abs(torch.from_numpy(grad_y) - y.grad)) < 1e-6
    assert torch.sum(torch.abs(torch.from_numpy(grad_z) - z.grad)) < 1e-6

    **Overview**:
        Test auto-grad mechanism, compare numpy hand-crafed version and PyTorch auto-grad version.
    
chapter3_obs/gradient.py