From 8029a4eaf353946877d3eb945b580f0a35747924 Mon Sep 17 00:00:00 2001 From: Alicia1529 Date: Mon, 11 Nov 2019 14:09:36 +0800 Subject: [PATCH] numpy op empty_like, add nan_to_num to dispatch --- python/mxnet/ndarray/numpy/_op.py | 70 ++++++++++++++++++- python/mxnet/numpy/multiarray.py | 64 ++++++++++++++++- python/mxnet/numpy_dispatch_protocol.py | 2 + python/mxnet/numpy_op_fallback.py | 44 ++++++++++++ python/mxnet/symbol/numpy/_symbol.py | 60 +++++++++++++++- .../unittest/test_numpy_interoperability.py | 22 +++++- tests/python/unittest/test_numpy_op.py | 60 ++++++++++++++++ 7 files changed, 318 insertions(+), 4 deletions(-) diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index 3cc5b85c8384..7132d87d7918 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -39,7 +39,7 @@ 'around', 'hypot', 'bitwise_xor', 'rad2deg', 'deg2rad', 'unique', 'lcm', 'tril', 'identity', 'take', 'ldexp', 'vdot', 'inner', 'outer', 'equal', 'not_equal', 'greater', 'less', 'greater_equal', 'less_equal', 'hsplit', 'rot90', 'einsum', 'true_divide', 'nonzero', 'shares_memory', 'may_share_memory', 'diff', 'resize', - 'nan_to_num', 'where'] + 'nan_to_num', 'where', 'empty_like'] @set_module('mxnet.ndarray.numpy') @@ -201,6 +201,74 @@ def full(shape, fill_value, dtype=None, order='C', ctx=None, out=None): # pylint: enable=too-many-arguments, redefined-outer-name +def empty_like(prototype, dtype=None, order='K', subok=True, shape=None): + """ + Return a new array with the same shape and type as a given array. + + Parameters + ---------- + prototype : ndarray + The shape and data-type of `prototype` define these same attributes + of the returned array. + dtype : data-type, optional + Overrides the data type of the result. + + order : {'C', 'F', 'A', or 'K'}, optional + Overrides the memory layout of the result. 'C' means C-order, + 'F' means F-order, 'A' means 'F' if ``prototype`` is Fortran + contiguous, 'C' otherwise. 'K' means match the layout of ``prototype`` + as closely as possible. + + subok : bool, optional. + If True, then the newly created array will use the sub-class + type of 'a', otherwise it will be a base-class array. Defaults + to True. + shape : int or sequence of ints, optional. + Overrides the shape of the result. If order='K' and the number of + dimensions is unchanged, will try to keep order, otherwise, + order='C' is implied. + (Not supported at this moment) + + Returns + ------- + out : ndarray + Array of uninitialized (arbitrary) data with the same + shape and type as `prototype`. + + See Also + -------- + ones_like : Return an array of ones with shape and type of input. + zeros_like : Return an array of zeros with shape and type of input. + full_like : Return a new array with shape of input filled with value. + empty : Return a new uninitialized array. + + Notes + ----- + This function does *not* initialize the returned array; to do that use + `zeros_like` or `ones_like` instead. It may be marginally faster than + the functions that do set the array values. + + Examples + -------- + >>> a = np.array([[1,2,3], [4,5,6]]) + >>> np.empty_like(a) + array([[-5764607523034234880, -2305834244544065442, 4563075075], # uninitialized + [ 4567052944, -5764607523034234880, 844424930131968]]) + >>> a = np.array([[1., 2., 3.],[4.,5.,6.]]) + >>> np.empty_like(a) + array([[4.9e-324, 9.9e-324, 1.5e-323], # uninitialized + [2.0e-323, 2.5e-323, 3.0e-323]]) + """ + dtype_list = {None:'None', _np.int8:'int8', _np.uint8:'uint8', _np.int32:'int32', + _np.int64:'int64', _np.float16:'float16', _np.float32:'float32', + _np.float64:'float64', _np.bool_:'bool'} + try: + dtype = dtype if isinstance(dtype, str) else dtype_list[dtype] + except: + raise NotImplementedError("Do not support this dtype at this moment") + return _npi.empty_like_fallback(prototype, dtype=dtype, order=order, subok=subok, shape=shape) + + @set_module('mxnet.ndarray.numpy') def arange(start, stop=None, step=1, dtype=None, ctx=None): """Return evenly spaced values within a given interval. diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index e94d4c8341b4..a761a39fbc79 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -49,7 +49,7 @@ __all__ = ['ndarray', 'empty', 'array', 'shape', 'zeros', 'ones', 'full', 'add', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'power', 'arctan2', 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'log10', 'sqrt', 'cbrt', 'abs', 'absolute', 'exp', 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', - 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', + 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'empty_like', 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'append', 'tensordot', 'histogram', 'eye', 'linspace', 'logspace', 'expand_dims', 'tile', 'arange', 'split', 'vsplit', 'concatenate', 'stack', 'vstack', 'column_stack', 'dstack', 'mean', 'maximum', 'minimum', @@ -1963,6 +1963,68 @@ def empty(shape, dtype=_np.float32, order='C', ctx=None): # pylint: disable=red return ndarray(handle=_new_alloc_handle(shape, ctx, False, dtype)) +@set_module('mxnet.numpy') +def empty_like(prototype, dtype=None, order='K', subok=True, shape=None): + """ + Return a new array with the same shape and type as a given array. + + Parameters + ---------- + prototype : ndarray + The shape and data-type of `prototype` define these same attributes + of the returned array. + dtype : data-type, optional + Overrides the data type of the result. + + order : {'C', 'F', 'A', or 'K'}, optional + Overrides the memory layout of the result. 'C' means C-order, + 'F' means F-order, 'A' means 'F' if ``prototype`` is Fortran + contiguous, 'C' otherwise. 'K' means match the layout of ``prototype`` + as closely as possible. + + subok : bool, optional. + If True, then the newly created array will use the sub-class + type of 'a', otherwise it will be a base-class array. Defaults + to True. + shape : int or sequence of ints, optional. + Overrides the shape of the result. If order='K' and the number of + dimensions is unchanged, will try to keep order, otherwise, + order='C' is implied. + (Not supported at this moment) + + Returns + ------- + out : ndarray + Array of uninitialized (arbitrary) data with the same + shape and type as `prototype`. + + See Also + -------- + ones_like : Return an array of ones with shape and type of input. + zeros_like : Return an array of zeros with shape and type of input. + full_like : Return a new array with shape of input filled with value. + empty : Return a new uninitialized array. + + Notes + ----- + This function does *not* initialize the returned array; to do that use + `zeros_like` or `ones_like` instead. It may be marginally faster than + the functions that do set the array values. + + Examples + -------- + >>> a = np.array([[1,2,3], [4,5,6]]) + >>> np.empty_like(a) + array([[-5764607523034234880, -2305834244544065442, 4563075075], # uninitialized + [ 4567052944, -5764607523034234880, 844424930131968]]) + >>> a = np.array([[1., 2., 3.],[4.,5.,6.]]) + >>> np.empty_like(a) + array([[4.9e-324, 9.9e-324, 1.5e-323], # uninitialized + [2.0e-323, 2.5e-323, 3.0e-323]]) + """ + return _mx_nd_np.empty_like(prototype, dtype=dtype, order=order, subok=subok, shape=shape) + + @set_module('mxnet.numpy') def array(object, dtype=None, ctx=None): """ diff --git a/python/mxnet/numpy_dispatch_protocol.py b/python/mxnet/numpy_dispatch_protocol.py index f58159303d0f..2982d3c7d65a 100644 --- a/python/mxnet/numpy_dispatch_protocol.py +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -139,6 +139,8 @@ def _run_with_array_ufunc_proto(*args, **kwargs): 'diff', 'resize', 'where', + 'empty_like', + 'nan_to_num', ] diff --git a/python/mxnet/numpy_op_fallback.py b/python/mxnet/numpy_op_fallback.py index b98a211c7169..bd07da5a1979 100644 --- a/python/mxnet/numpy_op_fallback.py +++ b/python/mxnet/numpy_op_fallback.py @@ -18,6 +18,7 @@ """Fallback-to-NumPy operator implementation.""" from __future__ import absolute_import +from distutils.version import StrictVersion import functools import ast import numpy as np @@ -49,6 +50,49 @@ def _register_helper(prop_cls): return _register_helper +@use_np # enforce np shape and array semantics for all the methods in this class +class EmptyLike(operator.CustomOp): + """Fallback to NumPy empty_like operator.""" + def __init__(self, dtype, order, subok, shape): + super(EmptyLike, self).__init__() + self._dtype = dtype + self._order = order + self._subok = subok + self._shape = shape + + def forward(self, is_train, req, in_data, out_data, aux): + np_version = np.version.version + if StrictVersion(np_version) >= StrictVersion('1.6.0'): + out = np.empty_like(in_data[0].asnumpy(), dtype=self._dtype, order=self._order, + subok=self._subok) + else: + out = np.empty_like(in_data[0].asnumpy()) + self.assign(out_data[0], req[0], _mx_np.array(out, dtype=out.dtype, ctx=out_data[0].ctx)) + + def backward(self, req, out_grad, in_data, out_data, in_grad, aux): + raise NotImplementedError('Operator empty_like does not support gradient computation') + + +@register('empty_like_fallback') +class EmptyLikeProp(operator.CustomOpProp): + """Fallback empty_like operator properties.""" + def __init__(self, dtype, order, subok, shape): + super(EmptyLikeProp, self).__init__(need_top_grad=True) + self._dtype = None if dtype == 'None' else dtype + self._order = order + self._subok = ast.literal_eval(subok) + self._shape = ast.literal_eval(shape) + + def list_arguments(self): + return ['prototype'] + + def infer_shape(self, in_shape): + return (in_shape[0],), (in_shape[0],), () + + def create_operator(self, ctx, in_shapes, in_dtypes): + return EmptyLike(self._dtype, self._order, self._subok, self._shape) + + @use_np # enforce np shape and array semantics for all the methods in this class class Resize(operator.CustomOp): """Fallback to NumPy resize operator.""" diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 7da771966f1f..210d29818286 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -34,7 +34,7 @@ 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'log10', 'sqrt', 'cbrt', 'abs', 'absolute', 'exp', 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', - 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'histogram', 'eye', + 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'histogram', 'eye', 'empty_like', 'linspace', 'logspace', 'expand_dims', 'tile', 'arange', 'split', 'vsplit', 'concatenate', 'append', 'stack', 'vstack', 'column_stack', 'dstack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'argmin', 'std', 'var', 'indices', 'copysign', 'ravel', 'hanning', 'hamming', 'blackman', 'flip', @@ -1344,6 +1344,64 @@ def eye(N, M=None, k=0, dtype=_np.float32, **kwargs): return _npi.eye(N, M, k, ctx, dtype) +@set_module('mxnet.symbol.numpy') +def empty_like(prototype, dtype=None, order='K', subok=True, shape=None): + """ + Return a new array with the same shape and type as a given array. + + Parameters + ---------- + prototype : _Symbol + The shape and data-type of `prototype` define these same attributes + of the returned array. + dtype : data-type, optional + Overrides the data type of the result. + + order : {'C', 'F', 'A', or 'K'}, optional + Overrides the memory layout of the result. 'C' means C-order, + 'F' means F-order, 'A' means 'F' if ``prototype`` is Fortran + contiguous, 'C' otherwise. 'K' means match the layout of ``prototype`` + as closely as possible. + + subok : bool, optional. + If True, then the newly created array will use the sub-class + type of 'a', otherwise it will be a base-class array. Defaults + to True. + shape : int or sequence of ints, optional. + Overrides the shape of the result. If order='K' and the number of + dimensions is unchanged, will try to keep order, otherwise, + order='C' is implied. + (Not supported at this moment) + + Returns + ------- + out : _Symbol + Array of uninitialized (arbitrary) data with the same + shape and type as `prototype`. + + See Also + -------- + ones_like : Return an array of ones with shape and type of input. + zeros_like : Return an array of zeros with shape and type of input. + full_like : Return a new array with shape of input filled with value. + empty : Return a new uninitialized array. + + Notes + ----- + This function does *not* initialize the returned array; to do that use + `zeros_like` or `ones_like` instead. It may be marginally faster than + the functions that do set the array values. + """ + dtype_list = {None:'None', _np.int8:'int8', _np.uint8:'uint8', _np.int32:'int32', + _np.int64:'int64', _np.float16:'float16', _np.float32:'float32', + _np.float64:'float64', _np.bool_:'bool'} + try: + dtype = dtype if isinstance(dtype, str) else dtype_list[dtype] + except: + raise NotImplementedError("Do not support this dtype at this moment") + return _npi.empty_like_fallback(prototype, dtype=dtype, order=order, subok=subok, shape=shape) + + @set_module('mxnet.symbol.numpy') def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, ctx=None): # pylint: disable=too-many-arguments r""" diff --git a/tests/python/unittest/test_numpy_interoperability.py b/tests/python/unittest/test_numpy_interoperability.py index e52d25239d22..51c431777329 100644 --- a/tests/python/unittest/test_numpy_interoperability.py +++ b/tests/python/unittest/test_numpy_interoperability.py @@ -1242,6 +1242,24 @@ def _add_workload_resize(): OpArgMngr.add_workload('resize', np.zeros((10, 0)), (0, 10)) OpArgMngr.add_workload('resize', np.zeros((10, 0)), (0, 100)) +def _add_workload_empty_like(): + OpArgMngr.add_workload('empty_like', np.random.uniform(low=0, high=100, size=(1,3,4), dtype='float64')) + OpArgMngr.add_workload('empty_like', np.random.uniform(low=0, high=100, size=(9,3,1)), np.int32) + OpArgMngr.add_workload('empty_like', np.random.uniform(low=0, high=100, size=(9,3)), 'float32') + OpArgMngr.add_workload('empty_like', np.random.uniform(low=0, high=100, size=(9,3,1)), np.bool_, 'K', True) + OpArgMngr.add_workload('empty_like', np.random.uniform(low=0, high=100, size=(0,3)), np.float32) + + +def _add_workload_nan_to_num(): + array1 = np.array([[-433, 0, 456, _np.inf], [-1, -_np.inf, 0, 1]]) + array2 = np.array([_np.nan, _np.inf, -_np.inf, -574, 0, 23425, 24234,-5]) + array3 = np.array(-_np.inf) + OpArgMngr.add_workload('nan_to_num', array1, True, 0, 100, -100) + OpArgMngr.add_workload('nan_to_num', array1, True, 0.00) + OpArgMngr.add_workload('nan_to_num', array2, True) + OpArgMngr.add_workload('nan_to_num', array2, True, -2000, 10000, -10000) + OpArgMngr.add_workload('nan_to_num', array3, True) + @use_np def _prepare_workloads(): @@ -1358,6 +1376,8 @@ def _prepare_workloads(): _add_workload_shape() _add_workload_diff() _add_workload_resize() + _add_workload_empty_like() + _add_workload_nan_to_num() _prepare_workloads() @@ -1402,7 +1422,7 @@ def check_interoperability(op_list): for name in op_list: if name in _TVM_OPS and not is_op_runnable(): continue - if name in ['shares_memory', 'may_share_memory']: # skip list + if name in ['shares_memory', 'may_share_memory', 'empty_like']: # skip list continue print('Dispatch test:', name) workloads = OpArgMngr.get_workloads(name) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index d019081ec3ee..701dcfff421c 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -17,6 +17,7 @@ # pylint: skip-file from __future__ import absolute_import +from distutils.version import StrictVersion import sys import unittest import itertools @@ -4812,6 +4813,65 @@ def hybrid_forward(self, F, x): assert_almost_equal(y.asnumpy(), expected, use_broadcast=False) +@with_seed() +@use_np +def test_np_empty_like(): + class TestEmptyLike(HybridBlock): + def __init__(self, dtype, order, subok, shape): + super(TestEmptyLike, self).__init__() + self._dtype = dtype + self._order = order + self._subok = subok + self._shape = shape + + def hybrid_forward(self, F, x, *args, **kwargs): + return F.np.empty_like(x, self._dtype, self._order, self._subok, self._shape) + + if StrictVersion(platform.python_version()) < StrictVersion('3.0.0'): + return + + dtypes = [None, 'float16', 'float32', np.int8, np.uint8, np.int32, np.int64, + np.float16, np.float32, np.float64, np.bool_] + shapes = [ + (), + (1,), + (5,), + (4, 3), + (3, 5), + (4, 4), + (4, 5), + (5, 5), + (5, 6), + (6, 6), + (0, 1), + (6, 5, 6), + (2, 3, 3, 4), + (4, 2, 1, 2), + (0, 5, 3, 3), + (5, 0, 3, 3), + (3, 3, 0, 0), + ] + orders = ["C", "F", "A", "K"] + subok_list = [True, False] + flags = [True, False] + _np_version = _np.version.version + for dtype, shape, hybridize, order, subok in itertools.product(dtypes, shapes, flags, orders, subok_list): + prototype = np.random.uniform(low=0, high=100, size=shape, dtype='float64').astype(dtype) + test = TestEmptyLike(dtype, order, subok, shape) + if StrictVersion(_np_version) >= StrictVersion('1.6.0'): + expected_ret = _np.empty_like(prototype, dtype=dtype, order=order, subok=subok) + else: + expected_ret = _np.empty_like(prototype) + if hybridize: + test.hybridize() + ret = test(prototype) + assert ret.asnumpy().shape == expected_ret.shape + + # check imperative again + ret = np.empty_like(prototype, dtype, order, subok, shape) + assert ret.asnumpy().shape == expected_ret.shape + + if __name__ == '__main__': import nose nose.runmodule()