提交 dbc0ea78 authored 作者: Olivier Breuleux's avatar Olivier Breuleux

new stuff in tensor_random

上级 4e873cfe
"""Convenient driver of graph construction, optimization, and linking.""" """Convenient driver of graph construction, optimization, and linking."""
import numpy
import gof
import sys
from copy import copy
import tensor_opt
# class Supervisor:
# def __init__(self, protected):
# self.protected = protected
# def validate(self, env):
# if not hasattr(env, 'destroyers'):
# return True
# for r in self.protected + env.outputs:
# if env.destroyers(r):
# raise gof.InconsistencyError("Trying to destroy a protected Result.")
# class State(object):
# def __init__(self, variable, new_state = None):
# self.variable = variable
# if new_state is None:
# self.new_state = variable
# else:
# self.new_state = new_state
# class StateContainer(object):
# def __init__(self, data):
# self.data = data
# def env_with_state(normal_inputs, normal_outputs, states, accept_inplace = False):
# state_inputs = [s.variable for s in states]
# state_outputs = [s.new_state for s in states]
# inputs = normal_inputs + state_inputs
# outputs = normal_outputs + state_outputs
# inputs, outputs = gof.graph.clone(inputs, outputs)
# env = gof.env.Env(inputs, outputs)
# for node in env.nodes:
# if getattr(node.op, 'destroy_map', None):
# if not accept_inplace:
# raise TypeError("Graph must not contain inplace operations", node)
# else:
# env.extend(gof.DestroyHandler())
# break
# env.extend(Supervisor(normal_inputs))
# return env
# def function_with_state(fn, state_containers, unpack_single = True):
# n = len(state_containers)
# nin = len(fn.inputs)
# nout = len(fn.outputs)
# if n == 0:
# if unpack_single and nin == 1:
# return lambda *inputs: fn(*inputs)[0]
# else:
# return fn
# def f(*inputs):
# results = fn(*(list(inputs) + [c.data for c in state_containers]))
# for c, d in zip(state_containers, results[-n:]):
# c.data = d
# results = results[:-n]
# if unpack_single and len(results) == 1:
# return results[0]
# else:
# return results
# def check_equal(x, y):
# x, y = x[0], y[0]
# if isinstance(x, numpy.ndarray) or isinstance(y, numpy.ndarray):
# if x.dtype != y.dtype or x.shape != y.shape or numpy.any(abs(x - y) > 1e-10):
# raise Exception("Output mismatch.", {'performlinker': x, 'clinker': y})
# else:
# if x != y:
# raise Exception("Output mismatch.", {'performlinker': x, 'clinker': y})
# def infer_reuse_pattern(env, outputs_to_disown):
# do_not_reuse = list()
# seen = set()
# def walk(r):
# if r.owner is None or r in seen:
# return
# seen.add(r)
# do_not_reuse.append(r)
# node = r.owner
# op = node.op
# dmap = op.destroy_map if hasattr(op, 'destroy_map') else {}
# vmap = op.view_map if hasattr(op, 'view_map') else {}
# for l in dmap.values() + vmap.values():
# for i in l:
# walk(node.inputs[i])
# for output in outputs_to_disown:
# walk(output)
# return do_not_reuse
# predefined_linkers = {
# 'py' : gof.PerformLinker(),
# 'c' : gof.CLinker(),
# 'c|py' : gof.OpWiseCLinker(),
# 'c&py' : gof.DualLinker(checker = check_equal)
# }
# default_linker = 'c|py'
# predefined_optimizers = {
# None : lambda env: None,
# 'merge' : gof.MergeOptimizer(),
# 'math' : gof.MergeOptMerge(tensor_opt.math_optimizer)
# }
# default_optimizer = 'merge'
# class FunctionFactory:
# def __init__(self,
# inputs,
# outputs,
# states = [],
# linker = default_linker,
# optimizer = default_optimizer,
# borrow_outputs = False,
# accept_inplace = False):
# self.states = states
# inputs, outputs = list(inputs), list(outputs)
# # Error checking
# for r in inputs + outputs:
# if not isinstance(r, gof.Result):
# raise TypeError("All inputs and outputs to FunctionFactory should be Result instances. Received:", type(r), r)
# for state in states:
# if not isinstance(state, State):
# raise TypeError("All states must be State instances", type(state), state)
# if len(inputs) != len(set(inputs)):
# print >>sys.stderr, "Warning: duplicate inputs"
# # make the env
# env = env_with_state(inputs, outputs, states, accept_inplace)
# self.env = env
# # optimize the env
# optimizer = predefined_optimizers.get(optimizer, optimizer)
# optimizer(env)
# # initialize the linker
# linker = copy(predefined_linkers.get(linker, linker))
# if not hasattr(linker, 'accept'):
# raise ValueError("'linker' parameter of FunctionFactory should be a Linker with an accept method " \
# "or one of %s" % predefined_linkers.keys())
# if borrow_outputs:
# self.linker = linker.accept(env)
# else:
# self.linker = linker.accept(env, no_recycling = infer_reuse_pattern(env, env.outputs))
# def create(self,
# states = [],
# profiler = None,
# unpack_single = True,
# strict = 'if_destroyed'):
# # Error checking
# if strict not in [True, False, 'if_destroyed']:
# raise ValueError("'strict' parameter of create should be one of [True, False, 'if_destroyed']")
# if len(states) != len(self.states):
# raise ValueError("not the right number of state initializers (expected %i, got %i)" % (len(self.states), len(states)))
# # Get a function instance
# if profiler is None:
# # some linkers may not support profilers, so we avoid passing the option altogether
# _fn = self.linker.make_function(unpack_single = False)
# else:
# _fn = self.linker.make_function(unpack_single = False,
# profiler = profiler)
# fn = function_with_state(_fn, states, unpack_single)
# # Make the inputs strict accordingly to the specified policy
# for env_input, fn_input in zip(self.env.inputs, _fn.inputs):
# if strict is True or (strict == 'if_destroyed' and self.env.destroyers(env_input)):
# fn_input.strict = True
# return fn
# def function(inputs,
# outputs,
# states = [],
# linker = default_linker,
# optimizer = default_optimizer,
# borrow_outputs = False,
# accept_inplace = False,
# profiler = None,
# unpack_single = True,
# strict = 'if_destroyed'):
# ff = FunctionFactory(inputs,
# outputs,
# states = [s[0] for s in states],
# linker = linker,
# optimizer = optimizer,
# borrow_outputs = borrow_outputs)
# return ff.create(states = [s[1] for s in states],
# profiler = profiler,
# unpack_single = unpack_single,
# strict = strict)
import numpy import numpy
import gof import gof
...@@ -255,6 +565,10 @@ class OpFromGraph(gof.Op): ...@@ -255,6 +565,10 @@ class OpFromGraph(gof.Op):
#########################aaaaaaaaaaa
# class State: # class State:
# def __init__(self, init, next = None): # def __init__(self, init, next = None):
# self.init = init # self.init = init
......
...@@ -20,7 +20,6 @@ from gof.python25 import partial ...@@ -20,7 +20,6 @@ from gof.python25 import partial
### set up the external interface ### set up the external interface
from elemwise import Elemwise, DimShuffle, CAReduce, Sum from elemwise import Elemwise, DimShuffle, CAReduce, Sum
import tensor_random as random
def as_tensor(x, name = None): def as_tensor(x, name = None):
...@@ -926,16 +925,27 @@ class MakeVector(Op): ...@@ -926,16 +925,27 @@ class MakeVector(Op):
def __init__(self, stype): def __init__(self, stype):
self.stype = stype self.stype = stype
def make_node(self, *inputs): def make_node(self, *inputs):
inputs = map(as_tensor, inputs)
assert all(a.type == self.stype for a in inputs) assert all(a.type == self.stype for a in inputs)
return Apply(self, inputs, [Tensor(broadcastable = (False,), return Apply(self, inputs, [Tensor(broadcastable = (False,),
dtype = self.stype.dtype)()]) dtype = self.stype.dtype)()])
def perform(self, inputs, (out,)): def perform(self, node, inputs, (out,)):
return numpy.asarray([i[0] for i in inputs]) out[0] = numpy.asarray(inputs)
def grad(self, inputs, (gout,)): def grad(self, inputs, (gout,)):
return [None]*len(inputs) return [None]*len(inputs)
make_lvector = MakeVector(lscalar) make_lvector = MakeVector(lscalar)
def get_vector_length(v):
if isinstance(v, gof.Constant) and v.type.ndim == 1:
return len(v.data)
elif v.owner and isinstance(v.owner.op, MakeVector):
return len(v.owner.inputs)
elif v.owner and v.owner.op == shape:
return v.owner.inputs[0].type.ndim
else:
return None
class VerticalStack(Op): class VerticalStack(Op):
""" """
......
...@@ -4,146 +4,288 @@ import tensor ...@@ -4,146 +4,288 @@ import tensor
import numpy import numpy
import functools import functools
class RandomState(object): #from compile import State
"""The Theano version of numpy.RandomState from copy import copy
This class generates a sequence of L{Op} instances via the gen() and class RandomFunction(gof.Op):
gen_like() methods.
def __init__(self, fn, outtype, *args, **kwargs):
@ivar seed: an integer which determines the initial state of the L{Op}
instances returned by gen(), gen_like()
@type seed: int
"""
def __init__(self, seed):
self.seed = seed
def gen(self, dist, shape=(), ndim=None):
"""
@param dist: identifier of a sampling distribution. See L{_fn_from_dist}.
@param shape: tuple
@return: A tensor of random numbers, with given shape.
@rtype: L{Result} (output of L{Apply} of L{NumpyGenerator} instance)
"""
self.seed += 1
fn = RandomState._fn_from_dist(dist)
if isinstance(shape, tuple):
return NumpyGenerator(self.seed-1, len(shape),fn) (shape)
return NumpyGenerator(self.seed - 1, ndim, fn)(shape)
def gen_like(self, dist, x):
"""
@param dist: identifier of a sampling distribution. See L{_fn_from_dist}.
@param x: L{Result} of type L{Tensor}
@return: A tensor of random numbers, with the same shape as x.
@rtype: L{Result} (output of L{Apply} of L{NumpyGenerator} instance)
"""
self.seed += 1
fn = RandomState._fn_from_dist(dist)
return NumpyGenerator(self.seed-1, x.type.ndim, fn)(tensor.shape(x))
def uniform_like(self, template, low=0.,high=1.):
"""
Return a multivariate uniform(low,high)
random variable in a tensor of the same shape as template
(template can either be a tensor or a shape tuple). Each element of the
resulting tensor is sampled independently. low and high can
be scalars or have the same shape as the template (or broadcastable
to it).
"""
return self.gen_like(('uniform',{'low':low,'high':high}),template)
def binomial_like(self, template, n=1, p=0.5):
"""
Return a multivariate binomial(n,p) random variable in a tensor of the same shape as template
(template can either be a tensor or a shape tuple). Each element of the
resulting tensor is sampled independently. low and high can
be scalars or have the same shape as the template (or broadcastable
to it).
"""
return self.gen_like(('binomial',{'n':n,'p':p}),template)
@staticmethod
def _fn_from_dist(dist, cache={}):
"""Return a function from a distribution description
@param dist: identifier of a sampling distribution.
@type dist: callable or str or tuple(str, dict)
@param cache: The optional cache argument implements a closure, which ensures that
multiple requests for the same sampling function will get the same
sampling function. L{NumpyGenerator}.__hash__ depends on this.
@type cache: dict
"""
if callable(dist):
return dist
if isinstance(dist, str):
return getattr(numpy.random.RandomState, dist)
name, kwargs = dist
key = (name, tuple(kwargs.items()))
if key not in cache:
fn = getattr(numpy.random.RandomState, name)
fn = functools.partial(fn, **kwargs)
cache[key] = fn
return cache[key]
class NumpyGenerator(gof.op.Op):
"""Supply a sequence of random tensors of a given shape, from a given
distribution.
@param seed: initial state for instances of this L{Op}.
@type seed: anything that numpy.random.RandomState accepts.
@param ndim: the rank of random tensors produced by this op.
@type ndim: non-negative integer
@param fn: a sampling function
@type fn: a callable that can reply to fn(numpy.RandomState(), size=<tuple>)
"""
destroy_map = {0: [0]}
def __init__(self, seed, ndim, fn, **kwargs):
gof.op.Op.__init__(self, **kwargs)
self.seed = seed
self.ndim = ndim
self.fn = fn self.fn = fn
assert numpy.random.RandomState(seed) #test the seed self.outtype = outtype
assert 'int' in str(type(ndim)) self.args = map(tensor.as_tensor, args)
assert callable(self.fn) self.inplace = kwargs.pop('inplace', False)
if self.inplace:
self.destroy_map = {0: [0]}
def make_node(self, r, shape, *args):
args = map(tensor.as_tensor, args)
shape = tensor.as_tensor(shape)
assert shape.type == tensor.lvector
assert len(args) <= len(self.args)
args += (None,) * (len(self.args) - len(args))
inputs = []
for arg, default in zip(args, self.args):
assert arg is None or default.type.dtype == arg.type.dtype
input = default if arg is None else arg
inputs.append(input)
return gof.Apply(self,
[r, shape] + inputs,
[r.type(), self.outtype()])
def perform(self, node, inputs, (rout, out)):
r, shape, args = inputs[0], inputs[1], inputs[2:]
assert self.outtype.ndim == len(shape)
if not self.inplace:
r = copy(r)
rout[0] = r
out[0] = self.fn(r, *(args + [shape]))
def __eq__(self, other): def __eq__(self, other):
return (type(self) is type(other))\ return type(self) == type(other) \
and self.__class__ is NumpyGenerator \ and self.fn == other.fn\
and self.seed == other.seed \ and self.outtype == other.outtype\
and self.ndim == other.ndim \ and self.args == other.args\
and self.fn == other.fn and self.inplace == other.inplace
def __hash__(self): def __hash__(self):
return self.seed ^ self.ndim ^ hash(self.fn) return hash(self.fn) ^ hash(self.outtype) ^ hash(self.args) ^ hash(self.inplace)
def make_node(self, _shape):
#TODO: check for constant shape, and guess the broadcastable bits def random_function(fn, dtype, *rfargs, **rfkwargs):
shape = tensor.convert_to_int64(_shape) def f(ndim, *args, **kwargs):
if shape.type.ndim != 1: if isinstance(ndim, int):
raise TypeError('shape argument was not converted to 1-d tensor', _shape) r, shape, args = args[0], args[1], args[2:]
else:
# we generate one random number with the distribution to determine what dtype to expect r, shape, args = ndim, args[0], args[1:]
output_dtype = str(self.fn(numpy.random.RandomState(18), size=(1,)).dtype) shape = tensor.as_tensor(shape)
ndim = tensor.get_vector_length(shape)
inputs = [gof.Value(gof.type.generic, numpy.random.RandomState(self.seed)), shape] if ndim is None:
outputs = [tensor.Tensor(dtype=output_dtype, broadcastable = [False]*self.ndim).make_result()] raise ValueError('Cannot infer the number of dimensions from the shape argument.')
return gof.Apply(op = self, inputs = inputs, outputs = outputs) # note: rf should probably be cached for future use
rf = RandomFunction(fn, tensor.Tensor(dtype = dtype, broadcastable = (False,)*ndim), *rfargs, **rfkwargs)
def grad(self, inputs, grad_outputs): return rf(r, shape, *args, **kwargs)
return [None, None] return f
def perform(self, node, input_storage, output_storage):
rng = input_storage[0] uniform = random_function(numpy.random.RandomState.uniform, 'float64', 0.0, 1.0)
shape = input_storage[1]
if self.ndim != len(shape):
raise ValueError('shape argument %s had the wrong length (!=%i)' %
(shape, self.ndim) ) # T = tensor
output_storage[0][0] = self.fn(rng, size=shape) # import compile
# x, y = T.matrices('xy')
# r = gof.generic()
# shp = T.make_lvector(2, 2, 2)
# r2, z = uniform(r, shp, x, y)
# f = compile.function([r, x, y], [z])
# print f(numpy.random.RandomState(1000), [[-1, -1], [-10, -10]], [[10, 1], [10, 1]])
@gof.local_optimizer
def random_make_inplace(node):
op = node.op
if isinstance(op, RandomFunction) and not op.inplace:
return RandomFunction(op.fn, op.outtype, *op.args, **dict(inplace=True)).make_node(*node.inputs).outputs
# class RandomState(StateCollection):
# def __init__(self, name = None):
# self.states = []
# self.name = name
# def gen(self, op, *args, **kwargs):
# r = gof.Generic()
# new_r, out = op(*args, **kwargs)
# state = State(r, new_r)
# self.states.append(state)
# return out
# def make_states(self, init):
# return [Container(numpy.random.RandomState(0)) for state in self.states]
# class RandomState(object):
# """The Theano version of numpy.RandomState
# This class generates a sequence of L{Op} instances via the gen() and
# gen_like() methods.
# @ivar seed: an integer which determines the initial state of the L{Op}
# instances returned by gen(), gen_like()
# @type seed: int
# """
# def __init__(self, seed):
# self.seed = seed
# def gen(self, dist, shape=(), ndim=None):
# """
# @param dist: identifier of a sampling distribution. See L{_fn_from_dist}.
# @param shape: tuple
# @return: A tensor of random numbers, with given shape.
# @rtype: L{Result} (output of L{Apply} of L{NumpyGenerator} instance)
# """
# self.seed += 1
# fn = RandomState._fn_from_dist(dist)
# if isinstance(shape, tuple):
# return NumpyGenerator(self.seed-1, len(shape),fn) (shape)
# return NumpyGenerator(self.seed - 1, ndim, fn)(shape)
# def gen_like(self, dist, x):
# """
# @param dist: identifier of a sampling distribution. See L{_fn_from_dist}.
# @param x: L{Result} of type L{Tensor}
# @return: A tensor of random numbers, with the same shape as x.
# @rtype: L{Result} (output of L{Apply} of L{NumpyGenerator} instance)
# """
# self.seed += 1
# fn = RandomState._fn_from_dist(dist)
# return NumpyGenerator(self.seed-1, x.type.ndim, fn)(tensor.shape(x))
# def uniform_like(self, template, low=0.,high=1.):
# """
# Return a multivariate uniform(low,high)
# random variable in a tensor of the same shape as template
# (template can either be a tensor or a shape tuple). Each element of the
# resulting tensor is sampled independently. low and high can
# be scalars or have the same shape as the template (or broadcastable
# to it).
# """
# return self.gen_like(('uniform',{'low':low,'high':high}),template)
# def binomial_like(self, template, n=1, p=0.5):
# """
# Return a multivariate binomial(n,p) random variable in a tensor of the same shape as template
# (template can either be a tensor or a shape tuple). Each element of the
# resulting tensor is sampled independently. low and high can
# be scalars or have the same shape as the template (or broadcastable
# to it).
# """
# return self.gen_like(('binomial',{'n':n,'p':p}),template)
# @staticmethod
# def _fn_from_dist(dist, cache={}):
# """Return a function from a distribution description
# @param dist: identifier of a sampling distribution.
# @type dist: callable or str or tuple(str, dict)
# @param cache: The optional cache argument implements a closure, which ensures that
# multiple requests for the same sampling function will get the same
# sampling function. L{NumpyGenerator}.__hash__ depends on this.
# @type cache: dict
# """
# if callable(dist):
# return dist
# if isinstance(dist, str):
# return getattr(numpy.random.RandomState, dist)
# name, kwargs = dist
# key = (name, tuple(kwargs.items()))
# if key not in cache:
# fn = getattr(numpy.random.RandomState, name)
# fn = functools.partial(fn, **kwargs)
# cache[key] = fn
# return cache[key]
# class NumpyGenerator(gof.op.Op):
# """Supply a sequence of random tensors of a given shape, from a given
# distribution.
# @param seed: initial state for instances of this L{Op}.
# @type seed: anything that numpy.random.RandomState accepts.
# @param ndim: the rank of random tensors produced by this op.
# @type ndim: non-negative integer
# @param fn: a sampling function
# @type fn: a callable that can reply to fn(numpy.RandomState(), size=<tuple>)
# """
# destroy_map = {0: [0]}
# def __init__(self, seed, ndim, fn, **kwargs):
# gof.op.Op.__init__(self, **kwargs)
# self.seed = seed
# self.ndim = ndim
# self.fn = fn
# assert numpy.random.RandomState(seed) #test the seed
# assert 'int' in str(type(ndim))
# assert callable(self.fn)
# def __eq__(self, other):
# return (type(self) is type(other))\
# and self.__class__ is NumpyGenerator \
# and self.seed == other.seed \
# and self.ndim == other.ndim \
# and self.fn == other.fn
# def __hash__(self):
# return self.seed ^ self.ndim ^ hash(self.fn)
# def make_node(self, _shape):
# #TODO: check for constant shape, and guess the broadcastable bits
# shape = tensor.convert_to_int64(_shape)
# if shape.type.ndim != 1:
# raise TypeError('shape argument was not converted to 1-d tensor', _shape)
# # we generate one random number with the distribution to determine what dtype to expect
# output_dtype = str(self.fn(numpy.random.RandomState(18), size=(1,)).dtype)
# inputs = [gof.Value(gof.type.generic, numpy.random.RandomState(self.seed)), shape]
# outputs = [tensor.Tensor(dtype=output_dtype, broadcastable = [False]*self.ndim).make_result()]
# return gof.Apply(op = self, inputs = inputs, outputs = outputs)
# def grad(self, inputs, grad_outputs):
# return [None, None]
# def perform(self, node, input_storage, output_storage):
# rng = input_storage[0]
# shape = input_storage[1]
# if self.ndim != len(shape):
# raise ValueError('shape argument %s had the wrong length (!=%i)' %
# (shape, self.ndim) )
# output_storage[0][0] = self.fn(rng, size=shape)
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论