예제 #1
0
    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)))
예제 #2
0
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)
예제 #3
0
 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)
예제 #4
0
 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)
예제 #5
0
 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
예제 #6
0
 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
예제 #7
0
 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
예제 #8
0
 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
예제 #9
0
 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))
예제 #10
0
    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)
예제 #11
0
 def _asarray(x):
     if not utils.isvar(x):
         return np.asarray(x)
     else:
         return x
예제 #12
0
 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)
예제 #13
0
    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))