From 4a1a59576cf1bcac54b565bfd8ca2ec6d7d4d550 Mon Sep 17 00:00:00 2001 From: Ying Date: Wed, 3 Jul 2019 18:41:40 +0800 Subject: [PATCH] numpy operator around * change the name of argument * add doc in three files and fix some bug * change the data type in .h and add test function cancel optimization when abs(temp) < 0.5 modify test on cpu and add test on gpu do not support float16 edit testcase on gpu and add 'Do not support float16 on doc' * edit doc: support scalar * adjust the format * add license * fix format error * delete gpu test * move around to np_elemwise_unary_op_basic * edit AroundOpType * replace int kernel with identity_with_cast and fix format error * delete unused req_type --- python/mxnet/ndarray/numpy/_op.py | 59 ++++++++++++- python/mxnet/numpy/multiarray.py | 54 +++++++++++- python/mxnet/symbol/numpy/_symbol.py | 45 +++++++++- .../numpy/np_elemwise_unary_op_basic.cc | 34 ++++++++ .../numpy/np_elemwise_unary_op_basic.cu | 3 + src/operator/tensor/elemwise_unary_op.h | 83 +++++++++++++++++++ tests/python/gpu/test_operator_gpu.py | 2 +- tests/python/unittest/test_numpy_op.py | 32 +++++++ 8 files changed, 308 insertions(+), 4 deletions(-) diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index 7a5cf37403b4..2cdfff173b8e 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -34,7 +34,7 @@ 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'vstack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign', - 'ravel', 'hanning', 'hamming', 'blackman', 'flip'] + 'ravel', 'hanning', 'hamming', 'blackman', 'flip', 'around'] @set_module('mxnet.ndarray.numpy') @@ -2896,3 +2896,60 @@ def flip(m, axis=None, out=None): return _npi.flip(m, axis, out=out) else: raise TypeError('type {} not supported'.format(str(type(m)))) + + +@set_module('mxnet.ndarray.numpy') +def around(x, decimals=0, out=None, **kwargs): + r""" + around(x, decimals=0, out=None) + + Evenly round to the given number of decimals. + Parameters + ---------- + x : ndarray or scalar + Input data. + decimals : int, optional + Number of decimal places to round to (default: 0). If + decimals is negative, it specifies the number of positions to + the left of the decimal point. + out : ndarray, optional + Alternative output array in which to place the result. It must have + the same shape and type as the expected output. + + Returns + ------- + rounded_array : ndarray or scalar + An array of the same type as `x`, containing the rounded values. + A reference to the result is returned. + + Notes + ----- + For values exactly halfway between rounded decimal values, NumPy + rounds to the nearest even value. Thus 1.5 and 2.5 round to 2.0, + -0.5 and 0.5 round to 0.0, etc. + + This function differs from the original numpy.prod in the following aspects: + + - Cannot cast type automatically. Dtype of `out` must be same as the expected one. + - Cannot support complex-valued number. + + Examples + -------- + >>> np.around([0.37, 1.64]) + array([ 0., 2.]) + >>> np.around([0.37, 1.64], decimals=1) + array([ 0.4, 1.6]) + >>> np.around([.5, 1.5, 2.5, 3.5, 4.5]) # rounds to nearest even value + array([ 0., 2., 2., 4., 4.]) + >>> np.around([1, 2, 3, 11], decimals=1) # ndarray of ints is returned + array([ 1, 2, 3, 11]) + >>> np.around([1, 2, 3, 11], decimals=-1) + array([ 0, 0, 0, 10]) + """ + from ...numpy import ndarray + if isinstance(x, numeric_types): + return _np.around(x, decimals, **kwargs) + elif isinstance(x, ndarray): + return _npi.around(x, decimals, out=out, **kwargs) + else: + raise TypeError('type {} not supported'.format(str(type(x)))) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 5b387601f59d..e0c7a67d4715 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -53,7 +53,7 @@ 'fix', 'ceil', 'floor', 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'vstack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', - 'copysign', 'ravel', 'hanning', 'hamming', 'blackman', 'flip'] + 'copysign', 'ravel', 'hanning', 'hamming', 'blackman', 'flip', 'around'] # Return code for dispatching indexing function call _NDARRAY_UNSUPPORTED_INDEXING = -1 @@ -4429,3 +4429,55 @@ def flip(m, axis=None, out=None): [3, 2]]]) """ return _mx_nd_np.flip(m, axis, out=out) + + +@set_module('mxnet.numpy') +def around(x, decimals=0, out=None, **kwargs): + r""" + around(x, decimals=0, out=None) + + Evenly round to the given number of decimals. + + Parameters + ---------- + x : ndarray or scalar + Input data. + decimals : int, optional + Number of decimal places to round to (default: 0). If + decimals is negative, it specifies the number of positions to + the left of the decimal point. + out : ndarray, optional + Alternative output array in which to place the result. It must have + the same shape and type as the expected output. + + Returns + ------- + rounded_array : ndarray or scalar + An array of the same type as `x`, containing the rounded values. + A reference to the result is returned. + + Notes + ----- + For values exactly halfway between rounded decimal values, NumPy + rounds to the nearest even value. Thus 1.5 and 2.5 round to 2.0, + -0.5 and 0.5 round to 0.0, etc. + + This function differs from the original numpy.prod in the following aspects: + + - Cannot cast type automatically. Dtype of `out` must be same as the expected one. + - Cannot support complex-valued number. + + Examples + -------- + >>> np.around([0.37, 1.64]) + array([ 0., 2.]) + >>> np.around([0.37, 1.64], decimals=1) + array([ 0.4, 1.6]) + >>> np.around([.5, 1.5, 2.5, 3.5, 4.5]) # rounds to nearest even value + array([ 0., 2., 2., 4., 4.]) + >>> np.around([1, 2, 3, 11], decimals=1) # ndarray of ints is returned + array([ 1, 2, 3, 11]) + >>> np.around([1, 2, 3, 11], decimals=-1) + array([ 0, 0, 0, 10]) + """ + return _mx_nd_np.around(x, decimals, out=out, **kwargs) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index dc2c84da167d..1bfbba209989 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -36,7 +36,7 @@ 'trunc', 'logical_not', 'arcsinh', 'arccosh', 'arctanh', 'tensordot', 'linspace', 'expand_dims', 'tile', 'arange', 'split', 'concatenate', 'stack', 'vstack', 'mean', 'maximum', 'minimum', 'swapaxes', 'clip', 'argmax', 'std', 'var', 'indices', 'copysign', - 'ravel', 'hanning', 'hamming', 'blackman', 'flip'] + 'ravel', 'hanning', 'hamming', 'blackman', 'flip', 'around'] def _num_outputs(sym): @@ -3129,4 +3129,47 @@ def flip(m, axis=None, out=None): raise TypeError('type {} not supported'.format(str(type(m)))) +@set_module('mxnet.symbol.numpy') +def around(x, decimals=0, out=None, **kwargs): + r""" + around(x, decimals=0, out=None) + + Evenly round to the given number of decimals. + Parameters + ---------- + x : _Symbol or scalar + Input data. + decimals : int, optional + Number of decimal places to round to (default: 0). If + decimals is negative, it specifies the number of positions to + the left of the decimal point. + out : _Symbol, optional + Alternative output array in which to place the result. It must have + the same shape and type as the expected output. + + Returns + ------- + rounded_array : _Symbol or scalar + An array of the same type as `x`, containing the rounded values. + A reference to the result is returned. + + Notes + ----- + For values exactly halfway between rounded decimal values, NumPy + rounds to the nearest even value. Thus 1.5 and 2.5 round to 2.0, + -0.5 and 0.5 round to 0.0, etc. + + This function differs from the original numpy.prod in the following aspects: + + - Cannot cast type automatically. Dtype of `out` must be same as the expected one. + - Cannot support complex-valued number. + """ + if isinstance(x, numeric_types): + return _np.around(x, decimals, **kwargs) + elif isinstance(x, _Symbol): + return _npi.around(x, decimals, out=out, **kwargs) + else: + raise TypeError('type {} not supported'.format(str(type(x)))) + + _set_np_symbol_class(_Symbol) diff --git a/src/operator/numpy/np_elemwise_unary_op_basic.cc b/src/operator/numpy/np_elemwise_unary_op_basic.cc index b5764a7fbb4b..b454d89212e2 100644 --- a/src/operator/numpy/np_elemwise_unary_op_basic.cc +++ b/src/operator/numpy/np_elemwise_unary_op_basic.cc @@ -362,5 +362,39 @@ computed element-wise. )code" ADD_FILELINE) .set_attr("FGradient", ElemwiseGradUseIn{ "_backward_arctanh" }); +inline bool AroundOpType(const nnvm::NodeAttrs& attrs, + std::vector* in_attrs, + std::vector* out_attrs) { + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), 1U); + + TYPE_ASSIGN_CHECK(*out_attrs, 0, in_attrs->at(0)); + TYPE_ASSIGN_CHECK(*in_attrs, 0, out_attrs->at(0)); + + CHECK(in_attrs->at(0) != mshadow::kFloat16) << "Do not support `float16` as input.\n"; + return out_attrs->at(0) != -1; +} + +DMLC_REGISTER_PARAMETER(AroundParam); + +NNVM_REGISTER_OP(_npi_around) +.set_attr_parser(ParamParser) +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + return std::vector{"x"}; + }) +.set_attr("FInferShape", ElemwiseShape<1, 1>) +.set_attr("FInferType", AroundOpType) +.set_attr("FCompute", AroundOpForward) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 0}}; + }) +.add_argument("x", "NDArray-or-Symbol", "Input ndarray") +.add_arguments(AroundParam::__FIELDS__()) +.set_attr("FGradient", MakeZeroGradNodes); + } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/np_elemwise_unary_op_basic.cu b/src/operator/numpy/np_elemwise_unary_op_basic.cu index cb8bb771b27d..7f68386560f3 100644 --- a/src/operator/numpy/np_elemwise_unary_op_basic.cu +++ b/src/operator/numpy/np_elemwise_unary_op_basic.cu @@ -106,5 +106,8 @@ MXNET_OPERATOR_REGISTER_NUMPY_UNARY_GPU(_npi_arccosh, mshadow_op::arccosh); MXNET_OPERATOR_REGISTER_NUMPY_UNARY_GPU(_npi_arctanh, mshadow_op::arctanh); +NNVM_REGISTER_OP(_npi_around) +.set_attr("FCompute", AroundOpForward); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_unary_op.h b/src/operator/tensor/elemwise_unary_op.h index 87964ac246f0..b50f89b8c7fa 100644 --- a/src/operator/tensor/elemwise_unary_op.h +++ b/src/operator/tensor/elemwise_unary_op.h @@ -560,6 +560,89 @@ struct ReshapeLikeParam : public dmlc::Parameter { } }; +struct AroundParam : public dmlc::Parameter { + int decimals; + DMLC_DECLARE_PARAMETER(AroundParam) { + DMLC_DECLARE_FIELD(decimals) + .set_default(0) + .describe("Number of decimal places to round to."); + } +}; + +template +struct around_forward { + template + MSHADOW_XINLINE static void Map(int i, DType* out_data, const DType* in_data, + const int decimals) { + int d = 0; + DType temp = in_data[i]; + DType roundtemp; + while (d != decimals) { + if (decimals > 0) { + d++; + temp *= 10; + } else { + d--; + temp /= 10; + } + } + roundtemp = (DType)round(static_cast(temp)); + // If temp is x.5 and roundtemp is odd number, decrease or increase roundtemp by 1. + // For example, in numpy, around(0.5) should be 0 but in c, round(0.5) is 1. + if (roundtemp - temp == 0.5 && (static_cast(roundtemp)) % 2 != 0) { + roundtemp -= 1; + } else if (temp - roundtemp == 0.5 && (static_cast(roundtemp)) % 2 != 0) { + roundtemp += 1; + } + while (d != 0) { + if (roundtemp == 0) { + break; + } + if (decimals > 0) { + d--; + roundtemp /= 10; + } else { + d++; + roundtemp *= 10; + } + } + KERNEL_ASSIGN(out_data[i], req, roundtemp); + } +}; + +template +void AroundOpForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + CHECK_EQ(inputs.size(), 1U); + CHECK_EQ(outputs.size(), 1U); + CHECK_EQ(req.size(), 1U); + mshadow::Stream *s = ctx.get_stream(); + const TBlob& in_data = inputs[0]; + const TBlob& out_data = outputs[0]; + const AroundParam& param = nnvm::get(attrs.parsed); + using namespace mxnet_op; + // if the type is uint8, int8, int32 or int64 and decimals is greater than 0 + // we simply return the number back. + if (in_data.type_flag_ >= mshadow::kUint8 && in_data.type_flag_ <= mshadow::kInt64 \ + && param.decimals > 0) { + MSHADOW_TYPE_SWITCH(out_data.type_flag_, DType, { + Kernel::Launch( + s, out_data.Size(), out_data.dptr(), in_data.dptr()); + }); + } else { + MSHADOW_TYPE_SWITCH(out_data.type_flag_, DType, { + MXNET_ASSIGN_REQ_SWITCH(req[0], req_type, { + Kernel, xpu>::Launch( + s, out_data.Size(), out_data.dptr(), in_data.dptr(), + param.decimals); + }); + }); + } +} + /*! \brief Unary compute */ #define MXNET_OPERATOR_REGISTER_UNARY(__name$) \ NNVM_REGISTER_OP(__name$) \ diff --git a/tests/python/gpu/test_operator_gpu.py b/tests/python/gpu/test_operator_gpu.py index c4d96fb550c0..e3862abbebd1 100644 --- a/tests/python/gpu/test_operator_gpu.py +++ b/tests/python/gpu/test_operator_gpu.py @@ -2346,7 +2346,7 @@ def test_arange_like_dtype(): out = mod.forward(is_train=False) for v in out: assert v.dtype == t - + if __name__ == '__main__': import nose diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 95140a69ab45..1324af03d1b5 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -2348,6 +2348,38 @@ def hybrid_forward(self, F, x): assert_almost_equal(mx_out.asnumpy(), np_out, rtol=rtol, atol=atol) +@with_seed() +@use_np +def test_np_around(): + class TestAround(HybridBlock): + def __init__(self, decimals): + super(TestAround, self).__init__() + self.decimals = decimals + + def hybrid_forward(self, F, x): + return F.np.around(x, self.decimals) + + shapes = [(), (1, 2, 3), (1, 0)] + types = ['int32', 'int64', 'float32', 'float64'] + for hybridize in [True, False]: + for oneType in types: + rtol, atol = 1e-3, 1e-5 + for shape in shapes: + for d in range(-5, 6): + test_around = TestAround(d) + if hybridize: + test_around.hybridize() + x = rand_ndarray(shape, dtype=oneType).as_np_ndarray() + np_out = _np.around(x.asnumpy(), d) + mx_out = test_around(x) + assert mx_out.shape == np_out.shape + assert_almost_equal(mx_out.asnumpy(), np_out, rtol=rtol, atol=atol) + + mx_out = np.around(x, d) + np_out = _np.around(x.asnumpy(), d) + assert_almost_equal(mx_out.asnumpy(), np_out, rtol=rtol, atol=atol) + + if __name__ == '__main__': import nose nose.runmodule()