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

merge

...@@ -59,7 +59,7 @@ def __oplist_tag(thing, tag): ...@@ -59,7 +59,7 @@ def __oplist_tag(thing, tag):
thing.__oplist_tags = tags thing.__oplist_tags = tags
def as_tensor(x, name = None): def as_tensor(x, name = None, ndim=None):
"""Return `x`, transformed into a `Tensor` """Return `x`, transformed into a `Tensor`
This function is often used by `make_node` methods of `Op` subclasses to This function is often used by `make_node` methods of `Op` subclasses to
...@@ -73,6 +73,8 @@ def as_tensor(x, name = None): ...@@ -73,6 +73,8 @@ def as_tensor(x, name = None):
to make an ndarray. to make an ndarray.
- `name`: str or None - `name`: str or None
If a new `Result` instance is created, it will be named with this string. If a new `Result` instance is created, it will be named with this string.
- `ndim`: None or integer
Return a Result with this many dimensions. Raise TypeError if it's not possible.
:Exceptions: :Exceptions:
- `ValueError`: raised if an `Apply` with no default output is fetched - `ValueError`: raised if an `Apply` with no default output is fetched
...@@ -88,12 +90,23 @@ def as_tensor(x, name = None): ...@@ -88,12 +90,23 @@ def as_tensor(x, name = None):
x = x.outputs[0] x = x.outputs[0]
if isinstance(x, Result): if isinstance(x, Result):
if isinstance(x.type, scal.Scalar): if isinstance(x.type, scal.Scalar):
return tensor_from_scalar(x) x = tensor_from_scalar(x)
if not isinstance(x.type, Tensor): if not isinstance(x.type, Tensor):
raise TypeError("Result type field must be a Tensor.", x, x.type) raise TypeError("Result type field must be a Tensor.", x, x.type)
return x
if ndim is None:
return x
else:
if (x.type.ndim > ndim):
#TODO: strip off leading broadcastable dimensions
raise ValueError('Tensor could not be cast to have %i dimensions' % ndim, x.type)
elif (x.type.ndim < ndim):
return shape_padleft(x, n_ones=(ndim - x.type.ndim))
else:
return x
try: try:
return constant(x) return constant(x, name=name, ndim=ndim)
except TypeError: except TypeError:
try: try:
str_x = str(x) str_x = str(x)
...@@ -105,43 +118,39 @@ def as_tensor(x, name = None): ...@@ -105,43 +118,39 @@ def as_tensor(x, name = None):
# to upcast their arguments... this internal-use function is a good place to put debugging stuff, better than the global astensor. # to upcast their arguments... this internal-use function is a good place to put debugging stuff, better than the global astensor.
_as_tensor = as_tensor _as_tensor = as_tensor
def constant_or_value(x, rtype, name=None, ndim=None):
def constant(x, name=None):
"""Return a symbolic `Constant` with value `x` """Return a symbolic `Constant` with value `x`
:Exceptions: :Exceptions:
- `TypeError`: `x` could not be converted to a numpy.ndarray - `TypeError`: `x` could not be converted to a numpy.ndarray
""" - `ValueError`: `x` could not be expanded to have ndim dimensions
if isinstance(x, numpy.ndarray):
x_ = x
else:
x_ = numpy.asarray(x)
try:
return TensorConstant(Tensor(dtype = x_.dtype,
broadcastable = [d == 1 for d in x_.shape]), x_, name=name)
except:
raise TypeError("Could not convert %s to Tensor" % x, type(x))
def value(x, name=None):
"""Return a symbolic `Value` with default value `x`
:Exceptions:
- `TypeError`: `x` could not be converted to a numpy.ndarray
""" """
if isinstance(x, numpy.ndarray): if isinstance(x, numpy.ndarray):
x_ = x x_ = x
else: else:
x_ = numpy.asarray(x) x_ = numpy.asarray(x)
bcastable = [d == 1 for d in x_.shape]
if ndim is not None:
if len(bcastable) < ndim:
bcastable = [True] * (ndim - len(bcastable)) + bcastable
elif len(bcastable) > ndim:
#TODO: strip off dimensions of size 1
raise ValueError('ndarray could not be cast to constant with %i dimensions' % ndim)
assert len(bcastable) == ndim
try: try:
if name is None: return rtype(Tensor(dtype = x_.dtype, broadcastable = bcastable), x_, name=name)
return TensorValue(Tensor(dtype = x_.dtype,
broadcastable = [d == 1 for d in x_.shape]), x_)
else:
return TensorValue(Tensor(dtype = x_.dtype,
broadcastable = [d == 1 for d in x_.shape]), x_, name=name)
except: except:
raise TypeError("Could not convert %s to Tensor" % x, type(x)) raise TypeError("Could not convert %s to Tensor" % x, type(x))
def constant(x, name=None, ndim=None):
return constant_or_value(x, rtype=TensorConstant, name=name, ndim=ndim)
def value(x, name=None, ndim=None):
return constant_or_value(x, rtype=TensorValue, name=name, ndim=ndim)
class Tensor(Type): class Tensor(Type):
......
...@@ -13,31 +13,122 @@ import sys ...@@ -13,31 +13,122 @@ import sys
RS = numpy.random.RandomState RS = numpy.random.RandomState
class RandomStateType(gof.Type):
"""A Type wrapper for numpy.RandomState
The reason this exists (and `Generic` doesn't suffice) is that RandomState objects that
would appear to be equal do not compare equal with the '==' operator. This Type exists to
provide an equals function that is used by DebugMode.
"""
def __str__(self):
return 'RandomStateType'
def filter(self, data, strict=False):
if self.is_valid_value(data):
return data
else:
raise TypeError()
def is_valid_value(self, a):
return type(a) == numpy.random.RandomState
def values_eq(self, a, b):
sa = a.get_state()
sb = b.get_state()
for aa, bb in zip(sa, sb):
if isinstance(aa, numpy.ndarray):
if not numpy.all(aa == bb):
return False
else:
if not aa == bb:
return False
return True
random_state_type = RandomStateType()
class RandomFunction(gof.Op): class RandomFunction(gof.Op):
"""Op that draws random numbers from a numpy.RandomState object
"""
def __init__(self, fn, outtype, *args, **kwargs): def __init__(self, fn, outtype, *args, **kwargs):
""" """
fn: a random function with the same signature as functions in numpy.random.RandomState :param fn: a member function of numpy.RandomState
outtype: the type of the output Technically, any function with a signature like the ones in numpy.random.RandomState
args: a list of default arguments for the function will do. This function must accept the shape (sometimes called size) of the output as
kwargs: if the 'inplace' key is there, its value will be used to determine if the op operates inplace or not the last positional argument.
:type fn: string or function reference. A string will be interpreted as the name of a
member function of numpy.random.RandomState.
:param outtype: the theano Type of the output
:param args: a list of default arguments for the function
:param kwargs: if the 'inplace' key is there, its value will be used to determine if the op operates inplace or not
""" """
self.__setstate__([fn, outtype, args, kwargs]) self.__setstate__([fn, outtype, args, kwargs])
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
def __hash__(self):
return hash(type(self)) ^ hash(self.fn) \
^ hash(self.outtype) ^ hash(self.args) ^ hash(self.inplace)
def __getstate__(self):
return self.state
def __setstate__(self, state):
self.state = state
fn, outtype, args, kwargs = state
self.fn = getattr(RS, fn) if isinstance(fn, str) else fn
self.outtype = outtype
self.args = tuple(tensor.as_tensor(arg) for arg in args)
self.inplace = kwargs.pop('inplace', False)
if self.inplace:
self.destroy_map = {0: [0]}
def make_node(self, r, shape, *args): def make_node(self, r, shape, *args):
""" """
in: r -> RandomState (gof.generic), :param r: a numpy.RandomState instance, or a Result of Type RandomStateType that will
shape -> lvector contain a RandomState instance.
args -> the arguments expected by the numpy function
out: r2 -> the new RandomState (gof.generic) :param shape: an lvector with the shape of the tensor output by this Op. At runtime,
out -> the random numbers we generated the value associated with this lvector must have a length that matches the number of
dimensions promised by `self.outtype`.
:param args: the values associated with these results will be passed to the RandomState
function during perform as extra "*args"-style arguments. These should be castable to
results of Type Tensor.
:rtype: Apply
:return: Apply with two outputs. The first output is a gof.generic Result from which
to draw further random numbers. The second output is the outtype() instance holding
the random draw.
""" """
args = map(tensor.as_tensor, args) args = map(tensor.as_tensor, args)
if shape == () or shape == []: if shape == () or shape == []:
shape = tensor.lvector() shape = tensor.lvector()
else: else:
shape = tensor.as_tensor(shape) shape = tensor.as_tensor(shape, ndim=1)
assert shape.type == tensor.lvector #print 'SHAPE TYPE', shape.type, tensor.lvector
assert shape.type.ndim == 1
assert shape.type.dtype == 'int64'
if not isinstance(r.type, RandomStateType):
print >> sys.stderr, 'WARNING: RandomState instances should be in RandomStateType'
if 0:
raise TypeError('r must be RandomStateType instance', r)
# assert shape.type == tensor.lvector doesn't work because we want to ignore the
# broadcastable vector
assert len(args) <= len(self.args) assert len(args) <= len(self.args)
args += (None,) * (len(self.args) - len(args)) args += (None,) * (len(self.args) - len(args))
inputs = [] inputs = []
...@@ -51,6 +142,7 @@ class RandomFunction(gof.Op): ...@@ -51,6 +142,7 @@ class RandomFunction(gof.Op):
def perform(self, node, inputs, (rout, out)): def perform(self, node, inputs, (rout, out)):
r, shape, args = inputs[0], inputs[1], inputs[2:] r, shape, args = inputs[0], inputs[1], inputs[2:]
assert type(r) == numpy.random.RandomState
r_orig = r r_orig = r
assert self.outtype.ndim == len(shape) assert self.outtype.ndim == len(shape)
if not self.inplace: if not self.inplace:
...@@ -64,31 +156,7 @@ class RandomFunction(gof.Op): ...@@ -64,31 +156,7 @@ class RandomFunction(gof.Op):
out[0] = rval out[0] = rval
def grad(self, inputs, outputs): def grad(self, inputs, outputs):
return [None] * len(inputs) return [None for i in inputs]
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
def __hash__(self):
return hash(self.fn) ^ hash(self.outtype) ^ hash(self.args) ^ hash(self.inplace)
def __getstate__(self):
return self.state
def __setstate__(self, state):
self.state = state
fn, outtype, args, kwargs = state
self.fn = getattr(RS, fn) if isinstance(fn, str) else fn
self.outtype = outtype
self.args = tuple(tensor.as_tensor(arg) for arg in args)
self.inplace = kwargs.pop('inplace', False)
if self.inplace:
self.destroy_map = {0: [0]}
...@@ -131,7 +199,7 @@ def random_function(fn, dtype, *rfargs, **rfkwargs): ...@@ -131,7 +199,7 @@ def random_function(fn, dtype, *rfargs, **rfkwargs):
ndim = tensor.get_vector_length(shape) ndim = tensor.get_vector_length(shape)
if ndim is None: if ndim is None:
raise ValueError('Cannot infer the number of dimensions from the shape argument.') raise ValueError('Cannot infer the number of dimensions from the shape argument.')
# note: rf should probably be cached for future use # note: rf could be cached for future use
rf = RandomFunction(fn, tensor.Tensor(dtype = dtype, broadcastable = (False,)*ndim), *rfargs, **rfkwargs) rf = RandomFunction(fn, tensor.Tensor(dtype = dtype, broadcastable = (False,)*ndim), *rfargs, **rfkwargs)
return rf(r, shape, *args, **kwargs) return rf(r, shape, *args, **kwargs)
return f return f
......
## TODO: REDO THESE TESTS ## TODO: REDO THESE TESTS
import sys
import unittest import unittest
import numpy as N import numpy as N
...@@ -9,6 +9,39 @@ from theano import tensor ...@@ -9,6 +9,39 @@ from theano import tensor
from theano import compile, gof 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)
assert not rf.inplace
assert getattr(rf, 'destroy_map', {}) == {}
rng_R = random_state_type()
print rng_R
post_r, out = rf(rng_R, (4,))
assert out.type == tensor.dvector
f = compile.function([rng_R], out)
rng_state0 = numpy.random.RandomState(55)
f_0 = f(rng_state0)
f_1 = f(rng_state0)
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)
assert rf.inplace
assert getattr(rf, 'destroy_map', {}) != {}
def test_inplace_optimization(self):
print >> sys.stderr, "WARNING NOT IMPLEMENTED T_random_function.test_inplace_optimization"
class T_test_module(unittest.TestCase): class T_test_module(unittest.TestCase):
def test_state_propagation(self): def test_state_propagation(self):
x = tensor.vector() x = tensor.vector()
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论