Completed
Push — master ( 5b14e7...41b8fa )
by David
55:16
created

blocks/bricks/base.py (13 issues)

1
import inspect
2
from abc import ABCMeta
3
from collections import OrderedDict
4
from six import wraps
0 ignored issues
show
The name wraps does not seem to exist in module six.
Loading history...
5
from operator import attrgetter
6
from types import MethodType
7
8
import six
9
from six import add_metaclass
10
from theano import tensor
11
from theano.gof import Variable
12
13
from blocks.graph import add_annotation, Annotation
14
from blocks.roles import add_role, PARAMETER, INPUT, OUTPUT
15
from blocks.utils import dict_union, pack, repr_attrs, reraise_as, unpack
16
from blocks.utils.containers import AnnotatingList
17
18
BRICK_DELIMITER = '/'
19
20
21
def create_unbound_method(func, cls):
22
    """Create an unbounded method from a function and a class.
23
24
    Notes
25
    -----
26
    See https://bitbucket.org/gutworth/six/pull-request/64.
27
28
    """
29
    if six.PY2:
30
        return MethodType(func, None, cls)
31
    if six.PY3:
32
        return func
33
34
# Rename built-in property to avoid conflict with Application.property
35
property_ = property
0 ignored issues
show
Coding Style Naming introduced by
The name property_ does not conform to the class naming conventions ([A-Z_][a-zA-Z0-9]+$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
36
37
38
class Parameters(AnnotatingList):
39
    """Adds the PARAMETER role to parameters automatically."""
40
    def __init__(self, brick, *args, **kwargs):
41
        self.brick = brick
42
        super(Parameters, self).__init__(*args, **kwargs)
43
44
    def _setitem(self, key, value):
45
        if isinstance(value, Variable):
46
            add_role(value, PARAMETER)
47
            add_annotation(value, self.brick)
48
49
50
class Children(AnnotatingList):
51
    """Adds the brick to the list of parents of its children."""
52
    def __init__(self, brick, *args, **kwargs):
53
        self.brick = brick
54
        super(Children, self).__init__(*args, **kwargs)
55
56
    def _setitem(self, key, value):
57
        if value is not None:
58
            value.parents.append(self.brick)
59
60
    def _delitem(self, key):
61
        child = self._items[key]
62
        if child is not None:
63
            child.parents.remove(self.brick)
64
65
66
class Application(object):
67
    """An application method belonging to a particular type of brick.
68
69
    The application methods of each :class:`Brick` class are automatically
70
    replaced by an instance of :class:`Application`. This allows us to
71
    store metadata about particular application methods (such as their in-
72
    and outputs) easily.
73
74
    Attributes
75
    ----------
76
    application : callable
77
        The original (unbounded) application function defined on the
78
        :class:`Brick`.
79
    delegate_function : callable
80
        A function that takes a :class:`Brick` instance as an argument and
81
        returns a :class:`BoundApplication` object to which attribute
82
        requests should be routed.
83
    properties : :obj:`dict` (:obj:`str`, :obj:`callable`)
84
        A dictionary of property getters that should be called when an
85
        attribute with the given name is requested.
86
    instances : :obj:`dict` (:class:`Brick`, :class:`BoundApplication`)
87
        A record of bound application instances created by the descriptor
88
        protocol.
89
    call_stack : :obj:`list` of :class:`Brick`
90
        The call stack of brick application methods. Used to check whether
91
        the current call was made by a parent brick.
92
    brick : type
93
        The brick class to which this instance belongs.
94
95
    Raises
96
    ------
97
    ValueError
98
        If a brick's application method is applied by another brick which
99
        does not list the former as a child.
100
    ValueError
101
        If the application method's inputs and/or outputs don't match with
102
        the function signature or the values returned (respectively).
103
104
    Notes
105
    -----
106
    When a :class:`Brick` is instantiated and its application method (i.e.
107
    an instance of this class) requested, the descriptor protocol (through
108
    the :meth:`__get__` method) automatically instantiates a
109
    :class:`BoundApplication` class and returns this. This bound
110
    application class can be used to store application information
111
    particular to a brick instance. Any attributes unknown to the bounded
112
    application are automatically routed to the application that
113
    instantiated it.
114
115
    """
116
    call_stack = []
117
118
    def __init__(self, application_function):
119
        self.__doc__ = application_function.__doc__
120
        self._application_function = application_function
121
        self.application_name = application_function.__name__
122
        self.delegate_function = None
123
        self.properties = {}
124
125
    @property
126
    def application_function(self):
127
        if hasattr(self, '_application_function'):
128
            return self._application_function
129
        return getattr(self.brick, '_' + self.application_name)
130
131
    def property(self, name):
132
        """Decorator to make application properties.
133
134
        Parameters
135
        ----------
136
        name : str
137
            The name the property should take.
138
139
        Examples
140
        --------
141
        >>> class Foo(Brick):
142
        ...     @application
143
        ...     def apply(self, x):
144
        ...         return x + 1
145
        ...
146
        ...     @apply.property('inputs')
147
        ...     def apply_inputs(self):
148
        ...         return ['foo', 'bar']
149
        >>> foo = Foo()
150
        >>> foo.apply.inputs
151
        ['foo', 'bar']
152
153
        """
154
        if not isinstance(name, six.string_types):
155
            raise ValueError
156
157
        def wrap_property(application_property):
158
            self.properties[name] = application_property.__name__
159
            return application_property
160
        return wrap_property
161
162
    def delegate(self, f):
163
        """Decorator to assign a delegate application.
164
165
        An application method can assign a delegate application. Whenever
166
        an attribute is not available, it will be requested from the
167
        delegate instead.
168
169
        Examples
170
        --------
171
        >>> class Foo(Brick):
172
        ...     @application(outputs=['baz'])
173
        ...     def apply(self, x):
174
        ...         return x + 1
175
        ...
176
        ...     @apply.property('inputs')
177
        ...     def apply_inputs(self):
178
        ...         return ['foo', 'bar']
179
        >>> class Bar(Brick):
180
        ...     def __init__(self, foo):
181
        ...         self.foo = foo
182
        ...
183
        ...     @application(outputs=['foo'])
184
        ...     def apply(self, x):
185
        ...         return x + 1
186
        ...
187
        ...     @apply.delegate
188
        ...     def apply_delegate(self):
189
        ...         return self.foo.apply
190
        >>> foo = Foo()
191
        >>> bar = Bar(foo)
192
        >>> bar.apply.outputs
193
        ['foo']
194
        >>> bar.apply.inputs
195
        ['foo', 'bar']
196
197
        """
198
        self.delegate_function = f.__name__
199
        return f
200
201
    def __get__(self, instance, owner):
202
        """Instantiate :class:`BoundApplication` for each :class:`Brick`."""
203
        if instance is None:
204
            return self
205
        if not hasattr(instance, "_bound_applications"):
206
            instance._bound_applications = {}
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _bound_applications was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
207
        key = "{}.{}".format(self.brick.__name__, self.application_name)
208
        return instance._bound_applications.setdefault(
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _bound_applications was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
209
            key, BoundApplication(self, instance))
210
211
    def __getattr__(self, name):
212
        # Mimic behavior of properties
213
        if 'properties' in self.__dict__ and name in self.properties:
214
            return property(create_unbound_method(
215
                getattr(self, self.properties[name]), self.brick))
216
        raise AttributeError
217
218
    def __setattr__(self, name, value):
219
        # Mimic behavior of read-only properties
220
        if 'properties' in self.__dict__ and name in self.properties:
221
            raise AttributeError("can't set attribute")
222
        super(Application, self).__setattr__(name, value)
223
224
    @property_
225
    def inputs(self):
226
        return self._inputs
227
228
    @inputs.setter
229
    def inputs(self, inputs):
230
        args_names, varargs_name, _, _ = inspect.getargspec(
231
            self.application_function)
232
        if not all(input_ in args_names + [varargs_name] for input_ in inputs):
233
            raise ValueError("Unexpected inputs")
234
        self._inputs = inputs
235
236
    @property_
237
    def name(self):
238
        return self.application_name
239
240
    def __call__(self, brick, *args, **kwargs):
241
        if not isinstance(brick, Brick) and six.PY2:
242
            raise TypeError
243
        bound_application = self.__get__(brick, brick.__class__)
244
        return self.apply(bound_application, *args, **kwargs)
245
246
    def apply(self, bound_application, *args, **kwargs):
247
        as_dict = kwargs.pop('as_dict', False)
248
        as_list = kwargs.pop('as_list', False)
249
        if as_list and as_dict:
250
            raise ValueError
251
252
        brick = bound_application.brick
253
254
        # Find the names of the inputs to the application method
255
        args_names, varargs_name, _, _ = inspect.getargspec(
256
            self.application_function)
257
        args_names = args_names[1:]
258
259
        # Construct the ApplicationCall, used to store data in for this call
260
        call = ApplicationCall(bound_application)
261
        args = list(args)
262
        if 'application' in args_names:
263
            args.insert(args_names.index('application'), bound_application)
264
        if 'application_call' in args_names:
265
            args.insert(args_names.index('application_call'), call)
266
267
        # Allocate before applying, and optionally initialize
268
        if not brick.allocated:
269
            brick.allocate()
270
271
        # Annotate all the input variables which are Theano variables
272
273
        for i, input_ in enumerate(args):
274
            if isinstance(input_, tensor.Variable):
275
                if i < len(args_names):
276
                    name = args_names[i]
277
                else:
278
                    name = "{}_{}".format(varargs_name, i - len(args_names))
279
                args[i] = copy_and_tag(input_, brick, call, INPUT,
280
                                       self.name, name)
281
        for name, input_ in kwargs.items():
282
            if isinstance(input_, tensor.Variable):
283
                kwargs[name] = copy_and_tag(input_, brick, call, INPUT,
284
                                            self.name, name)
285
286
        # Run the application method on the annotated variables
287
        last_brick = self.call_stack[-1] if self.call_stack else None
288
        if (last_brick and brick is not last_brick and
289
                brick not in last_brick.children):
290
            raise ValueError('Brick ' + str(self.call_stack[-1]) + ' tries '
291
                             'to call brick ' + str(self.brick) + ' which '
292
                             'is not in the list of its children. This could '
293
                             'be caused because an @application decorator is '
294
                             'missing.')
295
        self.call_stack.append(brick)
296
        try:
297
            outputs = self.application_function(brick, *args, **kwargs)
298
            outputs = pack(outputs)
299
        finally:
300
            self.call_stack.pop()
301
302
        # Rename and annotate output variables
303
        for i, output in enumerate(outputs):
304
            if isinstance(output, tensor.Variable):
305
                try:
306
                    name = bound_application.outputs[i]
307
                except AttributeError:
308
                    name = "output_{}".format(i)
309
                except IndexError:
310
                    reraise_as(ValueError("Unexpected outputs"))
311
                # TODO Tag with dimensions, axes, etc. for error-checking
312
                outputs[i] = copy_and_tag(outputs[i], brick, call,
313
                                          OUTPUT, self.name, name)
314
315
        # Return values
316
        if as_list:
317
            return outputs
318
        if as_dict:
319
            return OrderedDict(zip(bound_application.outputs, outputs))
320
        return unpack(outputs)
321
322
    # Application instances are used instead of usual methods in bricks.
323
    # The usual methods are not pickled per-se, similarly to classes
324
    # and modules. Instead, a reference to the method is put into the pickle.
325
    # Here, we ensure the same behaviour for Application instances.
326
    def __reduce__(self):
327
        return (getattr, (self.brick, self.application_name))
328
329
330
class BoundApplication(object):
331
    """An application method bound to a :class:`Brick` instance."""
332
    def __init__(self, application, brick):
0 ignored issues
show
Comprehensibility Bug introduced by
application is re-defining a name which is already available in the outer-scope (previously defined on line 911).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
333
        self.application = application
334
        self.brick = brick
335
336
    def __getattr__(self, name):
337
        # Prevent infinite loops
338
        if name == 'application':
339
            raise AttributeError
340
        # These always belong to the parent (the unbound application)
341
        if name in ('delegate_function', 'properties'):
342
            return getattr(self.application, name)
343
        if name in self.properties.values():
344
            return getattr(self.application.brick, name)
345
        if name in self.properties:
346
            return getattr(self, self.properties[name])(self.brick)
347
        # First try the parent (i.e. class level), before trying the delegate
348
        try:
349
            return getattr(self.application, name)
350
        except AttributeError:
351
            if self.delegate_function:
352
                return getattr(getattr(self.brick,
353
                                       self.delegate_function)(),
354
                               name)
355
            raise
356
357
    @property
358
    def name(self):
359
        return self.application.name
360
361
    def __call__(self, *args, **kwargs):
362
        return self.application.apply(self, *args, **kwargs)
363
364
365
def rename_function(function, new_name):
366
    old_name = function.__name__
367
    function.__name__ = new_name
368
    if six.PY3:
369
        function.__qualname__ = \
370
            function.__qualname__[:-len(old_name)] + new_name
371
    return function
372
373
374
class _Brick(ABCMeta):
375
    """Metaclass which attaches brick instances to the applications.
376
377
    In addition picklability of :class:`Application` objects is ensured.
378
    This means that :class:`Application` objects can not be added to a
379
    brick class after it is created. To allow adding application methods
380
    programatically, the following hook is supported: the class namespace
381
    is searched for `decorators` attribute, which can contain a
382
    list of functions to be applied to the namespace of the class being
383
    created. These functions can arbitratily modify this namespace.
384
385
    """
386
    def __new__(mcs, name, bases, namespace):
387
        decorators = namespace.get('decorators', [])
388
        for decorator in decorators:
389
            decorator(mcs, name, bases, namespace)
390
        for attr in list(namespace.values()):
391
            if (isinstance(attr, Application) and
392
                    hasattr(attr, '_application_function')):
393
                namespace['_' + attr.application_name] = \
394
                    rename_function(attr._application_function,
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _application_function was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
395
                                    '_' + attr.application_name)
396
                del attr._application_function
397
        brick = super(_Brick, mcs).__new__(mcs, name, bases, namespace)
398
        for attr in namespace.values():
399
            if isinstance(attr, Application):
400
                attr.brick = brick
401
        return brick
402
403
404
@add_metaclass(_Brick)
405
class Brick(Annotation):
406
    """A brick encapsulates Theano operations with parameters.
407
408
    A brick goes through the following stages:
409
410
    1. Construction: The call to :meth:`__init__` constructs a
411
       :class:`Brick` instance with a name and creates any child bricks as
412
       well.
413
    2. Allocation of parameters:
414
415
       a) Allocation configuration of children: The
416
          :meth:`push_allocation_config` method configures any children of
417
          this block.
418
       b) Allocation: The :meth:`allocate` method allocates the shared
419
          Theano variables required for the parameters. Also allocates
420
          parameters for all children.
421
422
    3. The following can be done in either order:
423
424
       a) Application: By applying the brick to a set of Theano
425
          variables a part of the computational graph of the final model is
426
          constructed.
427
       b) The initialization of parameters:
428
429
          1. Initialization configuration of children: The
430
             :meth:`push_initialization_config` method configures any
431
             children of this block.
432
          2. Initialization: This sets the initial values of the
433
             parameters by a call to :meth:`initialize`, which is needed
434
             to call the final compiled Theano function.  Also initializes
435
             all children.
436
437
    Not all stages need to be called explicitly. Step 3(a) will
438
    automatically allocate the parameters if needed. Similarly, step
439
    3(b.2) and 2(b) will automatically perform steps 3(b.1) and 2(a) if
440
    needed. They only need to be called separately if greater control is
441
    required. The only two methods which always need to be called are an
442
    application method to construct the computational graph, and the
443
    :meth:`initialize` method in order to initialize the parameters.
444
445
    At each different stage, a brick might need a certain set of
446
    configuration settings. All of these settings can be passed to the
447
    :meth:`__init__` constructor. However, by default many bricks support
448
    *lazy initialization*. This means that the configuration settings can
449
    be set later.
450
451
    .. note::
452
453
       Some arguments to :meth:`__init__` are *always* required, even when
454
       lazy initialization is enabled. Other arguments must be given before
455
       calling :meth:`allocate`, while others yet only need to be given in
456
       order to call :meth:`initialize`. Always read the documentation of
457
       each brick carefully.
458
459
    Lazy initialization can be turned off by setting ``Brick.lazy =
460
    False``. In this case, there is no need to call :meth:`initialize`
461
    manually anymore, but all the configuration must be passed to the
462
    :meth:`__init__` method.
463
464
    Parameters
465
    ----------
466
    name : str, optional
467
        The name of this brick. This can be used to filter the application
468
        of certain modifications by brick names. By default, the brick
469
        receives the name of its class (lowercased).
470
471
    Attributes
472
    ----------
473
    name : str
474
        The name of this brick.
475
    print_shapes : bool
476
        ``False`` by default. If ``True`` it logs the shapes of all the
477
        input and output variables, which can be useful for debugging.
478
    parameters : list of :class:`~tensor.TensorSharedVariable` and ``None``
479
        After calling the :meth:`allocate` method this attribute will be
480
        populated with the shared variables storing this brick's
481
        parameters. Allows for ``None`` so that parameters can always be
482
        accessed at the same index, even if some parameters are only
483
        defined given a particular configuration.
484
    children : list of bricks
485
        The children of this brick.
486
    allocated : bool
487
        ``False`` if :meth:`allocate` has not been called yet. ``True``
488
        otherwise.
489
    initialized : bool
490
        ``False`` if :meth:`allocate` has not been called yet. ``True``
491
        otherwise.
492
    allocation_config_pushed : bool
493
        ``False`` if :meth:`allocate` or :meth:`push_allocation_config`
494
        hasn't been called yet. ``True`` otherwise.
495
    initialization_config_pushed : bool
496
        ``False`` if :meth:`initialize` or
497
        :meth:`push_initialization_config` hasn't been called yet. ``True``
498
        otherwise.
499
500
    Notes
501
    -----
502
    To provide support for lazy initialization, apply the :meth:`lazy`
503
    decorator to the :meth:`__init__` method.
504
505
    Brick implementations *must* call the :meth:`__init__` constructor of
506
    their parent using `super(BlockImplementation,
507
    self).__init__(**kwargs)` at the *beginning* of the overriding
508
    `__init__`.
509
510
    The methods :meth:`_allocate` and :meth:`_initialize` need to be
511
    overridden if the brick needs to allocate shared variables and
512
    initialize their values in order to function.
513
514
    A brick can have any number of methods which apply the brick on Theano
515
    variables. These methods should be decorated with the
516
    :func:`application` decorator.
517
518
    If a brick has children, they must be listed in the :attr:`children`
519
    attribute. Moreover, if the brick wants to control the configuration of
520
    its children, the :meth:`_push_allocation_config` and
521
    :meth:`_push_initialization_config` methods need to be overridden.
522
523
    Examples
524
    --------
525
    Most bricks have lazy initialization enabled.
526
527
    >>> import theano
528
    >>> from blocks.initialization import IsotropicGaussian, Constant
529
    >>> from blocks.bricks import Linear
530
    >>> linear = Linear(input_dim=5, output_dim=3,
531
    ...                 weights_init=IsotropicGaussian(),
532
    ...                 biases_init=Constant(0))
533
    >>> x = theano.tensor.vector()
534
    >>> linear.apply(x)  # Calls linear.allocate() automatically
535
    linear_apply_output
536
    >>> linear.initialize()  # Initializes the weight matrix
537
538
    """
539
    #: See :attr:`Brick.print_shapes`
540
    print_shapes = False
541
542
    def __init__(self, name=None, children=None):
543
        if name is None:
544
            name = self.__class__.__name__.lower()
545
546
        if children is None:
547
            children = []
548
549
        self.name = name
550
        self.children = children
551
        self.parents = []
552
553
        self.allocated = False
554
        self.allocation_config_pushed = False
555
        self.initialized = False
556
        self.initialization_config_pushed = False
557
        super(Brick, self).__init__()
558
559
    def __repr__(self):
560
        return repr_attrs(self, 'name')
561
562
    @property
563
    def parameters(self):
564
        return self._parameters
565
566
    @parameters.setter
567
    def parameters(self, value):
568
        self._parameters = Parameters(self, value)
569
570
    @property
571
    def children(self):
572
        return self._children
573
574
    @children.setter
575
    def children(self, value):
576
        self._children = Children(self, value)
577
578
    def allocate(self):
579
        """Allocate shared variables for parameters.
580
581
        Based on the current configuration of this :class:`Brick` create
582
        Theano shared variables to store the parameters.  After allocation,
583
        parameters are accessible through the :attr:`parameters` attribute.
584
585
        This method calls the :meth:`allocate` method of all children
586
        first, allowing the :meth:`_allocate` method to override the
587
        parameters of the children if needed.
588
589
        Raises
590
        ------
591
        ValueError
592
            If the configuration of this brick is insufficient to determine
593
            the number of parameters or their dimensionality to be
594
            initialized.
595
596
        Notes
597
        -----
598
        This method sets the :attr:`parameters` attribute to an empty list.
599
        This is in order to ensure that calls to this method completely
600
        reset the parameters.
601
602
        """
603
        if hasattr(self, 'allocation_args'):
604
            missing_config = [arg for arg in self.allocation_args
0 ignored issues
show
The Instance of Brick does not seem to have a member named allocation_args.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
605
                              if getattr(self, arg) is NoneAllocation]
606
            if missing_config:
607
                raise ValueError('allocation config not set: '
608
                                 '{}'.format(', '.join(missing_config)))
609
        if not self.allocation_config_pushed:
610
            self.push_allocation_config()
611
        for child in self.children:
612
            child.allocate()
613
        self.parameters = []
614
        self._allocate()
615
        self.allocated = True
616
617
    def _allocate(self):
618
        """Brick implementation of parameter initialization.
619
620
        Implement this if your brick needs to allocate its parameters.
621
622
        .. warning::
623
624
           This method should never be called directly. Call
625
           :meth:`initialize` instead.
626
627
        """
628
        pass
629
630
    def initialize(self):
631
        """Initialize parameters.
632
633
        Intialize parameters, such as weight matrices and biases.
634
635
        Notes
636
        -----
637
        If the brick has not allocated its parameters yet, this method will
638
        call the :meth:`allocate` method in order to do so.
639
640
        """
641
        if hasattr(self, 'initialization_args'):
642
            missing_config = [arg for arg in self.initialization_args
0 ignored issues
show
The Instance of Brick does not seem to have a member named initialization_args.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
643
                              if getattr(self, arg) is NoneInitialization]
644
            if missing_config:
645
                raise ValueError('initialization config not set: '
646
                                 '{}'.format(', '.join(missing_config)))
647
        if not self.allocated:
648
            self.allocate()
649
        if not self.initialization_config_pushed:
650
            self.push_initialization_config()
651
        for child in self.children:
652
            child.initialize()
653
        self._initialize()
654
        self.initialized = True
655
656
    def _initialize(self):
657
        """Brick implementation of parameter initialization.
658
659
        Implement this if your brick needs to initialize its parameters.
660
661
        .. warning::
662
663
           This method should never be called directly. Call
664
           :meth:`initialize` instead.
665
666
        """
667
        pass
668
669
    def push_allocation_config(self):
670
        """Push the configuration for allocation to child bricks.
671
672
        Bricks can configure their children, based on their own current
673
        configuration. This will be automatically done by a call to
674
        :meth:`allocate`, but if you want to override the configuration of
675
        child bricks manually, then you can call this function manually.
676
677
        """
678
        self._push_allocation_config()
679
        self.allocation_config_pushed = True
680
        for child in self.children:
681
            try:
682
                child.push_allocation_config()
683
            except Exception:
684
                self.allocation_config_pushed = False
685
                raise
686
687
    def _push_allocation_config(self):
688
        """Brick implementation of configuring child before allocation.
689
690
        Implement this if your brick needs to set the configuration of its
691
        children before allocation.
692
693
        .. warning::
694
695
           This method should never be called directly. Call
696
           :meth:`push_allocation_config` instead.
697
698
        """
699
        pass
700
701
    def push_initialization_config(self):
702
        """Push the configuration for initialization to child bricks.
703
704
        Bricks can configure their children, based on their own current
705
        configuration. This will be automatically done by a call to
706
        :meth:`initialize`, but if you want to override the configuration
707
        of child bricks manually, then you can call this function manually.
708
709
        """
710
        self._push_initialization_config()
711
        self.initialization_config_pushed = True
712
        for child in self.children:
713
            try:
714
                child.push_initialization_config()
715
            except Exception:
716
                self.initialization_config_pushed = False
717
                raise
718
719
    def _push_initialization_config(self):
720
        """Brick implementation of configuring child before initialization.
721
722
        Implement this if your brick needs to set the configuration of its
723
        children before initialization.
724
725
        .. warning::
726
727
           This method should never be called directly. Call
728
           :meth:`push_initialization_config` instead.
729
730
        """
731
        pass
732
733
    def get_dim(self, name):
734
        """Get dimension of an input/output variable of a brick.
735
736
        Parameters
737
        ----------
738
        name : str
739
            The name of the variable.
740
741
        """
742
        raise ValueError("No dimension information for {} available"
743
                         .format(name))
744
745
    def get_dims(self, names):
746
        """Get list of dimensions for a set of input/output variables.
747
748
        Parameters
749
        ----------
750
        names : list
751
            The variable names.
752
753
        Returns
754
        -------
755
        dims : list
756
            The dimensions of the sources.
757
758
        """
759
        return [self.get_dim(name) for name in names]
760
761
    def get_unique_path(self):
762
        """Returns unique path to this brick in the application graph."""
763
        if self.parents:
764
            parent = min(self.parents, key=attrgetter('name'))
765
            return parent.get_unique_path() + [self]
766
        else:
767
            return [self]
768
769
    def get_hierarchical_name(self, parameter, delimiter=BRICK_DELIMITER):
770
        """Return hierarhical name for a parameter.
771
772
        Returns a path of the form ``brick1/brick2/brick3.parameter1``. The
773
        delimiter is configurable.
774
775
        Parameters
776
        ----------
777
        delimiter : str
778
            The delimiter used to separate brick names in the path.
779
780
        """
781
        return '{}.{}'.format(
782
            delimiter.join(
783
                [""] + [brick.name for brick in
784
                        self.get_unique_path()]),
785
            parameter.name)
786
787
788
def args_to_kwargs(args, f):
789
    arg_names, vararg_names, _, _ = inspect.getargspec(f)
790
    return dict((arg_name, arg) for arg_name, arg
791
                in zip(arg_names + [vararg_names], args))
792
793
794
class LazyNone(object):
795
    def __init__(self, name):
796
        self.name = name
797
798
    def __repr__(self):
799
        return self.name
800
801
    def __bool__(self):
802
        return False
803
804
    __nonzero__ = __bool__
805
806
NoneAllocation = LazyNone('NoneAllocation')
0 ignored issues
show
Coding Style Naming introduced by
The name NoneAllocation does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
807
NoneInitialization = LazyNone('NoneInitialization')
0 ignored issues
show
Coding Style Naming introduced by
The name NoneInitialization does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
808
809
810
def lazy(allocation=None, initialization=None):
811
    """Makes the initialization lazy.
812
813
    This decorator allows the user to define positional arguments which
814
    will not be needed until the allocation or initialization stage of the
815
    brick. If these arguments are not passed, it will automatically replace
816
    them with a custom ``None`` object. It is assumed that the missing
817
    arguments can be set after initialization by setting attributes with
818
    the same name.
819
820
    Parameters
821
    ----------
822
    allocation : list
823
        A list of argument names that are needed for allocation.
824
    initialization : list
825
        A list of argument names that are needed for initialization.
826
827
    Examples
828
    --------
829
    >>> class SomeBrick(Brick):
830
    ...     @lazy(allocation=['a'], initialization=['b'])
831
    ...     def __init__(self, a, b, c='c', d=None):
832
    ...         print(a, b, c, d)
833
    >>> brick = SomeBrick('a')
834
    a NoneInitialization c None
835
    >>> brick = SomeBrick(d='d', b='b')
836
    NoneAllocation b c d
837
838
    """
839
    if not allocation:
840
        allocation = []
841
    if not initialization:
842
        initialization = []
843
844
    def lazy_wrapper(init):
845
        def lazy_init(*args, **kwargs):
846
            self = args[0]
847
            self.allocation_args = (getattr(self, 'allocation_args',
848
                                            []) + allocation)
849
            self.initialization_args = (getattr(self, 'initialization_args',
850
                                                []) + initialization)
851
            kwargs = dict_union(args_to_kwargs(args, init), kwargs)
852
            for allocation_arg in allocation:
853
                kwargs.setdefault(allocation_arg, NoneAllocation)
854
            for initialization_arg in initialization:
855
                kwargs.setdefault(initialization_arg, NoneInitialization)
856
            return init(**kwargs)
857
        wraps(init)(lazy_init)
858
        return lazy_init
859
    return lazy_wrapper
860
861
862
class ApplicationCall(Annotation):
863
    """A link between the variable tags and bricks.
864
865
    The application call can be used to attach to an apply call auxiliary
866
    variables (e.g. monitors or regularizers) that do not form part of the
867
    main computation graph.
868
869
    The application call object is created before the call to the
870
    application method and can be accessed by specifying an
871
    application_call argument.
872
873
    Also see :class:`.Annotation`.
874
875
    Parameters
876
    ----------
877
    application : :class:`BoundApplication` instance
878
        The bound application (i.e. belong to a brick instance) object
879
        being called
880
881
    Examples
882
    --------
883
    >>> class Foo(Brick):
884
    ...     @application
885
    ...     def apply(self, x, application_call):
886
    ...         application_call.add_auxiliary_variable(x.mean())
887
    ...         return x + 1
888
    >>> x = tensor.vector()
889
    >>> y = Foo().apply(x)
890
    >>> from blocks.filter import get_application_call
891
    >>> get_application_call(y) # doctest: +ELLIPSIS
892
    <blocks.bricks.base.ApplicationCall object at ...>
893
894
    """
895
    def __init__(self, application):
0 ignored issues
show
Comprehensibility Bug introduced by
application is re-defining a name which is already available in the outer-scope (previously defined on line 911).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
896
        self.application = application
897
        self.metadata = {}
898
        super(ApplicationCall, self).__init__()
899
900
    def add_auxiliary_variable(self, variable, roles=None, name=None):
901
        if name:
902
            variable.name = _variable_name(
903
                self.application.brick.name, self.application.name, name)
904
            variable.tag.name = name
905
            name = None
906
        add_annotation(variable, self.application.brick)
907
        return super(ApplicationCall, self).add_auxiliary_variable(
908
            variable, roles, name)
909
910
911
def application(*args, **kwargs):
912
    r"""Decorator for methods that apply a brick to inputs.
913
914
    Parameters
915
    ----------
916
    \*args, optional
917
        The application method to wrap.
918
    \*\*kwargs, optional
919
        Attributes to attach to this application.
920
921
    Notes
922
    -----
923
    This decorator replaces application methods with :class:`Application`
924
    instances. It also sets the attributes given as keyword arguments to
925
    the decorator.
926
927
    Note that this decorator purposely does not wrap the original method
928
    using e.g. :func:`~functools.wraps` or
929
    :func:`~functools.update_wrapper`, since that would make the class
930
    impossible to pickle (see notes at :class:`Application`).
931
932
    Examples
933
    --------
934
    >>> class Foo(Brick):
935
    ...     @application(inputs=['x'], outputs=['y'])
936
    ...     def apply(self, x):
937
    ...         return x + 1
938
    ...     @application
939
    ...     def other_apply(self, x):
940
    ...         return x - 1
941
    >>> foo = Foo()
942
    >>> Foo.apply.inputs
943
    ['x']
944
    >>> foo.apply.outputs
945
    ['y']
946
    >>> Foo.other_apply # doctest: +ELLIPSIS
947
    <blocks.bricks.base.Application object at ...>
948
949
    """
950
    if not ((args and not kwargs) or (not args and kwargs)):
951
        raise ValueError
952
    if args:
953
        application_function, = args
954
        application = Application(application_function)
0 ignored issues
show
Comprehensibility Bug introduced by
application is re-defining a name which is already available in the outer-scope (previously defined on line 911).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
955
        return application
956
    else:
957
        def wrap_application(application_function):
958
            application = Application(application_function)
0 ignored issues
show
Comprehensibility Bug introduced by
application is re-defining a name which is already available in the outer-scope (previously defined on line 911).

It is generally a bad practice to shadow variables from the outer-scope. In most cases, this is done unintentionally and might lead to unexpected behavior:

param = 5

class Foo:
    def __init__(self, param):   # "param" would be flagged here
        self.param = param
Loading history...
959
            for key, value in kwargs.items():
960
                setattr(application, key, value)
961
            return application
962
        return wrap_application
963
964
965
def _variable_name(brick_name, application_name, name):
966
    return "{}_{}_{}".format(brick_name, application_name, name)
967
968
969
def copy_and_tag(variable, brick, call, role, application_name, name):
970
    """Helper method to copy a variable and annotate it."""
971
    copy = variable.copy()
972
    # Theano name
973
    copy.name = _variable_name(brick.name, application_name, name)
974
    add_annotation(copy, brick)
975
    add_annotation(copy, call)
976
    # Blocks name
977
    copy.tag.name = name
978
    add_role(copy, role)
979
    return copy
980