| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| import numpy as np |
| import tvm |
| from tvm import te |
| import tvm.testing |
| |
| |
| def test_check_numerical_grads(): |
| # Functions and their derivatives |
| functions = [ |
| lambda x: (x * x * x, 3 * x * x), |
| lambda x: (x * x, 2 * x), |
| lambda x: (np.abs(x), np.sign(x)), |
| lambda x: (np.log(np.abs(x)), 1 / x), |
| lambda x: (np.sqrt(np.abs(x)), np.sign(x) / (2 * np.sqrt(np.abs(x)))), |
| lambda x: (1 / x, -1 / (x * x)), |
| lambda x: (np.sign(np.sin(1 / x)), np.zeros_like(x)), |
| lambda x: (x * np.sin(1 / x), np.sin(1 / x) - np.cos(1 / x) / x), |
| lambda x: (np.sin(1 / x), -np.cos(1 / x) / (x * x)), |
| lambda x: (np.tan(x), 1.0 / (np.cos(x) * np.cos(x))), |
| ] |
| |
| np.random.seed(0) |
| |
| # Avoid values too close to 0 since singularities of our functions are there |
| min_x = 0.5 |
| |
| for func in functions: |
| x_input = np.random.uniform(min_x, 10, size=(3, 4)) |
| |
| # We need a function returning a scalar, so sum the results |
| func_forw = lambda x: np.sum(func(x)[0]) |
| grads = [func(x_input)[1]] |
| |
| tvm.testing.check_numerical_grads(func_forw, [x_input], grads) |
| |
| # Check functions with multiple arguments |
| for f1 in functions: |
| for f2 in functions: |
| x_input = np.random.uniform(min_x, 10, size=(3, 4)) |
| y_input = np.random.uniform(min_x, 10, size=(3, 4)) |
| |
| func_forw = lambda x, y: np.sum(f1(x)[0] + f2(y)[0]) |
| grads = [f1(x_input)[1], f2(y_input)[1]] |
| |
| tvm.testing.check_numerical_grads(func_forw, [x_input, y_input], grads) |
| |
| # Same thing but with keyword arguments |
| func_forw = lambda x, y: np.sum(f1(x)[0] + f2(y)[0]) |
| grads = {"x": f1(x_input)[1], "y": f2(y_input)[1]} |
| |
| tvm.testing.check_numerical_grads(func_forw, {"x": x_input, "y": y_input}, grads) |
| |
| def _noise1(x, atol=1e-2, rtol=0.1): |
| # We go in random direction using twice the original tolerance to be sure this |
| # results in an error |
| sqrt_n = np.sqrt(float(np.prod(x.shape))) |
| tol = 2 * (np.linalg.norm(x) * rtol + atol * sqrt_n) |
| noise = np.random.normal(size=x.shape) |
| noise = tol * noise / np.linalg.norm(noise) |
| return x + noise |
| |
| def _noise2(x, atol=1e-2, rtol=0.1): |
| # This noise affects just a single component |
| sqrt_n = np.sqrt(float(np.prod(x.shape))) |
| tol = 2 * (np.linalg.norm(x) * rtol + atol * sqrt_n) |
| n = np.random.randint(np.prod(x.shape)) |
| noise = np.zeros_like(x) |
| noise.reshape(-1)[n] = tol |
| return x + noise |
| |
| # Add noise to gradients and check that the function throws |
| for f1 in functions: |
| for f2 in functions: |
| x_input = np.random.uniform(min_x, 10, size=(3, 4)) |
| y_input = np.random.uniform(min_x, 10, size=(3, 4)) |
| |
| func_forw = lambda x, y: np.sum(f1(x)[0] + f2(y)[0]) |
| grads = [_noise1(f1(x_input)[1]), _noise1(f2(y_input)[1])] |
| |
| try: |
| tvm.testing.check_numerical_grads(func_forw, [x_input, y_input], grads) |
| except AssertionError as e: |
| pass |
| else: |
| raise AssertionError("tvm.testing.check_numerical_grads didn't raise an exception") |
| |
| func_forw = lambda x, y: np.sum(f1(x)[0] + f2(y)[0]) |
| grads = {"x": _noise2(f1(x_input)[1]), "y": _noise2(f2(y_input)[1])} |
| |
| try: |
| tvm.testing.check_numerical_grads(func_forw, {"x": x_input, "y": y_input}, grads) |
| except AssertionError as e: |
| pass |
| else: |
| raise AssertionError("tvm.testing.check_numerical_grads didn't raise an exception") |
| |
| |
| if __name__ == "__main__": |
| test_tvm.testing.check_numerical_grads() |