def get_symbolic(self, x): """ Attempts to retrieve the symbolic version of x. if x is an numeric object (int, float, numpy array), it must have been traced by the context during recompiled function execution. if x is a string, it must have been tagged with autodiff.functions.tag(). """ if isinstance(x, basestring): if x in self.sym_vars: return self.sym_vars[x] elif x in self.tags: return self.tags[x] else: raise ValueError( 'Requested the symbolic variable of tag `{0}`' ', but `{0}` was not tagged.'.format(x)) elif utils.isvar(x): return x elif id(x) in self.sym_vars: return self.sym_vars[id(x)] elif isinstance(x, int) and not isinstance(x, bool) and -5 <= x <= 256: raise ValueError( 'Small integers (-5 <= x <= 256) can not be shadowed due to ' 'CPython caching. Try casting the variable as a NumPy int ' 'type or array before tracing: {0}'.format(x)) else: raise ValueError( 'Requested the symbolic variable shadowing object {0}' ', but it was not traced.'.format(repr(x)))
def _checkfn(context, f, var_ndim=None, *args, **kwargs): context.reset() override = kwargs.pop('override', None) var_ndim = utils.as_seq(var_ndim) dim = [[4] * nd for nd in var_ndim] values = tuple([np.random.random(d) for d in dim]) # make shallow copies to avoid inplace corruption sym_values = copy.copy(values) sym_args = copy.copy(args) sym_kwargs = copy.copy(kwargs) F = context.recompile(f) sym_vars = F(*(sym_values + sym_args), **sym_kwargs) sym_result = [ v.eval() if utils.isvar(v) else v for v in utils.as_seq(sym_vars) ] if len(sym_result) == 0: sym_result = None py_result = override or f(*(values + args), **kwargs) if sym_result is None: return sym_result is None and py_result is None else: return np.allclose(py_result, sym_result)
def zip_(*args): if __builtin__.any(utils.isvar(a) for a in args): raise TypeError( 'Called zip() on Tensor but Tensors ' 'do not support iteration. Maybe try escaping ' 'the tensor?') else: return zip(*args)
def enumerate_(iterable, start=0): if utils.isvar(iterable): raise TypeError( 'Called enumerate() on Tensor {0} but Tensors ' 'do not support iteration. Maybe try escaping ' 'the tensor?'.format(iterable)) else: return enumerate(iterable, start=start)
def handle_tag_function_arg(self, obj, tag): """ A version of tagging called only by visit_FunctionDef, which tags top-level function arguments and stores the tags in sym_vars. These tags can not be overwritten. """ self.context.sym_vars[tag] = obj if utils.isvar(obj): obj.name = tag
def handle_bool_subscript(self, x): """ Theano doesn't have a bool type, but we can track certain variables that we know must be boolean and possibly use that information (for advanced indexing, for example). """ if utils.isvar(x) and x.dtype == 'int8': return x.nonzero() else: return x
def escape(x): if isinstance(x, theano.tensor.sharedvar.SharedVariable): return x.get_value() elif utils.isvar(x): try: return x.eval() except: raise ValueError('Could not escape {0}'.format(x)) else: return x
def handle_tag(self, obj, tag): if not isinstance(tag, basestring): raise ValueError('Tag must be a string. Received: {0}'.format(tag)) if tag in self.context.tags: logger.warning( '{0} was tagged as {1}, but the tag {1} was already ' 'assigned. Note that the new tag will overwrite ' 'the old one.'.format(obj, tag)) else: self.context.tags[tag] = obj if utils.isvar(obj): obj.name = tag return obj
def handle_comparison(self, operator, left, right): """ This method is called whenever an operator is encountered with a single rhs comparator, since tensors do not properly them. """ if utils.isvar(left) or utils.isvar(right): return getattr(T, operator)(left, right) elif operator == 'gt': return left > right elif operator == 'ge': return left >= right elif operator == 'lt': return left < right elif operator == 'le': return left <= right elif operator == 'eq': return left == right elif operator == 'neq': return left != right else: # shouldn't ever reach here! raise ValueError( 'Not sure how to handle operator: {0}'.format(operator))
def handle_methods(self, var, method_name): """ This method is called whenever: 1. An array method is requested that doesn't exist for Theano variables (like _.swapaxes()). `handle_methods` is used to supply a replacement method. Note that in this case, `handle_methods` is called directly. 2. A method is requested that DOES exist for Theano variables. In this case, `handle_methods` is called by `handle_functions` prior to calling the method. `handle_methods` is used to supply a replacement function that properly handles the supplied arguments (since they are compliant with the Numpy signature, not the Theano one). """ # if we're not dealing with a Theano variable, nothing to do here. if not utils.isvar(var): return getattr(var, method_name) # ** ======================= Reshape # Theano's reshape requires dim to be in a collection, unlike Numpy. if method_name == 'reshape': def reshape(*args, **kwargs): if args and not isinstance(args[0], (list, tuple)): args = [args] # Theano doesn't handle (), as an arg, which NumPy interprets # as casting length-1 vectors to scalars if args in ( (), ((),) ): if var.ndim > 1: raise ValueError( 'Reshape with `()` as an arg can only be used ' 'with vectors of length 1.') return var[0] else: return var.reshape(*args, **kwargs) return reshape # ** ======================= swapaxes # Theano has no swapaxes method elif method_name == 'swapaxes': def swapaxes(*args, **kwargs): axis1, axis2 = (self.handle_escape(a) for a in args) dims = range(var.ndim) dims[axis1], dims[axis2] = dims[axis2], dims[axis1] return var.dimshuffle(*dims) return swapaxes # ** ======================= astype # Theano doesn't process numpy dtype objects or 'bool' elif method_name == 'astype': def astype(*args, **kwargs): dtype = kwargs.pop('dtype', None) if not dtype: dtype = args[0] if not isinstance(dtype, str): # get numpy dtype objects like np.float32 try: dtype = dtype.__name__ except: raise NotImplementedError( 'Unsupported dtype: {0}'.format(dtype)) if 'bool' in dtype: dtype = 'int8' logger.info('Warning: Theano has no bool type; ' 'upgrading to int8.') return var.astype(dtype) return astype # ** ======================= sort elif method_name == 'sort': def sort_(*args, **kwargs): raise ValueError( 'Calling an array\'s `sort()` method is not supported ' 'because in NumPy it is an inplace operation, but in ' 'Theano it is not. Please use numpy.sort() instead.') return sort_ # ** ======================= reductions elif method_name in ('argmax', 'argmin', 'argsort', 'max', 'mean', 'min', 'norm', 'prod', 'std', 'sum', 'var'): def reduce_(*args, **kwargs): method = getattr(var, method_name) all_args = inspect.getcallargs(method, *args, **kwargs) for k, v in all_args.items(): if v is method.im_self: all_args.pop(k) all_args['axis'] = self.handle_escape(all_args['axis']) return method(**all_args) return reduce_ # ** ======================= anything else # ...Otherwise, try to access the method on the Theano variable else: return getattr(var, method_name)
def _asarray(x): if not utils.isvar(x): return np.asarray(x) else: return x
def alloc(shp, dtype=None): if (not isinstance(shp, (list, tuple)) and not utils.isvar(shp)): shp = [shp] return getattr(T, func.__name__)(shp, dtype)
def handle_functions(self, func): """ Given some function for, return another function. Generally used to exchange NumPy functions for Theano equivalents. """ # ** ======================= first handle functions defined here! if getattr(func, '__module__', None) == __name__: return func elif func in self.context.ignore: return func # ** ======================= special autodiff functions elif func is autodiff.functions.escape: # escapes a variable from Tensor representation return self.handle_escape elif func is autodiff.functions.escaped_call: # call a function on escaped arguments without transforming the AST return self.handle_escaped_call elif func is autodiff.functions.tag: # tag a variable return self.handle_tag # ** ======================= autodiff classes elif isinstance(func, autodiff.symbolic.Symbolic): return func.symfn # ** ======================= __theano_op__ elif hasattr(func, '__theano_op__'): return func.__theano_op__ # ** ======================= array methods (with tensor instances) elif utils.isvar(getattr(func, '__self__', None)): return self.handle_methods(func.__self__, func.__name__) # ** ======================= Theano function elif (getattr(func, '__module__', None) and getattr(func, '__module__').startswith('theano')): return func # ** ======================= type/casting functions elif type(func) is type: if func in(bool, np.bool_, np.bool8): logger.info('Warning: Theano has no bool type; ' 'upgrading to int8.') def bool_(x): return T.neq(x, 0) return bool_ elif func.__name__ in T.basic._cast_mapping.keys(): def cast(x): return T.cast(x, dtype=func.__name__) return cast elif func is float: def float_(x): return T.cast(x, dtype=theano.config.floatX) return float_ elif func is int: def int_(x): return T.cast(x, dtype='int' + theano.config.floatX[-2:]) return int_ elif func is enumerate: def enumerate_(iterable, start=0): if utils.isvar(iterable): raise TypeError( 'Called enumerate() on Tensor {0} but Tensors ' 'do not support iteration. Maybe try escaping ' 'the tensor?'.format(iterable)) else: return enumerate(iterable, start=start) return enumerate_ else: def new_type(*args, **kwargs): try: return self.shadow(func(*args, **kwargs)) except: raise ValueError('Unsupported type: {0}'.format(func)) return new_type # ** ======================= numpy functions elif (getattr(func, '__module__', None) and getattr(func, '__module__').startswith('numpy') or isinstance(func, np.ufunc) or func in (min, max)): # abs if func in (np.abs, np.absolute): return abs # ones/zeros # FIXME submitted a PR to Theano to make syntax more # like Numpy; this change shouldn't be needed afterward. elif func in (np.ones, np.zeros): def alloc(shp, dtype=None): if (not isinstance(shp, (list, tuple)) and not utils.isvar(shp)): shp = [shp] return getattr(T, func.__name__)(shp, dtype) return alloc # handle asarray elif func is np.asarray: def _asarray(x): if not utils.isvar(x): return np.asarray(x) else: return x return _asarray # atleast_1d elif func is np.atleast_1d: def _atleast_1d(x): if x.ndim == 0: return x.dimshuffle('x') else: return x return _atleast_1d # atleast_2d elif func is np.atleast_2d: def _atleast_2d(x): if x.ndim == 0: return x.dimshuffle('x', 'x') elif x.ndim == 1: return x.dimshuffle('x', 0) else: return x return _atleast_2d # atleast_3d elif func is np.atleast_3d: def _atleast_3d(x): if x.ndim == 0: return x.dimshuffle('x', 'x', 'x') elif x.ndim == 1: return x.dimshuffle('x', 'x', 0) elif x.ndim == 2: return x.dimshuffle('x', 0, 1) else: return x return _atleast_3d # get equivalent Theano function elif hasattr(T, func.__name__): return getattr(T, func.__name__) else: raise ValueError( 'Autodiff unsupported function: {0}'.format(func)) # ** ======================= built-ins elif '<built-in' in str(func): # ranges if func in (range, xrange): def range_(*args): return func(*(self.handle_escape(a) for a in args)) return range_ # zip elif func is zip: def zip_(*args): if __builtin__.any(utils.isvar(a) for a in args): raise TypeError( 'Called zip() on Tensor but Tensors ' 'do not support iteration. Maybe try escaping ' 'the tensor?') else: return zip(*args) return zip_ # uniform random numbers (np.random.uniform) elif func is np.random.uniform: def rand_u(low=0.0, high=1.0, size=1): return global_randomstreams.uniform(low=low, high=high, size=size) return rand_u # standard uniform random numbers (np.random.random, np.random.rand) elif func in (np.random.random, np.random.rand): def rand_u(size): return global_randomstreams.uniform(size=size) return rand_u # normal random numbers (np.random.normal) elif func is np.random.normal: def rand_n(loc=0.0, scale=1.0, size=None): return global_randomstreams.normal(avg=loc, std=scale, size=size) return rand_n # standard normal random numbers (np.random.randn) elif func is np.random.randn: def rand_n(*size): return global_randomstreams.normal(size=size) return rand_n # binomial random numbers (np.random.binomial) elif func is np.random.binomial: def rand_b(n, p, size=None): return global_randomstreams.binomial(n=n, p=p, size=size) return rand_b # isinstance elif func is isinstance: def isinstance_(obj, types): escaped_obj = self.handle_escape(obj) if isinstance(escaped_obj, np.ndarray) and obj.ndim == 0: escaped_obj = np.asscalar(escaped_obj) return isinstance(escaped_obj, self.handle_escape(types)) return isinstance_ # inplace list methods elif isinstance(getattr(func, '__self__', None), list): def _inplace_list(*args): # check if the list is shadowing a different one if id(func.__self__) in self.context.shadowed_containers: l = self.context.shadowed_containers[id(func.__self__)] getattr(l, func.__name__)(*args) return func(*args) return _inplace_list # inplace dict methods elif isinstance(getattr(func, '__self__', None), dict): def _inplace_dict(*args): # check if the dict is shadowing a different one if id(func.__self__) in self.context.shadowed_containers: d = self.context.shadowed_containers[id(func.__self__)] getattr(d, func.__name__)(*args) return func(*args) return _inplace_dict # anything else else: return func # ** ======================= A bound method not covered yet # elif isinstance(func, types.MethodType): # return func # ** ======================= Misc elif (('ipdb' in getattr(func, '__module__', '') or 'pdb' in getattr(func, '__module__', '')) and func.__name__ == 'set_trace'): return func # ** ======================= Anything else else: try: return self.context.recompile(func, nested=True) except: if self.context.escape_on_error: logger.warning( 'Error when recompiling {0}. Calling escaped version ' 'because escape_on_error is True.'.format(func)) def escapedfunc(*args, **kwargs): return self.handle_escaped_call(func, *args, **kwargs) return escapedfunc else: raise ValueError('Unsupported function: {0}'.format(func)) # ** ======================= Catchall (shouldn't be called) raise ValueError( 'handle_functions: No case matched function {0}. Something is ' 'wrong -- should not reach this point!'.format(func))