提交 1de3484e authored 作者: James Bergstra's avatar James Bergstra

removed random_function from raw_random

上级 019b7039
......@@ -50,7 +50,7 @@ class RandomFunction(gof.Op):
"""
def __init__(self, fn, outtype, *args, **kwargs):
def __init__(self, fn, outtype, inplace=False, ndim_added=0 ):
"""
:param fn: a member function of numpy.RandomState
Technically, any function with a signature like the ones in numpy.random.RandomState
......@@ -72,19 +72,18 @@ class RandomFunction(gof.Op):
addition to the shape's dimensions (used in multinomial and
permutation).
"""
self.__setstate__([fn, outtype, args, kwargs])
self.__setstate__([fn, outtype, inplace, ndim_added])
def __eq__(self, other):
return type(self) == type(other) \
and self.fn == other.fn\
and self.outtype == other.outtype\
and self.args == other.args\
and self.inplace == other.inplace\
and self.ndim_added == other.ndim_added
def __hash__(self):
return hash(type(self)) ^ hash(self.fn) \
^ hash(self.outtype) ^ hash(self.args)\
^ hash(self.outtype) \
^ hash(self.inplace) ^ hash(self.ndim_added)
def __getstate__(self):
......@@ -92,7 +91,7 @@ class RandomFunction(gof.Op):
def __setstate__(self, state):
self.state = state
fn, outtype, args, kwargs = state
fn, outtype, inplace, ndim_added = state
if isinstance(fn, str):
self.fn = getattr(numpy.random.RandomState, fn)
else:
......@@ -100,11 +99,10 @@ class RandomFunction(gof.Op):
#backport
#self.fn = getattr(numpy.random.RandomState, fn) if isinstance(fn, str) else fn
self.outtype = outtype
self.args = tuple(tensor.as_tensor_variable(arg) for arg in args)
self.inplace = kwargs.pop('inplace', False)
self.inplace = inplace
if self.inplace:
self.destroy_map = {0: [0]}
self.ndim_added = kwargs.pop('ndim_added', 0)
self.ndim_added = ndim_added
def make_node(self, r, shape, *args):
"""
......@@ -147,29 +145,9 @@ class RandomFunction(gof.Op):
# convert args to TensorType instances
# and append enough None's to match the length of self.args
args = map(tensor.as_tensor_variable, args)
if len(args) > len(self.args):
raise TypeError('Too many args for this kind of random generator')
args += (None,) * (len(self.args) - len(args))
assert len(args) == len(self.args)
# build the inputs to this Apply by overlaying args on self.args
inputs = []
for arg, default in zip(args, self.args):
# The NAACL test is failing because of this assert.
# I am commenting out the requirement that the dtypes match because it doesn't seem
# to me to be necessary (although I agree it is typically true).
# -JB 20090819
#assert arg is None or default.type.dtype == arg.type.dtype
if arg is None:
input = default
else:
input = arg
#backport
#input = default if arg is None else arg
inputs.append(input)
return gof.Apply(self,
[r, shape] + inputs,
[r, shape] + args,
[r.type(), self.outtype()])
def perform(self, node, inputs, (rout, out)):
......@@ -198,102 +176,80 @@ class RandomFunction(gof.Op):
def grad(self, inputs, outputs):
return [None for i in inputs]
def _infer_ndim(ndim, shape):
"""returns int, variable pair, such that the int is the length of the variable, and the
variable is an integer or uint vector
"""
if isinstance(shape, (tuple, list)):
v_shape = tensor.TensorConstant(type=tensor.lvector, data=numpy.asarray(shape, dtype='int64'))
else:
v_shape = tensor.as_tensor_variable(shape)
__oplist_constructor_list = []
"""List of functions to be listed as op constructors in the oplist (`gen_oplist`, doc/oplist.txt)."""
def constructor(f):
"""Add `f` to :doc:`oplist`.
if not (v_shape.dtype.startswith('int') or v_shape.dtype.startswith('uint')):
raise TypeError('shape must be an integer vector or list')
if ndim is None:
#infer ndim
ndim = tensor.get_vector_length(v_shape)
Make `f` appear as a constructor in the oplist (`gen_oplist`, doc/oplist.txt).
return ndim, v_shape
def uniform(random_state, size=(), low=0.0, high=0.0, ndim=None):
"""
__oplist_constructor_list.append(f)
return f
def __oplist_tag(thing, tag):
tags = getattr(thing, '__oplist_tags', [])
tags.append(tag)
thing.__oplist_tags = tags
def random_function(fn, dtype, *rfargs, **rfkwargs):
Sample from a uniform distribution between low and high.
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
Returns a wrapper around RandomFunction which automatically infers the number
of dimensions of the output from the given shape. If the shape cannot be inferred,
the user can give an integer as first argument, which will be interpreted as the
number of dimensions.
If the distribution is not scalar (e.g., a multinomial), the output will have
more dimensions than what the shape argument suggests. The "ndim_added" keyword
arguments allows to specify how many dimensions to add (for a multinomial, 1).
The number of dimensions for the following shape arguments can be inferred:
- shape(x)
- make_lvector(x, y, z, ...)
- constants
ndim, size = _infer_ndim(ndim, size)
op = RandomFunction('uniform',
tensor.TensorType(dtype = 'float64', broadcastable = (False,)*ndim) )
return op(random_state, size, low, high)
def binomial(random_state, size=(), n=1, prob=0.5, ndim=None):
"""
@constructor
def f(r, ndim, *args, **kwargs):
if isinstance(ndim, int):
shape, args = args[0], args[1:]
else:
shape = ndim
if shape == () or shape == []:
shape = tensor.TensorConstant(type = tensor.lvector, data = shape)
else:
shape = tensor.as_tensor_variable(shape)
ndim = tensor.get_vector_length(shape)
if ndim is None:
raise ValueError('Cannot infer the number of dimensions from the shape argument.')
# note: rf could be cached for future use
ndim_added = rfkwargs.get('ndim_added', 0)
ndim += ndim_added
rf = RandomFunction(fn, tensor.TensorType(dtype = dtype, broadcastable = (False,)*ndim), *rfargs, **rfkwargs)
return rf(r, shape, *args, **kwargs)
return f
# we need to provide defaults for all the functions in order to infer the argument types...
uniform = random_function('uniform', 'float64', 0.0, 1.0)
uniform.__doc__ = """
Usage: uniform(random_state, size, low=0.0, high=1.0)
Sample from a uniform distribution between low and high.
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
binomial = random_function('binomial', 'int64', 1, 0.5)
binomial.__doc__ = """
Usage: binomial(random_state, size, n=1, prob=0.5)
Sample n times with probability of success prob for each trial,
return the number of successes.
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
normal = random_function('normal', 'float64', 0.0, 1.0)
normal.__doc__ = """
Usage: normal(random_state, size, avg=0.0, std=1.0)
Sample from a normal distribution centered on avg with
the specified standard deviation (std)
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
random_integers = random_function('random_integers', 'int64', 0, 1)
random_integers.__doc__ = """
Usage: random_integers(random_state, size, low=0, high=1)
Sample a random integer between low and high, both inclusive.
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
Sample n times with probability of success prob for each trial, return the number of
successes.
If the size argument is ambiguous on the number of dimensions, the first argument may be a
plain integer to supplement the missing information.
"""
ndim, size = _infer_ndim(ndim, size)
op = RandomFunction('binomial',
tensor.TensorType(dtype = 'int64', broadcastable = (False,)*ndim) )
return op(random_state, size, n, prob)
def normal(random_state, size=(), avg=0.0, std=1.0, ndim=None):
"""
Usage: normal(random_state, size,
Sample from a normal distribution centered on avg with
the specified standard deviation (std)
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
ndim, size = _infer_ndim(ndim, size)
op = RandomFunction('normal',
tensor.TensorType(dtype = 'float64', broadcastable = (False,)*ndim) )
return op(random_state, size, avg, std)
def random_integers(random_state, size=(), low=0, high=1, ndim=None):
"""
Usage: random_integers(random_state, size, low=0, high=1)
Sample a random integer between low and high, both inclusive.
If the size argument is ambiguous on the number of
dimensions, the first argument may be a plain integer
to supplement the missing information.
"""
ndim, size = _infer_ndim(ndim, size)
op = RandomFunction('random_integers',
tensor.TensorType(dtype = 'int64', broadcastable = (False,)*ndim) )
return op(random_state, size, low, high)
def permutation_helper(random_state, n, shape):
"""Helper function to generate permutations from integers.
......@@ -316,41 +272,56 @@ def permutation_helper(random_state, n, shape):
out = numpy.zeros(out_shape, int)
for i in numpy.ndindex(*shape):
out[i] = random_state.permutation(n)
print 'RETURNING', out.shape
return out
permutation = random_function(permutation_helper, 'int64', 1, ndim_added=1)
permutation.__doc__ = """
Usage: permutation(random_state, size, n)
Returns permutations of the integers between 0 and n-1, as many times
as required by size. For instance, if size=(p,q), p*q permutations
will be generated, and the output shape will be (p,q,n), because each
permutation is of size n.
If the size argument is ambiguous on the number of dimensions, the first
argument may be a plain integer i, which should correspond to len(size).
Note that the output will then be of dimension i+1.
"""
def permutation(random_state, size=(), n=1, ndim=None):
"""
Returns permutations of the integers between 0 and n-1, as many times
as required by size. For instance, if size=(p,q), p*q permutations
will be generated, and the output shape will be (p,q,n), because each
permutation is of size n.
Theano tries to infer the number of dimensions from the length of the size argument, but you
may always specify it with the `ndim` parameter.
multinomial = random_function('multinomial', 'float64', 1, [0.5, 0.5], ndim_added=1)
multinomial.__doc__ = """
Usage: multinomial(random_state, size, pvals)
.. note::
Note that the output will then be of dimension ndim+1.
"""
ndim, size = _infer_ndim(ndim, size)
print "NDIM", ndim, size
op = RandomFunction(permutation_helper,
tensor.TensorType(dtype='int64', broadcastable=(False,)*(ndim+1)),
ndim_added=1)
return op(random_state, size, n)
def multinomial(random_state, size=(), n=1, pvals=[0.5, 0.5], ndim=None):
"""
Sample n times from a multinomial distribution defined by probabilities pvals,
as many times as required by size. For instance, if size=(p,q), p*q
samples will be drawn, and the output shape will be (p,q,len(pvals)).
Sample from a multinomial distribution defined by probabilities pvals,
as many times as required by size. For instance, if size=(p,q), p*q
samples will be drawn, and the output shape will be (p,q,len(pvals)).
Theano tries to infer the number of dimensions from the length of the size argument, but you
may always specify it with the `ndim` parameter.
If the size argument is ambiguous on the number of dimensions, the first
argument may be a plain integer i, which should correspond to len(size).
Note that the output will then be of dimension i+1.
"""
.. note::
Note that the output will then be of dimension ndim+1.
"""
ndim, size = _infer_ndim(ndim, size)
op = RandomFunction('multinomial',
tensor.TensorType(dtype = 'float64', broadcastable = (False,)*(ndim+1)),
ndim_added=1)
return op(random_state, size, n, pvals)
@gof.local_optimizer([None])
def random_make_inplace(node):
op = node.op
if isinstance(op, RandomFunction) and not op.inplace:
opkwargs = dict(inplace=True, ndim_added=op.ndim_added)
return RandomFunction(op.fn, op.outtype, *op.args, **opkwargs).make_node(*node.inputs).outputs
new_op = RandomFunction(op.fn, op.outtype, inplace=True, ndim_added=op.ndim_added)
return new_op.make_node(*node.inputs).outputs
return False
optdb.register('random_make_inplace', opt.in2out(random_make_inplace, ignore_newtrees=True), 99, 'fast_run', 'inplace')
......
......@@ -5,6 +5,7 @@ import numpy as N
from theano.tests import unittest_tools
from theano.tensor.raw_random import *
from theano.tensor import raw_random
from theano import tensor
......@@ -12,7 +13,7 @@ from theano import compile, gof
class T_random_function(unittest.TestCase):
def test_basic_usage(self):
rf = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, -2.0, 2.0)
rf = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector)
assert not rf.inplace
assert getattr(rf, 'destroy_map', {}) == {}
......@@ -32,23 +33,21 @@ class T_random_function(unittest.TestCase):
assert numpy.all(f_0 == f_1)
def test_inplace_norun(self):
rf = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, -2.0, 2.0,
inplace=True)
rf = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, inplace=True)
assert rf.inplace
assert getattr(rf, 'destroy_map', {}) != {}
def test_args(self):
"""Test that arguments to RandomFunction are honored"""
rf2 = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, -2.0, 2.0)
rf4 = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, -4.0, 4.0,
inplace=True)
rf2 = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector)
rf4 = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, inplace=True)
rng_R = random_state_type()
# use make_node to override some of the self.args
post_r2, out2 = rf2(rng_R, (4,))
post_r2_4, out2_4 = rf2(rng_R, (4,), -4.0)
post_r2, out2 = rf2(rng_R, (4,), -2, 2)
post_r2_4, out2_4 = rf2(rng_R, (4,), -4.0, 2)
post_r2_4_4, out2_4_4 = rf2(rng_R, (4,), -4.0, 4.0)
post_r4, out4 = rf4(rng_R, (4,))
post_r4, out4 = rf4(rng_R, (4,), -4, 4)
f = compile.function(
[compile.In(rng_R, value=numpy.random.RandomState(55), update=post_r4, mutable=True)],
......@@ -65,7 +64,7 @@ class T_random_function(unittest.TestCase):
def test_inplace_optimization(self):
"""Test that FAST_RUN includes the random_make_inplace optimization"""
#inplace = False
rf2 = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector, -2.0, 2.0)
rf2 = RandomFunction(numpy.random.RandomState.uniform, tensor.dvector)
rng_R = random_state_type()
# use make_node to override some of the self.args
......@@ -92,19 +91,18 @@ class T_random_function(unittest.TestCase):
def test_random_function_ndim(self):
"""Test that random_function helper function accepts ndim as first argument"""
rf2 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0)
rng_R = random_state_type()
# ndim is an optional argument indicating the length of the 'shape'
# ndim not specified, OK
post_out4, out4 = rf2(rng_R, (4,))
post_out4, out4 = uniform(rng_R, (4,))
# ndim specified, consistent with shape, OK
post_out1_4, out1_4 = rf2(rng_R, 1, (4,))
post_out2_4_4, out2_4_4= rf2(rng_R, 2, (4, 4))
post_out1_4, out1_4 = uniform(rng_R, (4,), ndim=1)
post_out2_4_4, out2_4_4= uniform(rng_R, (4, 4), ndim=2)
# ndim specified, but not compatible with shape
post_out2_4, out2_4 = rf2(rng_R, 2, (4,))
post_out2_4, out2_4 = uniform(rng_R, (4,), ndim=2)
f_ok = compile.function(
[compile.In(rng_R, value=numpy.random.RandomState(55), update=post_out2_4_4, mutable=True)],
......@@ -132,18 +130,31 @@ class T_random_function(unittest.TestCase):
# Specifying a different ndim_added will change the Op's output ndim,
# so numpy.uniform will produce a result of incorrect shape,
# and a ValueError should be raised.
uni_1 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0, ndim_added=1)
uni_0 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0, ndim_added=0)
uni_m1 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0, ndim_added=-1)
def ndim_added_deco(ndim_added):
def randomfunction(random_state, size=(), low=0.0, high=0.0, ndim=None):
ndim, size = raw_random._infer_ndim(ndim, size)
op = RandomFunction('uniform',
tensor.TensorType(dtype = 'float64', broadcastable =
(False,)*(ndim+ndim_added)),
ndim_added=ndim_added)
return op(random_state, size, low, high)
return randomfunction
uni_1 = ndim_added_deco(1)
uni_0 = ndim_added_deco(0)
uni_m1 = ndim_added_deco(-1)
#uni_1 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0, ndim_added=1)
#uni_0 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0, ndim_added=0)
#uni_m1 = random_function(numpy.random.RandomState.uniform, 'float64', -2.0, 2.0, ndim_added=-1)
rng_R = random_state_type()
p_uni11, uni11 = uni_1(rng_R, 1, (4,))
p_uni12, uni12 = uni_1(rng_R, 2, (3,4))
p_uni01, uni01 = uni_0(rng_R, 1, (4,))
p_uni02, uni02 = uni_0(rng_R, 2, (3,4))
p_unim11, unim11 = uni_m1(rng_R, 1, (4,))
p_unim12, unim12 = uni_m1(rng_R, 2, (3,4))
p_uni11, uni11 = uni_1(rng_R, size=(4,))
p_uni12, uni12 = uni_1(rng_R, size=(3,4))
p_uni01, uni01 = uni_0(rng_R, size=(4,))
p_uni02, uni02 = uni_0(rng_R, size=(3,4))
p_unim11, unim11 = uni_m1(rng_R, size=(4,))
p_unim12, unim12 = uni_m1(rng_R, size=(3,4))
self.assertEqual(uni11.ndim, 2)
self.assertEqual(uni12.ndim, 3)
......@@ -320,7 +331,8 @@ class T_random_function(unittest.TestCase):
def test_permutation(self):
"""Test that raw_random.permutation generates the same results as numpy."""
rng_R = random_state_type()
post_r, out = permutation(rng_R, (9,), 6)
post_r, out = permutation(rng_R, size=(9,), n=6)
print 'OUT NDIM', out.ndim
f = compile.function(
[compile.In(rng_R, value=numpy.random.RandomState(55), update=post_r, mutable=True)],
[out], accept_inplace=True)
......@@ -365,6 +377,24 @@ class T_random_function(unittest.TestCase):
self.assertTrue(val0.shape == (7,3,5))
self.assertTrue(val1.shape == (7,3,5))
def test_symbolic_shape(self):
rng_R = random_state_type()
shape = tensor.lvector()
post_r, out = uniform(rng_R, shape, ndim=2)
f = compile.function([rng_R, shape], out)
rng_state0 = numpy.random.RandomState(55)
assert f(rng_state0, [2,3]).shape == (2,3)
assert f(rng_state0, [4,8]).shape == (4,8)
self.assertRaises(ValueError, f, rng_state0, [4])
self.assertRaises(ValueError, f, rng_state0, [4,3,4,5])
if __name__ == '__main__':
from theano.tests import main
main("test_raw_random")
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论