提交 4dee786c authored 作者: James Bergstra's avatar James Bergstra

refactored randomstreams code

上级 337df9be
......@@ -6,7 +6,7 @@ import numpy
from theano.compile import module, In, Component
from theano.gof import Container
from theano.tensor import raw_random, permute_row_elements
from theano.tensor import raw_random
class RandomStreamsInstance(object):
"""RandomStreamsInstance"""
......@@ -86,7 +86,7 @@ class RandomStreamsInstance(object):
return
raise KeyError(item)
class RandomStreams(Component):
class RandomStreams(Component, raw_random.RandomStreamsBase):
"""Module component with similar interface to numpy.random (numpy.random.RandomState)"""
random_state_variables = []
......@@ -147,52 +147,3 @@ class RandomStreams(Component):
self.random_state_variables.append((random_state_variable, new_r))
return out
def binomial(self, *args, **kwargs):
"""Return a symbolic binomial sample
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.binomial, *args, **kwargs)
def uniform(self, *args, **kwargs):
"""Return a symbolic uniform sample
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.uniform, *args, **kwargs)
def normal(self, *args, **kwargs):
"""Return a symbolic normal sample
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.normal, *args, **kwargs)
def random_integers(self, *args, **kwargs):
"""Return a symbolic random integer sample
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.random_integers, *args, **kwargs)
def permutation(self, *args, **kwargs):
"""Return a symbolic permutation of integers
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.permutation, *args, **kwargs)
def multinomial(self, *args, **kwargs):
"""Return a symbolic multinomial sample
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.multinomial, *args, **kwargs)
def shuffle_row_elements(self, input):
"""Return a variable with every row (rightmost index) shuffled"""
perm = self.permutation(input.ndim-1, input.shape[:-1], input.shape[-1])
shuffled = permute_row_elements(input, perm)
return shuffled
......@@ -195,7 +195,7 @@ def _infer_ndim(ndim, shape):
return ndim, v_shape
def uniform(random_state, size=(), low=0.0, high=0.0, ndim=None):
def uniform(random_state, size=(), low=0.0, high=1.0, ndim=None):
"""
Sample from a uniform distribution between low and high.
......@@ -311,7 +311,7 @@ def multinomial(random_state, size=(), n=1, pvals=[0.5, 0.5], ndim=None):
"""
ndim, size = _infer_ndim(ndim, size)
op = RandomFunction('multinomial',
tensor.TensorType(dtype = 'float64', broadcastable = (False,)*(ndim+1)),
tensor.TensorType(dtype = 'int64', broadcastable = (False,)*(ndim+1)),
ndim_added=1)
return op(random_state, size, n, pvals)
......@@ -327,3 +327,90 @@ def random_make_inplace(node):
optdb.register('random_make_inplace', opt.in2out(random_make_inplace, ignore_newtrees=True), 99, 'fast_run', 'inplace')
class RandomStreamsBase(object):
def binomial(self, size=(), n=1, prob=0.5, ndim=None):
"""
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.
"""
return self.gen(binomial, size, n, prob, ndim=ndim)
def uniform(self, size=(), low=0.0, high=1.0, ndim=None):
"""
Sample a tensor of given size whose element 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.
"""
return self.gen(uniform, size, low, high, ndim=ndim)
def normal(self, 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.
"""
return self.gen(normal, size, avg, std, ndim=ndim)
def random_integers(self, 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.
"""
return self.gen(random_integers, size, low, high, ndim=ndim)
def permutation(self, 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.
.. note::
Note that the output will then be of dimension ndim+1.
"""
return self.gen(permutation, size, n, ndim=ndim)
def multinomial(self, 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)).
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.
.. note::
Note that the output will then be of dimension ndim+1.
"""
return self.gen(multinomial, size, n, pvals, ndim=ndim)
def shuffle_row_elements(self, input):
"""Return a variable with every row (rightmost index) shuffled.
This uses permutation random variable internally, available via the ``.permutation``
attribute of the return value.
"""
perm = self.permutation(size=input.shape[:-1], n=input.shape[-1], ndim=input.ndim-1)
shuffled = tensor.permute_row_elements(input, perm)
shuffled.permutation = perm
return shuffled
......@@ -22,7 +22,7 @@ def randomstate_constructor(value, name=None, strict=False):
name=name,
strict=strict)
class RandomStreams(object):
class RandomStreams(raw_random.RandomStreamsBase):
"""Module component with similar interface to numpy.random (numpy.random.RandomState)"""
state_updates = []
......@@ -100,7 +100,6 @@ class RandomStreams(object):
"""
item.value = val
def gen(self, op, *args, **kwargs):
"""Create a new random stream in this container.
......@@ -123,39 +122,3 @@ class RandomStreams(object):
self.state_updates.append(out.update)
return out
def binomial(self, *args, **kwargs):
"""Return a symbolic binomial sample
*args and **kwargs will be passed to numpy.random.RandomState.binomial
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.binomial, *args, **kwargs)
def uniform(self, *args, **kwargs):
"""Return a symbolic uniform sample
*args and **kwargs will be passed to numpy.random.RandomState.uniform
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.uniform, *args, **kwargs)
def normal(self, *args, **kwargs):
"""Return a symbolic normal sample
*args and **kwargs will be passed to numpy.random.RandomState.normal
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.normal, *args, **kwargs)
def random_integers(self, *args, **kwargs):
"""Return a symbolic random integer sample
*args and **kwargs will be passed to numpy.random.RandomState.random_integers
This is a shortcut for a call to `self.gen`
"""
return self.gen(raw_random.random_integers, *args, **kwargs)
......@@ -109,12 +109,18 @@ class T_RandomStreams(unittest.TestCase):
out = m.random.uniform((2,2))
m.fn = Method([], out)
made = m.make()
made.random.initialize(seed=789)
#as a distraction, install various seeds
made.random.initialize(seed=789)
made.random.seed(888)
rng = numpy.random.RandomState(823874)
made.random[out.rng] = numpy.random.RandomState(823874)
# then replace the rng of the stream we care about via setitem
realseed = 823874
rng = numpy.random.RandomState(realseed)
made.random[out.rng] = numpy.random.RandomState(realseed)
print made.fn()
print rng.uniform(size=(2,2))
fn_val0 = made.fn()
fn_val1 = made.fn()
......@@ -153,7 +159,7 @@ class T_RandomStreams(unittest.TestCase):
# ndim specified, consistent with shape, OK
m2 = Module()
m2.random = RandomStreams(234)
m2.fn = Method([], m2.random.uniform(2, (2,2)))
m2.fn = Method([], m2.random.uniform((2,2), ndim=2))
made2 = m2.make()
made2.random.initialize()
......@@ -164,7 +170,7 @@ class T_RandomStreams(unittest.TestCase):
# ndim specified, inconsistent with shape, should raise ValueError
m3 = Module()
m3.random = RandomStreams(234)
m3.fn = Method([], m3.random.uniform(1, (2,2)))
m3.fn = Method([], m3.random.uniform((2,2), ndim=1))
made3 = m3.make()
made3.random.initialize()
self.assertRaises(ValueError, made3.fn)
......
......@@ -11,8 +11,9 @@ from theano import function
from theano import tensor
from theano import compile, gof
from theano.tests import unittest_tools
class T_RandomStreams(unittest.TestCase):
class T_SharedRandomStreams(unittest.TestCase):
def test_tutorial(self):
srng = RandomStreams(seed=234)
......@@ -109,6 +110,96 @@ class T_RandomStreams(unittest.TestCase):
assert numpy.all(fn_val0 == numpy_val0)
assert numpy.all(fn_val1 == numpy_val1)
def test_permutation(self):
"""Test that RandomStreams.uniform generates the same results as numpy"""
# Check over two calls to see if the random state is correctly updated.
random = RandomStreams(234)
fn = function([], random.permutation((20,), 10), updates=random.updates())
fn_val0 = fn()
fn_val1 = fn()
rng_seed = numpy.random.RandomState(234).randint(2**30)
rng = numpy.random.RandomState(int(rng_seed)) #int() is for 32bit
# rng.permutation outputs one vector at a time, so we iterate.
numpy_val0 = numpy.asarray([rng.permutation(10) for i in range(20)])
numpy_val1 = numpy.asarray([rng.permutation(10) for i in range(20)])
assert numpy.all(fn_val0 == numpy_val0)
assert numpy.all(fn_val1 == numpy_val1)
def test_multinomial(self):
"""Test that RandomStreams.multinomial generates the same results as numpy"""
# Check over two calls to see if the random state is correctly updated.
random = RandomStreams(234)
fn = function([], random.multinomial((4,4), 1, [0.1]*10), updates=random.updates())
fn_val0 = fn()
fn_val1 = fn()
rng_seed = numpy.random.RandomState(234).randint(2**30)
rng = numpy.random.RandomState(int(rng_seed)) #int() is for 32bit
numpy_val0 = rng.multinomial(1, [0.1]*10, size=(4,4))
numpy_val1 = rng.multinomial(1, [0.1]*10, size=(4,4))
assert numpy.all(fn_val0 == numpy_val0)
assert numpy.all(fn_val1 == numpy_val1)
def test_shuffle_row_elements(self):
"""Test that RandomStreams.shuffle_row_elements generates the right results"""
# Check over two calls to see if the random state is correctly updated.
# On matrices, for each row, the elements of that row should be shuffled.
# Note that this differs from numpy.random.shuffle, where all the elements
# of the matrix are shuffled.
random = RandomStreams(234)
m_input = tensor.dmatrix()
f = function([m_input], random.shuffle_row_elements(m_input), updates=random.updates())
val_rng = numpy.random.RandomState(unittest_tools.fetch_seed())
in_mval = val_rng.uniform(-2, 2, size=(20,5))
fn_mval0 = f(in_mval)
fn_mval1 = f(in_mval)
print in_mval[0]
print fn_mval0[0]
print fn_mval1[0]
assert not numpy.all(in_mval == fn_mval0)
assert not numpy.all(in_mval == fn_mval1)
assert not numpy.all(fn_mval0 == fn_mval1)
rng_seed = numpy.random.RandomState(234).randint(2**30)
rng = numpy.random.RandomState(int(rng_seed))
numpy_mval0 = in_mval.copy()
numpy_mval1 = in_mval.copy()
for row in numpy_mval0:
rng.shuffle(row)
for row in numpy_mval1:
rng.shuffle(row)
assert numpy.all(numpy_mval0 == fn_mval0)
assert numpy.all(numpy_mval1 == fn_mval1)
# On vectors, the behaviour is the same as numpy.random.shuffle,
# except that it does not work in place, but returns a shuffled vector.
random1 = RandomStreams(234)
v_input = tensor.dvector()
f1 = function([v_input], random1.shuffle_row_elements(v_input))
in_vval = val_rng.uniform(-3, 3, size=(12,))
fn_vval = f1(in_vval)
numpy_vval = in_vval.copy()
vrng = numpy.random.RandomState(int(rng_seed))
vrng.shuffle(numpy_vval)
print in_vval
print fn_vval
print numpy_vval
assert numpy.all(numpy_vval == fn_vval)
# Trying to shuffle a vector with function that should shuffle
# matrices, or vice versa, raises a TypeError
self.assertRaises(TypeError, f1, in_mval)
self.assertRaises(TypeError, f, in_vval)
if __name__ == '__main__':
from theano.tests import main
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论