From f0f2e2f92269c59be7b6fa552cfdb43fdcfe65d0 Mon Sep 17 00:00:00 2001 From: JYChen Date: Fri, 1 Apr 2022 14:25:47 +0800 Subject: [PATCH] Add notes and more cases for quantile unittest. (#41191) * add notes for quantile UT * Supoort quantile in static-mode and add UT --- .../fluid/tests/unittests/test_quantile.py | 86 +++++++++++++++++++ python/paddle/tensor/stat.py | 9 +- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/python/paddle/fluid/tests/unittests/test_quantile.py b/python/paddle/fluid/tests/unittests/test_quantile.py index 0fd3c1de9ca..936d1d3be3a 100644 --- a/python/paddle/fluid/tests/unittests/test_quantile.py +++ b/python/paddle/fluid/tests/unittests/test_quantile.py @@ -20,46 +20,59 @@ import paddle class TestQuantile(unittest.TestCase): + """ + This class is used for numerical precision testing. If there is + a corresponding numpy API, the precision comparison can be performed directly. + Otherwise, it needs to be verified by numpy implementated function. + """ + def setUp(self): np.random.seed(678) self.input_data = np.random.rand(6, 7, 8, 9, 10) + # Test correctness when q and axis are set. def test_quantile_single_q(self): x = paddle.to_tensor(self.input_data) paddle_res = paddle.quantile(x, q=0.5, axis=2) np_res = np.quantile(self.input_data, q=0.5, axis=2) self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + # Test correctness for default axis. def test_quantile_with_no_axis(self): x = paddle.to_tensor(self.input_data) paddle_res = paddle.quantile(x, q=0.35) np_res = np.quantile(self.input_data, q=0.35) self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + # Test correctness for multiple axis. def test_quantile_with_multi_axis(self): x = paddle.to_tensor(self.input_data) paddle_res = paddle.quantile(x, q=0.75, axis=[0, 2, 3]) np_res = np.quantile(self.input_data, q=0.75, axis=[0, 2, 3]) self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + # Test correctness when keepdim is set. def test_quantile_with_keepdim(self): x = paddle.to_tensor(self.input_data) paddle_res = paddle.quantile(x, q=0.35, axis=4, keepdim=True) np_res = np.quantile(self.input_data, q=0.35, axis=4, keepdims=True) self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + # Test correctness when all parameters are set. def test_quantile_with_keepdim_and_multiple_axis(self): x = paddle.to_tensor(self.input_data) paddle_res = paddle.quantile(x, q=0.1, axis=[1, 4], keepdim=True) np_res = np.quantile(self.input_data, q=0.1, axis=[1, 4], keepdims=True) self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + # Test correctness when q = 0. def test_quantile_with_boundary_q(self): x = paddle.to_tensor(self.input_data) paddle_res = paddle.quantile(x, q=0, axis=3) np_res = np.quantile(self.input_data, q=0, axis=3) self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + # Test correctness when input includes NaN. def test_quantile_include_NaN(self): input_data = np.random.randn(2, 3, 4) input_data[0, 1, 1] = np.nan @@ -69,6 +82,10 @@ class TestQuantile(unittest.TestCase): class TestQuantileMuitlpleQ(unittest.TestCase): + """ + This class is used to test multiple input of q. + """ + def setUp(self): np.random.seed(678) self.input_data = np.random.rand(10, 3, 4, 5, 4) @@ -95,56 +112,125 @@ class TestQuantileMuitlpleQ(unittest.TestCase): class TestQuantileError(unittest.TestCase): + """ + This class is used to test that exceptions are thrown correctly. + Validity of all parameter values and types should be considered. + """ + def setUp(self): self.x = paddle.randn((2, 3, 4)) def test_errors(self): + # Test error when q > 1 def test_q_range_error_1(): paddle_res = paddle.quantile(self.x, q=1.5) self.assertRaises(ValueError, test_q_range_error_1) + # Test error when q < 0 def test_q_range_error_2(): paddle_res = paddle.quantile(self.x, q=[0.2, -0.3]) self.assertRaises(ValueError, test_q_range_error_2) + # Test error with no valid q def test_q_range_error_3(): paddle_res = paddle.quantile(self.x, q=[]) self.assertRaises(ValueError, test_q_range_error_3) + # Test error when x is not Tensor def test_x_type_error(): x = [1, 3, 4] paddle_res = paddle.quantile(x, q=0.9) self.assertRaises(TypeError, test_x_type_error) + # Test error when scalar axis is not int def test_axis_type_error_1(): paddle_res = paddle.quantile(self.x, q=0.4, axis=0.4) self.assertRaises(ValueError, test_axis_type_error_1) + # Test error when axis in List is not int def test_axis_type_error_2(): paddle_res = paddle.quantile(self.x, q=0.4, axis=[1, 0.4]) self.assertRaises(ValueError, test_axis_type_error_2) + # Test error when axis not in [-D, D) def test_axis_value_error_1(): paddle_res = paddle.quantile(self.x, q=0.4, axis=10) self.assertRaises(ValueError, test_axis_value_error_1) + # Test error when axis not in [-D, D) def test_axis_value_error_2(): paddle_res = paddle.quantile(self.x, q=0.4, axis=[1, -10]) self.assertRaises(ValueError, test_axis_value_error_2) + # Test error with no valid axis def test_axis_value_error_3(): paddle_res = paddle.quantile(self.x, q=0.4, axis=[]) self.assertRaises(ValueError, test_axis_value_error_3) +class TestQuantileRuntime(unittest.TestCase): + """ + This class is used to test the API could run correctly with + different devices, different data types, and dygraph/static mode. + """ + + def setUp(self): + np.random.seed(678) + self.input_data = np.random.rand(6, 7, 8, 9, 10) + self.dtypes = ['float32', 'float64'] + self.devices = ['cpu'] + if paddle.device.is_compiled_with_cuda(): + self.devices.append('gpu') + + def test_dygraph(self): + paddle.disable_static() + for device in self.devices: + # Check different devices + paddle.set_device(device) + for dtype in self.dtypes: + # Check different dtypes + np_input_data = self.input_data.astype(dtype) + x = paddle.to_tensor(np_input_data, dtype=dtype) + paddle_res = paddle.quantile(x, q=0.5, axis=2) + np_res = np.quantile(np_input_data, q=0.5, axis=2) + self.assertTrue(np.allclose(paddle_res.numpy(), np_res)) + + def test_static(self): + paddle.enable_static() + for device in self.devices: + x = paddle.static.data( + name="x", shape=self.input_data.shape, dtype=paddle.float32) + x_fp64 = paddle.static.data( + name="x_fp64", + shape=self.input_data.shape, + dtype=paddle.float64) + + results = paddle.quantile(x, q=0.5, axis=2) + np_input_data = self.input_data.astype('float32') + results_fp64 = paddle.quantile(x_fp64, q=0.5, axis=2) + np_input_data_fp64 = self.input_data.astype('float64') + + exe = paddle.static.Executor(device) + paddle_res, paddle_res_fp64 = exe.run( + paddle.static.default_main_program(), + feed={"x": np_input_data, + "x_fp64": np_input_data_fp64}, + fetch_list=[results, results_fp64]) + np_res = np.quantile(np_input_data, q=0.5, axis=2) + np_res_fp64 = np.quantile(np_input_data_fp64, q=0.5, axis=2) + self.assertTrue( + np.allclose(paddle_res, np_res) and np.allclose(paddle_res_fp64, + np_res_fp64)) + + if __name__ == '__main__': unittest.main() diff --git a/python/paddle/tensor/stat.py b/python/paddle/tensor/stat.py index dd0da03e4fd..5876b918082 100644 --- a/python/paddle/tensor/stat.py +++ b/python/paddle/tensor/stat.py @@ -387,7 +387,7 @@ def quantile(x, q, axis=None, keepdim=False): if not isinstance(x, Variable): raise TypeError("input x should be a Tensor.") dims = len(x.shape) - out_shape = x.shape + out_shape = list(x.shape) if axis is None: x = paddle.flatten(x) axis = 0 @@ -433,16 +433,15 @@ def quantile(x, q, axis=None, keepdim=False): indices.append(q_num * (x.shape[axis] - 1)) else: raise TypeError("Type of q should be int, float, list or tuple.") - indices = paddle.to_tensor(indices).astype(paddle.float32) sorted_tensor = paddle.sort(x, axis) - indices_below = paddle.floor(indices).astype(paddle.int32) - indices_upper = paddle.ceil(indices).astype(paddle.int32) + indices_tensor = paddle.assign(indices).astype(paddle.float32) + indices_below = paddle.floor(indices_tensor).astype(paddle.int32) + indices_upper = paddle.ceil(indices_tensor).astype(paddle.int32) outputs = [] def expand_dim(indices, sorted_tensor_shape, axis): assert axis < len(list(sorted_tensor_shape)) expanded_shape = [1] * len(list(sorted_tensor_shape)) - expanded_shape[axis] = len(indices) expanded_shape = tuple(expanded_shape) indices = indices.reshape(expanded_shape) return indices -- GitLab