diff --git a/python/mxnet/ndarray/numpy/random.py b/python/mxnet/ndarray/numpy/random.py index be918615bfd9..71707d41c8e8 100644 --- a/python/mxnet/ndarray/numpy/random.py +++ b/python/mxnet/ndarray/numpy/random.py @@ -20,7 +20,71 @@ from ...context import current_context from . import _internal as _npi -__all__ = ['uniform'] + +__all__ = ['randint', 'uniform'] + + +def randint(low, high=None, size=None, dtype=None, **kwargs): + """Return random integers from `low` (inclusive) to `high` (exclusive). + + Return random integers from the "discrete uniform" distribution of + the specified dtype in the "half-open" interval [`low`, `high`). If + `high` is None (the default), then results are from [0, `low`). + + Parameters + ---------- + low : int + Lowest (signed) integer to be drawn from the distribution (unless + ``high=None``, in which case this parameter is one above the + *highest* such integer). + high : int, optional + If provided, one above the largest (signed) integer to be drawn + from the distribution (see above for behavior if ``high=None``). + size : int or tuple of ints, optional + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + dtype : dtype, optional + Desired dtype of the result. All dtypes are determined by their + name, i.e., 'int64', 'int', etc, so byteorder is not available + and a specific precision may have different C types depending + on the platform. The default value is 'np.int'. + ctx : Context, optional + Device context of output. Default is current context. + out : ndarray, optional + The output ndarray (default is `None`). + + Returns + ------- + out : ndarray of ints + `size`-shaped array of random integers from the appropriate + distribution, or a single such random int if `size` not provided. + + Examples + -------- + >>> np.random.randint(2, size=10) + array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0]) + >>> np.random.randint(1, size=10) + array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + Generate a 2 x 4 array of ints between 0 and 4, inclusive: + + >>> np.random.randint(5, size=(2, 4)) + array([[4, 0, 2, 1], + [3, 2, 2, 0]]) + """ + ctx = kwargs.pop('ctx', None) + out = kwargs.pop('out', None) + if dtype is None: + dtype = 'int' + if ctx is None: + ctx = current_context() + if size is None: + size = 1 + if high is None: + high = low + low = 0 + return _npi.random_randint(low, high, shape=size, dtype=dtype, ctx=ctx, out=out) def uniform(low=0.0, high=1.0, size=None, dtype=None, ctx=None, out=None): diff --git a/python/mxnet/numpy/random.py b/python/mxnet/numpy/random.py index f85936345b7f..f0fd43eb0e70 100644 --- a/python/mxnet/numpy/random.py +++ b/python/mxnet/numpy/random.py @@ -20,7 +20,60 @@ from __future__ import absolute_import from ..ndarray import numpy as _mx_nd_np -__all__ = ['uniform'] + +__all__ = ["randint", "uniform"] + + +def randint(low, high=None, size=None, dtype=None, **kwargs): + """Return random integers from `low` (inclusive) to `high` (exclusive). + + Return random integers from the "discrete uniform" distribution of + the specified dtype in the "half-open" interval [`low`, `high`). If + `high` is None (the default), then results are from [0, `low`). + + Parameters + ---------- + low : int + Lowest (signed) integer to be drawn from the distribution (unless + ``high=None``, in which case this parameter is one above the + *highest* such integer). + high : int, optional + If provided, one above the largest (signed) integer to be drawn + from the distribution (see above for behavior if ``high=None``). + size : int or tuple of ints, optional + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + dtype : dtype, optional + Desired dtype of the result. All dtypes are determined by their + name, i.e., 'int64', 'int', etc, so byteorder is not available + and a specific precision may have different C types depending + on the platform. The default value is 'np.int'. + ctx : Context, optional + Device context of output. Default is current context. + out : ndarray, optional + The output ndarray (default is `None`). + + Returns + ------- + out : ndarray of ints + `size`-shaped array of random integers from the appropriate + distribution, or a single such random int if `size` not provided. + + Examples + -------- + >>> np.random.randint(2, size=10) + array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0]) + >>> np.random.randint(1, size=10) + array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + Generate a 2 x 4 array of ints between 0 and 4, inclusive: + + >>> np.random.randint(5, size=(2, 4)) + array([[4, 0, 2, 1], + [3, 2, 2, 0]]) + """ + return _mx_nd_np.random.randint(low, high, size, dtype, **kwargs) def uniform(low=0.0, high=1.0, size=None, dtype=None, ctx=None, out=None): diff --git a/python/mxnet/symbol/numpy/random.py b/python/mxnet/symbol/numpy/random.py index 338a5e28be4e..86d0ba3095e1 100644 --- a/python/mxnet/symbol/numpy/random.py +++ b/python/mxnet/symbol/numpy/random.py @@ -21,7 +21,71 @@ from ...context import current_context from . import _internal as _npi -__all__ = ['uniform'] + +__all__ = ['randint', 'uniform'] + + +def randint(low, high=None, size=None, dtype=None, **kwargs): + """Return random integers from `low` (inclusive) to `high` (exclusive). + + Return random integers from the "discrete uniform" distribution of + the specified dtype in the "half-open" interval [`low`, `high`). If + `high` is None (the default), then results are from [0, `low`). + + Parameters + ---------- + low : int + Lowest (signed) integer to be drawn from the distribution (unless + ``high=None``, in which case this parameter is one above the + *highest* such integer). + high : int, optional + If provided, one above the largest (signed) integer to be drawn + from the distribution (see above for behavior if ``high=None``). + size : int or tuple of ints, optional + Output shape. If the given shape is, e.g., ``(m, n, k)``, then + ``m * n * k`` samples are drawn. Default is None, in which case a + single value is returned. + dtype : dtype, optional + Desired dtype of the result. All dtypes are determined by their + name, i.e., 'int64', 'int', etc, so byteorder is not available + and a specific precision may have different C types depending + on the platform. The default value is 'np.int'. + ctx : Context, optional + Device context of output. Default is current context. + out : symbol, optional + The output symbol (default is `None`). + + Returns + ------- + out : symbol + `size`-shaped array of random integers from the appropriate + distribution, or a single such random int if `size` not provided. + + Examples + -------- + >>> np.random.randint(2, size=10) + array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0]) + >>> np.random.randint(1, size=10) + array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + + Generate a 2 x 4 array of ints between 0 and 4, inclusive: + + >>> np.random.randint(5, size=(2, 4)) + array([[4, 0, 2, 1], + [3, 2, 2, 0]]) + """ + ctx = kwargs.pop('ctx', None) + out = kwargs.pop('out', None) + if dtype is None: + dtype = 'int' + if ctx is None: + ctx = current_context() + if size is None: + size = 1 + if high is None: + high = low + low = 0 + return _npi.random_randint(low, high, shape=size, dtype=dtype, ctx=ctx, out=out) def uniform(low=0.0, high=1.0, size=None, dtype=None, ctx=None, out=None): diff --git a/src/operator/random/sample_op.cc b/src/operator/random/sample_op.cc index 543146257ddf..d5b5e288258c 100644 --- a/src/operator/random/sample_op.cc +++ b/src/operator/random/sample_op.cc @@ -182,6 +182,7 @@ Example:: MXNET_OPERATOR_REGISTER_SAMPLE(_random_randint, SampleRandIntParam) .add_alias("random_randint") +.add_alias("_npi_random_randint") .describe(R"code(Draw random samples from a discrete uniform distribution. Samples are uniformly distributed over the half-open interval *[low, high)* diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 6a1a6a6dfd5d..38fb52809fbe 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -28,6 +28,8 @@ from common import assertRaises, with_seed import random import collections +import scipy.stats as ss +from mxnet.test_utils import verify_generator, gen_buckets_probs_with_ppf, retry @with_seed() @@ -931,6 +933,50 @@ def hybrid_forward(self, F, a, *args): assert same(mx_out.asnumpy(), np_out) +@with_seed() +@use_np +def test_np_randint(): + ctx = mx.context.current_context() + # test shapes + params = [ + (0, 10), + (5, None) + ] + shapes = [ + (3, 3), + (3, 4), + (0, 0), + (3, 3, 3), + (0, 0, 0), + (2, 2, 4, 3), + (2, 2, 4, 3), + (2, 0, 3, 0), + (2, 0, 2, 3) + ] + for shape in shapes: + for (low, high) in params: + data_mx = np.random.randint(low, high, size=shape) + assert data_mx.shape == shape + + # test generator + for dtype in ['int32', 'int64']: + for low, high in [(50000000, 50001000),(-50000100,-50000000),(-500,199)]: + scale = high - low + buckets, probs = gen_buckets_probs_with_ppf(lambda x: ss.uniform.ppf(x, loc=low, scale=scale), 5) + # Quantize bucket boundaries to reflect the actual dtype and adjust probs accordingly + buckets = _np.array(buckets, dtype=dtype).tolist() + probs = [(buckets[i][1] - buckets[i][0]) / float(scale) for i in range(5)] + generator_mx = lambda x: np.random.randint(low, high, size=x, dtype=dtype, ctx=ctx).asnumpy() + verify_generator(generator=generator_mx, buckets=buckets, probs=probs, nrepeat=100) + # Scipy uses alpha = 0.01 for testing discrete distribution generator but we are using default alpha=0.05 (higher threshold ensures robustness) + # Refer - https://github.com/scipy/scipy/blob/9f12af697763fb5f9767d5cb1280ce62456a3974/scipy/stats/tests/test_discrete_basic.py#L45 + generator_mx_same_seed = \ + lambda x: _np.concatenate( + [np.random.randint(low, high, size=x // 10, dtype=dtype, ctx=ctx).asnumpy() + for _ in range(10)]) + verify_generator(generator=generator_mx_same_seed, buckets=buckets, probs=probs, nrepeat=100) + + if __name__ == '__main__': import nose nose.runmodule()