提交 a7310185 authored 作者: James Bergstra's avatar James Bergstra

updated randomstreams documentation to work for shared_randomstreams

上级 7a271305
...@@ -6,149 +6,109 @@ Using RandomStreams ...@@ -6,149 +6,109 @@ Using RandomStreams
Since Theano uses a functional design, producing pseudo-random numbers in a Since Theano uses a functional design, producing pseudo-random numbers in a
graph is not quite as straightforward as it is in numpy. But close. If you are using theano's graph is not quite as straightforward as it is in numpy. But close. If you are using theano's
modules, then a RandomStreams object is probably what you want. If you are shared variables, then a RandomStreams object is probably what you want. If you are
using the function interface directly (not Module and Method) then have a look using the function interface directly, or you are using Module then this tutorial will be useful but not exactly what you want.
at the :api:`RandomFunction` Op. Have a look at the :api:`RandomFunction` Op.
I'm going to use the term "random stream" to mean the sequence of random The way to think about putting randomness into theano's computations is to
numbers that comes from a numpy RandomState object (starting from a particular put random variables in your graph. Theano will allocate a numpy RandomState
state). Try to think about putting random variables in your graph, rather than object for each such variable, and draw from it as necessary. I'll call this sort of sequence of
random number generators. random numbers a *random stream*.
Here's a brief example: Brief example
----------------------- -------------
.. code-block:: python
from theano.tensor import RandomStreams
m = Module()
m.random = RandomStreams(seed=234)
rv_u = m.random.uniform((2,2))
rv_n = m.random.normal((2,2))
m.fn = Method([], rv_u)
m.gn = Method([], rv_n)
m.nearly_zeros = Method([], rv_u + rv_u - 2 * rv_u)
made = m.make()
made.random.initialize()
fn_val0 = made.fn()
fn_val1 = made.fn() #different numbers from fn_val0
Here's a brief example. The setup code is:
Let's walk through it. .. code-block:: python
>>> from theano.tensor import RandomStreams from theano.tensor.shared_randomstreams import RandomStreams
>>> m = Module() srng = RandomStreams(seed=234)
>>> m.random = RandomStreams(seed=234) rv_u = srng.random.uniform((2,2))
>>> rv_u = m.random.uniform((2,2)) rv_n = srng.random.normal((2,2))
>>> rv_n = m.random.normal((2,2)) f = function([], rv_u, updates=[rv_u.update])
g = function([], rv_n) #omitting rv_n.update
nearly_zeros = function([], rv_u + rv_u - 2 * rv_u, updates=[rv_u.update])
Here, 'rv_u' represents a random stream of 2x2 matrices of draws from a uniform Here, 'rv_u' represents a random stream of 2x2 matrices of draws from a uniform
distribution. Likewise, 'rv_n' represenents a random stream of 2x2 matrices of distribution. Likewise, 'rv_n' represenents a random stream of 2x2 matrices of
draws from a normal distribution. draws from a normal distribution. The distributions that are implemented are
defined in :ref:`tensor.shared_randomstreams.RandomStreams`.
>>> m.fn = Method([], rv_u) Now let's use these things. If we call f(), we get random uniform numbers.
>>> m.gn = Method([], rv_n) Since we are updating the internal state of the random number generator (via
the ``updates`` argument, we get different random numbers every time.
As usual, we can define methods that operate on Module members (the RandomState >>> f_val0 = f()
object for each of fn and gn). >>> f_val1 = f() #different numbers from f_val0
>>> m.nearly_zeros = Method([], rv_u + rv_u - 2 * rv_u) When we omit the updates argument (as in ``g``) to ``function``, then the
random number generator state is not affected by calling the returned function. So for example,
calling ``g`` multiple times will return the same numbers.
This function will always return a 2x2 matrix of small numbers, or possibly >>> g_val0 = g() # different numbers from f_val0 and f_val1
zeros. It illustrates that random variables are not re-drawn every time they >>> g_val0 = g() # same numbers as g_val0 !!!
are used, they are only drawn once per method call.
An important remark is that a random variable is drawn at most once during any
single function execution. So the ``nearly_zeros`` function is guaranteed to
return approximately 0 (except for rounding error) even though the ``rv_u``
random variable appears three times in the output expression.
>>> made = m.make() >>> nearly_zeros = function([], rv_u + rv_u - 2 * rv_u, updates=[rv_u.update])
When we compile things with m.make(), the RandomStreams instance (m.random)
emits an RandomStreamsInstance object (here called ``made.random``) containing a numpy
RandomState instance for each stream.
>>> assert isinstance(made.random[rv_u.rng], numpy.random.RandomState) Internal Attributes
--------------------
These RandomState objects can be accessed using normal indexing syntax. The random variables returned by methods like ``RandomStreams.uniform`` have
some useful internal attributes.
>>> fn_val0 = made.fn() The one we used above is the ``update`` attribute. ``rv_u.update`` is a pair
>>> fn_val1 = made.fn() whose first element is a shared variable whose value is a numpy RandomState,
and whose second element is an [symbolic] expression for the next value of that
RandomState after drawing samples.
Finally, we can use our methods to draw random numbers. Every call will of The first element of the ``update`` attribute is also accessible as
course, draw different ones! ``rv_u.rng``. A random variable can be re-seeded by seeding or assigning to
``rv_u.rng.value``.
The ``RandomStreams`` object itself has an ``updates()`` method that returns a
list of all the (state, new_state) update pairs from the random variables it
has returned. This can be a convenient shortcut to enumerating all
the random variables in a large graph in the ``update`` paramter of function.
Seedings Streams Seedings Streams
---------------- ----------------
All of the streams in a RandomStreams container can be seeded at once using the Random variables can be seeded individually or collectively.
seed method of a RandomStreamsInstance.
>>> made.random.seed(seed_value)
Of course, a RandomStreamsInstance can contain several RandomState instances and
these will _not_ all be seeded to the same seed_value. They will all be seeded
deterministically and probably uniquely as a function of the seed_value.
Seeding the generator in this way makes it possible to repeat random streams.
>>> made.random.seed(777)
>>> f0 = made.fn()
>>> made.random.seed(999)
>>> f1 = made.fn()
Here we have different values in f0 and f1 because they were created from You can seed just one random variable by seeding or assigning to the
generators seeded differently. ``.rng.value`` attribute.
>>> made.random.seed(777) >>> rv_u.rng.value.seed(89234) # seeds the generator for rv_u
>>> f2 = made.fn()
If we re-seed the generator with 777 and call fn again, we'll find that we You can also seed *all* of the random variables allocated by a ``RandomStreams``
re-drew the f0 values. object by that object's ``seed`` method. This seed will be used to seed a
temporary random number generator, that will in turn generate seeds for each
of the random variables.
>>> assert(numpy.all(f2 == f0)) >>> srng.seed(902340) # seeds rv_u and rv_n with different seeds each
Sharing Streams between Methods Sharing Streams between Functions
------------------------------- ---------------------------------
Streams in a Module, like other Members of a Module, are shared between Methods. As usual for shared variables, the random number generators used for random
This means that when one Method updates the state of a stream, it updates the variables are common between functions. So our ``nearly_zeros`` function will
state for other methods in that ModuleInstance too. update the state of the generators used in function ``f`` above.
For example: For example:
>>> m = Module() >>> state_after_v0 = rv_u.rng.value.get_state()
>>> m.random = RandomStreams(seed=234) >>> nearly_zeros() # this affects rv_u's generator
>>> rv_u = m.random.uniform((2,2)) >>> v1 = f()
>>> m.fn = Method([], rv_u) >>> rv_u.rng.value.set_state(state_after_v0)
>>> m.gn = Method([], rv_u) >>> v2 = f() # v2 != v1
>>> made = m.make()
>>> made.random.initialize()
>>> fn_val0 = made.fn()
>>> gn_val0 = made.gn()
At this point, fn_val0 != gn_val0 because made.fn() updated the random state
after it was called.
Compiled Modules are independent
--------------------------------
The RandomState objects are created only at the time of calling `make`, and
calling make repeatedly will create multiple independent RandomStreamsInstance
objects. So, for example, if we were to hijack the previous example like this:
>>> made = m.make()
>>> made_again = m.make()
>>> made.random.initialize()
>>> made_again.random.initialize()
>>> fn_val = made.fn()
>>> gn_val = made.gn()
>>> gn_val_ = made_again.gn()
>>> fn_val_ = made_again.fn()
We could call f and g in different orders in the two modules, and find that
``numpy.all(fn_val == fn_val_)`` and ``numpy.all(gn_val == gn_val_)``.
......
...@@ -25,7 +25,7 @@ def randomstate_constructor(value, name=None, strict=False): ...@@ -25,7 +25,7 @@ def randomstate_constructor(value, name=None, strict=False):
class RandomStreams(object): class RandomStreams(object):
"""Module component with similar interface to numpy.random (numpy.random.RandomState)""" """Module component with similar interface to numpy.random (numpy.random.RandomState)"""
random_state_variables = [] state_updates = []
"""A list of pairs of the form (input_r, output_r). This will be over-ridden by the module """A list of pairs of the form (input_r, output_r). This will be over-ridden by the module
instance to contain stream generators. instance to contain stream generators.
""" """
...@@ -39,7 +39,7 @@ class RandomStreams(object): ...@@ -39,7 +39,7 @@ class RandomStreams(object):
""" """
def updates(self): def updates(self):
return list(self.random_state_variables) return list(self.state_updates)
def __init__(self, seed=None): def __init__(self, seed=None):
""" """
...@@ -49,7 +49,7 @@ class RandomStreams(object): ...@@ -49,7 +49,7 @@ class RandomStreams(object):
`RandomStreamsInstance.__init__` for more details. `RandomStreamsInstance.__init__` for more details.
""" """
super(RandomStreams, self).__init__() super(RandomStreams, self).__init__()
self.random_state_variables = [] self.state_updates = []
self.default_instance_seed = seed self.default_instance_seed = seed
self.gen_seedgen = numpy.random.RandomState(seed) self.gen_seedgen = numpy.random.RandomState(seed)
...@@ -67,7 +67,7 @@ class RandomStreams(object): ...@@ -67,7 +67,7 @@ class RandomStreams(object):
seed = self.default_instance_seed seed = self.default_instance_seed
seedgen = numpy.random.RandomState(seed) seedgen = numpy.random.RandomState(seed)
for old_r, new_r in self.random_state_variables: for old_r, new_r in self.state_updates:
old_r_seed = seedgen.randint(2**30) old_r_seed = seedgen.randint(2**30)
old_r.value = numpy.random.RandomState(int(old_r_seed)) old_r.value = numpy.random.RandomState(int(old_r_seed))
...@@ -119,7 +119,8 @@ class RandomStreams(object): ...@@ -119,7 +119,8 @@ class RandomStreams(object):
random_state_variable = shared(numpy.random.RandomState(seed)) random_state_variable = shared(numpy.random.RandomState(seed))
new_r, out = op(random_state_variable, *args, **kwargs) new_r, out = op(random_state_variable, *args, **kwargs)
out.rng = random_state_variable out.rng = random_state_variable
self.random_state_variables.append((random_state_variable, new_r)) out.update = (random_state_variable, new_r)
self.state_updates.append(out.update)
return out return out
def binomial(self, *args, **kwargs): def binomial(self, *args, **kwargs):
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论