auto_gradient_check.md 6.5 KB
Newer Older
1
## Auto Gradient Check Design
Q
qiaolongfei 已提交
2

3 4 5 6 7
## Background:
- 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 because of the following challenges:
  1. The formula for backpropagation formula should be correct according to the forward computation.
  2. The Implementation of the above shoule be correct in CPP.
  3. It is difficult to prepare an unbiased test data.
Q
qiaolongfei 已提交
8

9 10 11
- Auto gradient checking gets a numerical gradient using forward Operator and uses it as a reference for the backward Operator's result. It has several advantages:
  1. Numerical gradient checker only needs the forward operator.
  2. The user only needs to prepare the input data for forward Operator and not worry about the backward Operator.
Q
qiaolongfei 已提交
12

13
## Mathematical Theory
14
The following documents from Stanford have a detailed explanation of how to compute the numerical gradient and why it is useful.
Q
qiaolongfei 已提交
15 16 17 18 19

- [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)


20
## Numerical 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
                         input_values,
                         output_name,
                         input_to_check,
                         delta=0.005,
                         local_scope=None):
    """
30
    Get Numerical Gradient for the input of an operator.
Q
qiaolongfei 已提交
31

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

45
### Explanation:
Q
qiaolongfei 已提交
46

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

50 51
- Why do we need `input_to_check`
  - One operator can have multiple inputs. Gradient Op can calculate the gradient of these inputs at the same time. But Numerical 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 each with a different input.
Q
qiaolongfei 已提交
52 53


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


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

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

70 71 72
        # Subtract delta from this element, run the op again
        # and get the new value of the result tensor.
        x_neg = original - delta
Q
qiaolongfei 已提交
73 74 75 76
        tensor_to_check.set_float_element(i, x_neg)
        y_neg = get_output()

        # restore old value
77
        tensor_to_check.set_float_element(i, original)
Q
qiaolongfei 已提交
78

79 80
        # compute the gradient of this element and store
        # it into a numpy array.
Q
qiaolongfei 已提交
81 82 83 84 85 86
        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())
```

87
## Auto Gradient Check Framework
Q
qiaolongfei 已提交
88 89 90

Each Operator Kernel has three kinds of Gradient:

P
Peng Li 已提交
91 92
1. Numerical gradient
2. CPU kernel gradient
93
3. GPU kernel gradient (if supported by the device)
Q
qiaolongfei 已提交
94

95
The numerical gradient only relies on the forward Operator, so we use the numerical gradient as the reference value. The gradient checking is performed in the following three steps:
Q
qiaolongfei 已提交
96

97 98 99
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 已提交
100

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

```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
115 116 117
          computation will use these variables.
        :param inputs_to_check: the input variable with respect to which the
          gradient will be computed.
P
Peng Li 已提交
118
        :param output_name: The final output variable name.
Q
qiaolongfei 已提交
119
        :param max_relative_error: The relative tolerance parameter.
120
        :param no_grad_set: used to create backward ops
Q
qiaolongfei 已提交
121 122 123 124 125
        :param only_cpu: only compute and check gradient on cpu kernel.
        :return:
        """
```

126 127
### How to check if two numpy arrays are close enough?
if `abs_numerical_grad` is nearly zero, then use absolute error for numerical_grad.
Q
qiaolongfei 已提交
128 129

```python
P
Peng Li 已提交
130
numerical_grad = ...
Q
qiaolongfei 已提交
131 132
operator_grad = numpy.array(scope.find_var(grad_var_name(name)).get_tensor())

P
Peng Li 已提交
133
abs_numerical_grad = numpy.abs(numerical_grad)
134 135
# if abs_numerical_grad is nearly zero, then use abs error for
# numeric_grad, instead of relative error.
P
Peng Li 已提交
136
abs_numerical_grad[abs_numerical_grad < 1e-3] = 1
Q
qiaolongfei 已提交
137

P
Peng Li 已提交
138
diff_mat = numpy.abs(abs_numerical_grad - operator_grad) / abs_numerical_grad
Q
qiaolongfei 已提交
139 140 141 142 143
max_diff = numpy.max(diff_mat)
```


#### Notes:
144
The Input data for auto gradient checker should be reasonable to avoid numerical stability problem.
Q
qiaolongfei 已提交
145 146


147
#### References:
Q
qiaolongfei 已提交
148 149 150

- [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)