From 525873f106d6958dc1c1146a29d37f05a029aedf Mon Sep 17 00:00:00 2001 From: Yiyan66 <57363390+Yiyan66@users.noreply.github.com> Date: Thu, 2 Apr 2020 12:03:36 +0800 Subject: [PATCH] [numpy] add op median (#17084) * part * wrapper * sanity --- 3rdparty/mkldnn | 2 +- python/mxnet/ndarray/numpy/_op.py | 51 ++++++++++++++++++- python/mxnet/numpy/multiarray.py | 51 ++++++++++++++++++- python/mxnet/numpy_dispatch_protocol.py | 1 + python/mxnet/symbol/numpy/_symbol.py | 39 +++++++++++++- .../unittest/test_numpy_interoperability.py | 9 ++++ tests/python/unittest/test_numpy_op.py | 40 +++++++++++++++ 7 files changed, 189 insertions(+), 4 deletions(-) diff --git a/3rdparty/mkldnn b/3rdparty/mkldnn index 8e96ef49488c..cb2cc7ac17ff 160000 --- a/3rdparty/mkldnn +++ b/3rdparty/mkldnn @@ -1 +1 @@ -Subproject commit 8e96ef49488c65e0738c552cec5c0563ab92c1af +Subproject commit cb2cc7ac17ff4e2ef50805c7048d33256d82be4d diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index a1b6ff8b5bac..abd8147353d5 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -36,7 +36,7 @@ 'absolute', 'exp', 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'matmul', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'histogram', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'argsort', 'all', 'any', 'sort', - 'tensordot', 'eye', 'linspace', + 'tensordot', 'eye', 'linspace', 'median', 'logspace', 'expand_dims', 'tile', 'arange', 'array_split', 'split', 'hsplit', 'vsplit', 'dsplit', 'concatenate', 'append', 'stack', 'vstack', 'row_stack', 'column_stack', 'hstack', 'dstack', 'average', 'mean', 'maximum', 'fmax', 'minimum', 'fmin', 'around', 'round', 'round_', 'flatnonzero', @@ -6923,6 +6923,55 @@ def percentile(a, q, axis=None, out=None, overwrite_input=None, interpolation='l return _api_internal.percentile(a, q, axis, interpolation, keepdims, out) +@set_module('mxnet.ndarray.numpy') +def median(a, axis=None, out=None, overwrite_input=None, keepdims=False): + r""" + Compute the median along the specified axis. + Returns the median of the array elements. + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : {int, sequence of int, None}, optional + Axis or axes along which the medians are computed. The default + is to compute the median along a flattened version of the array. + A sequence of axes is supported since version 1.9.0. + out : ndarray, optional + Alternative output array in which to place the result. It must + have the same shape and buffer length as the expected output, + but the type (of the output) will be cast if necessary. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `arr`. + Returns + ------- + median : ndarray + A new array holding the result. If the input contains integers + or floats smaller than ``float32``, then the output data-type is + ``np.float32``. Otherwise, the data-type of the output is the + same as that of the input. If `out` is specified, that array is + returned instead. + See Also + -------- + mean, percentile + Examples + -------- + >>> a = np.array([[10, 7, 4], [3, 2, 1]]) + >>> a + array([[10, 7, 4], + [ 3, 2, 1]]) + >>> np.median(a) + 3.5 + >>> np.median(a, axis=0) + array([6.5, 4.5, 2.5]) + >>> np.median(a, axis=1) + array([7., 2.]) + """ + return quantile(a=a, q=0.5, axis=axis, out=out, overwrite_input=overwrite_input, + interpolation='midpoint', keepdims=keepdims) + + @set_module('mxnet.ndarray.numpy') def quantile(a, q, axis=None, out=None, overwrite_input=None, interpolation='linear', keepdims=False): # pylint: disable=too-many-arguments """ diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 281a6f7cc3fc..aba1d7ba87e7 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -52,7 +52,7 @@ from . import fallback -__all__ = ['ndarray', 'empty', 'empty_like', 'array', 'shape', +__all__ = ['ndarray', 'empty', 'empty_like', 'array', 'shape', 'median', 'zeros', 'zeros_like', 'ones', 'ones_like', 'full', 'full_like', 'all', 'any', 'broadcast_to', 'add', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'fmod', 'power', 'bitwise_not', 'delete', @@ -8981,6 +8981,55 @@ def percentile(a, q, axis=None, out=None, overwrite_input=None, interpolation='l interpolation=interpolation, keepdims=keepdims) +@set_module('mxnet.numpy') +def median(a, axis=None, out=None, overwrite_input=None, keepdims=False): + r""" + Compute the median along the specified axis. + Returns the median of the array elements. + Parameters + ---------- + a : array_like + Input array or object that can be converted to an array. + axis : {int, sequence of int, None}, optional + Axis or axes along which the medians are computed. The default + is to compute the median along a flattened version of the array. + A sequence of axes is supported since version 1.9.0. + out : ndarray, optional + Alternative output array in which to place the result. It must + have the same shape and buffer length as the expected output, + but the type (of the output) will be cast if necessary. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `arr`. + Returns + ------- + median : ndarray + A new array holding the result. If the input contains integers + or floats smaller than ``float32``, then the output data-type is + ``np.float32``. Otherwise, the data-type of the output is the + same as that of the input. If `out` is specified, that array is + returned instead. + See Also + -------- + mean, percentile + Examples + -------- + >>> a = np.array([[10, 7, 4], [3, 2, 1]]) + >>> a + array([[10, 7, 4], + [ 3, 2, 1]]) + >>> np.median(a) + 3.5 + >>> np.median(a, axis=0) + array([6.5, 4.5, 2.5]) + >>> np.median(a, axis=1) + array([7., 2.]) + """ + return _mx_nd_np.median(a, axis=axis, overwrite_input=overwrite_input, + keepdims=keepdims, out=out) + + @set_module('mxnet.numpy') def quantile(a, q, axis=None, out=None, overwrite_input=None, interpolation='linear', keepdims=False): # pylint: disable=too-many-arguments """ diff --git a/python/mxnet/numpy_dispatch_protocol.py b/python/mxnet/numpy_dispatch_protocol.py index 110f2273a852..ab5893b3205a 100644 --- a/python/mxnet/numpy_dispatch_protocol.py +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -174,6 +174,7 @@ def _run_with_array_ufunc_proto(*args, **kwargs): 'shares_memory', 'may_share_memory', 'quantile', + 'median', 'percentile', 'diff', 'ediff1d', diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index a2a4cd9d3584..451416bb1897 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -39,7 +39,7 @@ 'delete', 'add', 'broadcast_to', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'fmod', 'power', 'arctan2', 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'log10', 'sqrt', 'cbrt', 'abs', 'absolute', 'fabs', 'exp', - 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'log1p', 'matmul', + 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'log1p', 'matmul', 'median', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'histogram', 'insert', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'argsort', 'sort', 'tensordot', 'eye', 'linspace', 'logspace', 'expand_dims', 'tile', 'arange', 'array_split', 'split', 'hsplit', 'vsplit', 'dsplit', @@ -6163,6 +6163,43 @@ def percentile(a, q, axis=None, out=None, overwrite_input=None, interpolation='l keepdims=keepdims, q_scalar=None, out=out) +@set_module('mxnet.symbol.numpy') +def median(a, axis=None, out=None, overwrite_input=None, keepdims=False): + r""" + Compute the median along the specified axis. + Returns the median of the array elements. + Parameters + ---------- + a : _Symbol + Input array or object that can be converted to an array. + axis : {int, sequence of int, None}, optional + Axis or axes along which the medians are computed. The default + is to compute the median along a flattened version of the array. + A sequence of axes is supported since version 1.9.0. + out : _Symbol, optional + Alternative output array in which to place the result. It must + have the same shape and buffer length as the expected output, + but the type (of the output) will be cast if necessary. + keepdims : bool, optional + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `arr`. + Returns + ------- + median : _Symbol + A new array holding the result. If the input contains integers + or floats smaller than ``float32``, then the output data-type is + ``np.float32``. Otherwise, the data-type of the output is the + same as that of the input. If `out` is specified, that array is + returned instead. + See Also + -------- + mean, percentile + """ + return quantile(a=a, q=0.5, axis=axis, out=out, overwrite_input=overwrite_input, + interpolation='midpoint', keepdims=keepdims) + + @set_module('mxnet.symbol.numpy') def quantile(a, q, axis=None, out=None, overwrite_input=None, interpolation='linear', keepdims=False): # pylint: disable=too-many-arguments """ diff --git a/tests/python/unittest/test_numpy_interoperability.py b/tests/python/unittest/test_numpy_interoperability.py index a4492a3beab1..57ed9d8feb54 100644 --- a/tests/python/unittest/test_numpy_interoperability.py +++ b/tests/python/unittest/test_numpy_interoperability.py @@ -178,6 +178,14 @@ def _add_workload_diagonal(): OpArgMngr.add_workload('diagonal', B, 0, 2, 1) +def _add_workload_median(array_pool): + OpArgMngr.add_workload('median', array_pool['4x1']) + OpArgMngr.add_workload('median', array_pool['4x1'], axis=0, keepdims=True) + OpArgMngr.add_workload('median', np.array([[1, 2, 3], [4, 5, 6]])) + OpArgMngr.add_workload('median', np.array([[1, 2, 3], [4, 5, 6]]), axis=0) + OpArgMngr.add_workload('median', np.array([[1, 2, 3], [4, 5, 6]]), axis=1) + + def _add_workload_quantile(): x1 = np.arange(8) * 0.5 x2 = np.arange(100.) @@ -2915,6 +2923,7 @@ def _prepare_workloads(): _add_workload_diff() _add_workload_ediff1d() _add_workload_quantile() + _add_workload_median(array_pool) _add_workload_percentile() _add_workload_resize() _add_workload_full_like(array_pool) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 3a8325cfb6e2..25b2098a0796 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -7388,6 +7388,46 @@ def test_np_share_memory(): assert not op(np.ones((5, 0), dtype=dt), np.ones((0, 3, 0), dtype=adt)) +def test_np_median(): + class TestMedian(HybridBlock): + def __init__(self, axis=None, keepdims=False): + super(TestMedian, self).__init__() + self._axis = axis + self._keepdims = keepdims + + def hybrid_forward(self, F, a): + return F.np.median(a, axis=self._axis, keepdims=self._keepdims) + + flags = [True, False] + dtypes = ['float16', 'float32', 'float64'] + qtypes = ['float32', 'float64'] + tensor_shapes = [ + ((2, 3), None), + ((2, 3, 4, 5), 3), + ((2, 3, 4), (0, 2)), + ((2, 3, 4), 1) + ] + + for hybridize, keepdims, (a_shape, axis), dtype in \ + itertools.product(flags, flags, tensor_shapes, dtypes): + atol = 3e-4 if dtype == 'float16' else 1e-4 + rtol = 3e-2 if dtype == 'float16' else 1e-2 + test_median = TestMedian(axis=axis, keepdims=keepdims) + if hybridize: + test_median.hybridize() + a = np.random.uniform(-1.0, 1.0, size=a_shape) + np_out = _np.median(a.asnumpy(), axis=axis, keepdims=keepdims) + mx_out = test_median(a) + + assert mx_out.shape == np_out.shape + assert_almost_equal(mx_out.asnumpy(), np_out, atol=atol, rtol=rtol) + + mx_out = np.median(a, axis=axis, keepdims=keepdims) + np_out = _np.median(a.asnumpy(), axis=axis, keepdims=keepdims) + + assert_almost_equal(mx_out.asnumpy(), np_out, atol=atol, rtol=rtol) + + @with_seed() @use_np def test_np_quantile():