auto_gradient_check.md 6.2 KB
Newer Older
1
## Auto Gradient Checker Design
Q
qiaolongfei 已提交
2 3

## Backgraound:
P
Peng Li 已提交
4 5 6 7
- Generally, it is easy to check whether the forward computation of an Operator is correct or not. However, backpropagation is a notoriously difficult algorithm to debug and get right:
  1. you should get the right backpropagation formula according to the forward computation.
  2. you should implement it right in CPP.
  3. it's difficult to prepare test data.
Q
qiaolongfei 已提交
8

P
Peng Li 已提交
9 10 11
- Auto gradient checking gets a numerical gradient by forward Operator and use it as a reference of the backward Operator's result. It has several advantages:
  1. numerical gradient checker only need forward operator.
  2. user only need to prepare the input data for forward Operator.
Q
qiaolongfei 已提交
12

13
## Mathematical Theory
P
Peng Li 已提交
14
The following two document from Stanford has a detailed explanation of how to get numerical gradient and why it's useful.
Q
qiaolongfei 已提交
15 16 17 18 19 20

- [Gradient checking and advanced optimization(en)](http://deeplearning.stanford.edu/wiki/index.php/Gradient_checking_and_advanced_optimization)
- [Gradient checking and advanced optimization(cn)](http://ufldl.stanford.edu/wiki/index.php/%E6%A2%AF%E5%BA%A6%E6%A3%80%E9%AA%8C%E4%B8%8E%E9%AB%98%E7%BA%A7%E4%BC%98%E5%8C%96)


## Numeric Gradient Implementation
21
### Python Interface
Q
qiaolongfei 已提交
22
```python
P
Peng Li 已提交
23
def get_numerical_gradient(op,
Q
qiaolongfei 已提交
24 25 26 27 28 29 30 31 32
                         input_values,
                         output_name,
                         input_to_check,
                         delta=0.005,
                         local_scope=None):
    """
    Get Numeric Gradient for an operator's input.

    :param op: C++ operator instance, could be an network
P
Peng Li 已提交
33 34
    :param input_values: The input variables. Should be an dictionary, whose key is
    variable name, and value is numpy array.
Q
qiaolongfei 已提交
35
    :param output_name: The final output variable name.
P
Peng Li 已提交
36
    :param input_to_check: The input variable with respect to which to compute the gradient.
Q
qiaolongfei 已提交
37 38
    :param delta: The perturbation value for numeric gradient method. The
    smaller delta is, the more accurate result will get. But if that delta is
P
Peng Li 已提交
39
     too small, it will suffer from numerical stability problem.
Q
qiaolongfei 已提交
40 41 42 43 44 45 46
    :param local_scope: The local scope used for get_numeric_gradient.
    :return: The gradient array in numpy format.
    """
```

### Explaination:

47
- Why need `output_name`
P
Peng Li 已提交
48
  - An Operator may have multiple Output, one can get independent gradient from each Output. So caller should specify the name of the output variable.
Q
qiaolongfei 已提交
49

50
- Why need `input_to_check`
P
Peng Li 已提交
51
  - One operator may have multiple inputs. Gradient Op can calculate the gradient of these inputs at the same time. But Numeric Gradient needs to calculate them one by one. So `get_numeric_gradient` is designed to calculate the gradient for one input. If you need to compute multiple inputs, you can call `get_numeric_gradient` multiple times.
Q
qiaolongfei 已提交
52 53


54
### Core Algorithm Implementation
Q
qiaolongfei 已提交
55 56 57


```python
P
Peng Li 已提交
58 59
    # we only compute gradient of one element a time.
    # we use a for loop to compute the gradient of each element.
Q
qiaolongfei 已提交
60
    for i in xrange(tensor_size):
P
Peng Li 已提交
61
        # get one input element by its index i.
Q
qiaolongfei 已提交
62 63
        origin = tensor_to_check.get_float_element(i)

P
Peng Li 已提交
64
        # add delta to it, run op and then get the new value of the result tensor.
Q
qiaolongfei 已提交
65 66 67 68
        x_pos = origin + delta
        tensor_to_check.set_float_element(i, x_pos)
        y_pos = get_output()

P
Peng Li 已提交
69
        # plus delta to this element, run op and get the new value of the result tensor.
Q
qiaolongfei 已提交
70 71 72 73 74 75 76 77 78 79 80 81 82 83
        x_neg = origin - delta
        tensor_to_check.set_float_element(i, x_neg)
        y_neg = get_output()

        # restore old value
        tensor_to_check.set_float_element(i, origin)

        # compute the gradient of this element and store it into a numpy array.
        gradient_flat[i] = (y_pos - y_neg) / delta / 2

    # reshape the gradient result to the shape of the source tensor.
    return gradient_flat.reshape(tensor_to_check.get_dims())
```

84
## Auto Graident Checker Framework
Q
qiaolongfei 已提交
85 86 87

Each Operator Kernel has three kinds of Gradient:

P
Peng Li 已提交
88 89 90
1. Numerical gradient
2. CPU kernel gradient
3. GPU kernel gradient (if supported)
Q
qiaolongfei 已提交
91

P
Peng Li 已提交
92
The numerical gradient only relies on forward Operator. So we use the numerical gradient as the reference value. And the gradient checking is performed in the following three steps:
Q
qiaolongfei 已提交
93

P
Peng Li 已提交
94 95 96
1. calculate the numerical gradient
2. calculate CPU kernel gradient with the backward Operator and compare it with the numerical gradient
3. calculate GPU kernel gradient with the backward Operator and compare it with the numeric gradient (if supported)
Q
qiaolongfei 已提交
97

98
#### Python Interface
Q
qiaolongfei 已提交
99 100 101 102 103 104 105 106 107 108 109 110 111 112

```python
    def check_grad(self,
                   forward_op,
                   input_vars,
                   inputs_to_check,
                   output_name,
                   no_grad_set=None,
                   only_cpu=False,
                   max_relative_error=0.005):
        """
        :param forward_op: used to create backward_op
        :param input_vars: numpy value of input variable. The following
            computation will use these variables.
P
Peng Li 已提交
113 114
        :param inputs_to_check: the input variable with respect to which to compute the gradient.
        :param output_name: The final output variable name.
Q
qiaolongfei 已提交
115 116 117 118 119 120 121
        :param max_relative_error: The relative tolerance parameter.
        :param no_grad_set: used when create backward ops
        :param only_cpu: only compute and check gradient on cpu kernel.
        :return:
        """
```

122
### How to check if two numpy array is close enough?
P
Peng Li 已提交
123
if `abs_numerical_grad` is nearly zero, then use abs error for numerical_grad
Q
qiaolongfei 已提交
124 125

```python
P
Peng Li 已提交
126
numerical_grad = ...
Q
qiaolongfei 已提交
127 128
operator_grad = numpy.array(scope.find_var(grad_var_name(name)).get_tensor())

P
Peng Li 已提交
129 130
abs_numerical_grad = numpy.abs(numerical_grad)
# if abs_numerical_grad is nearly zero, then use abs error for numeric_grad, not relative
Q
qiaolongfei 已提交
131
# error.
P
Peng Li 已提交
132
abs_numerical_grad[abs_numerical_grad < 1e-3] = 1
Q
qiaolongfei 已提交
133

P
Peng Li 已提交
134
diff_mat = numpy.abs(abs_numerical_grad - operator_grad) / abs_numerical_grad
Q
qiaolongfei 已提交
135 136 137 138 139
max_diff = numpy.max(diff_mat)
```


#### Notes:
P
Peng Li 已提交
140
The Input data for auto gradient checker should be reasonable to avoid numerical  stability problem.
Q
qiaolongfei 已提交
141 142


143
#### Refs:
Q
qiaolongfei 已提交
144 145 146

- [Gradient checking and advanced optimization(en)](http://deeplearning.stanford.edu/wiki/index.php/Gradient_checking_and_advanced_optimization)
- [Gradient checking and advanced optimization(cn)](http://ufldl.stanford.edu/wiki/index.php/%E6%A2%AF%E5%BA%A6%E6%A3%80%E9%AA%8C%E4%B8%8E%E9%AB%98%E7%BA%A7%E4%BC%98%E5%8C%96)