Completed
Pull Request — master (#980)
by David
01:21
created

blocks.bricks.Brick.initialize()   F

Complexity

Conditions 9

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 9
dl 0
loc 26
rs 3
1
import inspect
2
from abc import ABCMeta
3
from collections import OrderedDict
4
from functools import wraps
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
19
def create_unbound_method(func, cls):
20
    """Create an unbounded method from a function and a class.
21
22
    Notes
23
    -----
24
    See https://bitbucket.org/gutworth/six/pull-request/64.
25
26
    """
27
    if six.PY2:
28
        return MethodType(func, None, cls)
29
    if six.PY3:
30
        return func
31
32
# Rename built-in property to avoid conflict with Application.property
33
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...
34
35
36
class Parameters(AnnotatingList):
37
    """Adds the PARAMETER role to parameters automatically."""
38
    def __init__(self, brick, *args, **kwargs):
39
        self.brick = brick
40
        super(Parameters, self).__init__(*args, **kwargs)
41
42
    def _setitem(self, key, value):
43
        if isinstance(value, Variable):
44
            add_role(value, PARAMETER)
45
            add_annotation(value, self.brick)
46
47
48
class Children(AnnotatingList):
49
    """Adds the brick to the list of parents of its children."""
50
    def __init__(self, brick, *args, **kwargs):
51
        self.brick = brick
52
        super(Children, self).__init__(*args, **kwargs)
53
54
    def _setitem(self, key, value):
55
        if value is not None:
56
            value.parents.append(self.brick)
57
58
    def _delitem(self, key):
59
        child = self._items[key]
60
        if child is not None:
61
            child.parents.remove(self.brick)
62
63
64
class Application(object):
65
    """An application method belonging to a particular type of brick.
66
67
    The application methods of each :class:`Brick` class are automatically
68
    replaced by an instance of :class:`Application`. This allows us to
69
    store metadata about particular application methods (such as their in-
70
    and outputs) easily.
71
72
    Attributes
73
    ----------
74
    application : callable
75
        The original (unbounded) application function defined on the
76
        :class:`Brick`.
77
    delegate_function : callable
78
        A function that takes a :class:`Brick` instance as an argument and
79
        returns a :class:`BoundApplication` object to which attribute
80
        requests should be routed.
81
    properties : :obj:`dict` (:obj:`str`, :obj:`callable`)
82
        A dictionary of property getters that should be called when an
83
        attribute with the given name is requested.
84
    instances : :obj:`dict` (:class:`Brick`, :class:`BoundApplication`)
85
        A record of bound application instances created by the descriptor
86
        protocol.
87
    call_stack : :obj:`list` of :class:`Brick`
88
        The call stack of brick application methods. Used to check whether
89
        the current call was made by a parent brick.
90
    brick : type
91
        The brick class to which this instance belongs.
92
93
    Raises
94
    ------
95
    ValueError
96
        If a brick's application method is applied by another brick which
97
        does not list the former as a child.
98
    ValueError
99
        If the application method's inputs and/or outputs don't match with
100
        the function signature or the values returned (respectively).
101
102
    Notes
103
    -----
104
    When a :class:`Brick` is instantiated and its application method (i.e.
105
    an instance of this class) requested, the descriptor protocol (through
106
    the :meth:`__get__` method) automatically instantiates a
107
    :class:`BoundApplication` class and returns this. This bound
108
    application class can be used to store application information
109
    particular to a brick instance. Any attributes unknown to the bounded
110
    application are automatically routed to the application that
111
    instantiated it.
112
113
    """
114
    call_stack = []
115
116
    def __init__(self, application_function):
117
        self.__doc__ = application_function.__doc__
118
        self._application_function = application_function
119
        self.application_name = application_function.__name__
120
        self.delegate_function = None
121
        self.properties = {}
122
123
    @property
124
    def application_function(self):
125
        if hasattr(self, '_application_function'):
126
            return self._application_function
127
        return getattr(self.brick, '_' + self.application_name)
128
129
    def property(self, name):
130
        """Decorator to make application properties.
131
132
        Parameters
133
        ----------
134
        name : str
135
            The name the property should take.
136
137
        Examples
138
        --------
139
        >>> class Foo(Brick):
140
        ...     @application
141
        ...     def apply(self, x):
142
        ...         return x + 1
143
        ...
144
        ...     @apply.property('inputs')
145
        ...     def apply_inputs(self):
146
        ...         return ['foo', 'bar']
147
        >>> foo = Foo()
148
        >>> foo.apply.inputs
149
        ['foo', 'bar']
150
151
        """
152
        if not isinstance(name, six.string_types):
153
            raise ValueError
154
155
        def wrap_property(application_property):
156
            self.properties[name] = application_property.__name__
157
            return application_property
158
        return wrap_property
159
160
    def delegate(self, f):
161
        """Decorator to assign a delegate application.
162
163
        An application method can assign a delegate application. Whenever
164
        an attribute is not available, it will be requested from the
165
        delegate instead.
166
167
        Examples
168
        --------
169
        >>> class Foo(Brick):
170
        ...     @application(outputs=['baz'])
171
        ...     def apply(self, x):
172
        ...         return x + 1
173
        ...
174
        ...     @apply.property('inputs')
175
        ...     def apply_inputs(self):
176
        ...         return ['foo', 'bar']
177
        >>> class Bar(Brick):
178
        ...     def __init__(self, foo):
179
        ...         self.foo = foo
180
        ...
181
        ...     @application(outputs=['foo'])
182
        ...     def apply(self, x):
183
        ...         return x + 1
184
        ...
185
        ...     @apply.delegate
186
        ...     def apply_delegate(self):
187
        ...         return self.foo.apply
188
        >>> foo = Foo()
189
        >>> bar = Bar(foo)
190
        >>> bar.apply.outputs
191
        ['foo']
192
        >>> bar.apply.inputs
193
        ['foo', 'bar']
194
195
        """
196
        self.delegate_function = f.__name__
197
        return f
198
199
    def __get__(self, instance, owner):
200
        """Instantiate :class:`BoundApplication` for each :class:`Brick`."""
201
        if instance is None:
202
            return self
203
        if not hasattr(instance, "_bound_applications"):
204
            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...
205
        key = "{}.{}".format(self.brick.__name__, self.application_name)
206
        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...
207
            key, BoundApplication(self, instance))
208
209
    def __getattr__(self, name):
210
        # Mimic behavior of properties
211
        if 'properties' in self.__dict__ and name in self.properties:
212
            return property(create_unbound_method(
213
                getattr(self, self.properties[name]), self.brick))
214
        raise AttributeError
215
216
    def __setattr__(self, name, value):
217
        # Mimic behavior of read-only properties
218
        if 'properties' in self.__dict__ and name in self.properties:
219
            raise AttributeError("can't set attribute")
220
        super(Application, self).__setattr__(name, value)
221
222
    @property_
223
    def inputs(self):
224
        return self._inputs
225
226
    @inputs.setter
227
    def inputs(self, inputs):
228
        args_names, varargs_name, _, _ = inspect.getargspec(
229
            self.application_function)
230
        if not all(input_ in args_names + [varargs_name] for input_ in inputs):
231
            raise ValueError("Unexpected inputs")
232
        self._inputs = inputs
233
234
    @property_
235
    def name(self):
236
        return self.application_name
237
238
    def __call__(self, brick, *args, **kwargs):
239
        if not isinstance(brick, Brick) and six.PY2:
240
            raise TypeError
241
        bound_application = self.__get__(brick, brick.__class__)
242
        return self.apply(bound_application, *args, **kwargs)
243
244
    def apply(self, bound_application, *args, **kwargs):
245
        as_dict = kwargs.pop('as_dict', False)
246
        as_list = kwargs.pop('as_list', False)
247
        if as_list and as_dict:
248
            raise ValueError
249
250
        brick = bound_application.brick
251
252
        # Find the names of the inputs to the application method
253
        args_names, varargs_name, _, _ = inspect.getargspec(
254
            self.application_function)
255
        args_names = args_names[1:]
256
257
        # Construct the ApplicationCall, used to store data in for this call
258
        call = ApplicationCall(bound_application)
259
        args = list(args)
260
        if 'application' in args_names:
261
            args.insert(args_names.index('application'), bound_application)
262
        if 'application_call' in args_names:
263
            args.insert(args_names.index('application_call'), call)
264
265
        # Allocate before applying, and optionally initialize
266
        if not brick.allocated:
267
            brick.allocate()
268
269
        # Annotate all the input variables which are Theano variables
270
        def copy_and_tag(variable, role, name):
271
            """Helper method to copy a variable and annotate it."""
272
            copy = variable.copy()
273
            # Theano name
274
            copy.name = _variable_name(brick.name, self.name, name)
275
            add_annotation(copy, brick)
276
            add_annotation(copy, call)
277
            # Blocks name
278
            copy.tag.name = name
279
            add_role(copy, role)
280
            return copy
281
282
        for i, input_ in enumerate(args):
283
            if isinstance(input_, tensor.Variable):
284
                if i < len(args_names):
285
                    name = args_names[i]
286
                else:
287
                    name = "{}_{}".format(varargs_name, i - len(args_names))
288
                args[i] = copy_and_tag(input_, INPUT, name)
289
        for name, input_ in kwargs.items():
290
            if isinstance(input_, tensor.Variable):
291
                kwargs[name] = copy_and_tag(input_, INPUT, name)
292
293
        # Run the application method on the annotated variables
294
        last_brick = self.call_stack[-1] if self.call_stack else None
295
        if (last_brick and brick is not last_brick and
296
                brick not in last_brick.children):
297
            raise ValueError('Brick ' + str(self.call_stack[-1]) + ' tries '
298
                             'to call brick ' + str(self.brick) + ' which '
299
                             'is not in the list of its children. This could '
300
                             'be caused because an @application decorator is '
301
                             'missing.')
302
        self.call_stack.append(brick)
303
        try:
304
            outputs = self.application_function(brick, *args, **kwargs)
305
            outputs = pack(outputs)
306
        finally:
307
            self.call_stack.pop()
308
309
        # Rename and annotate output variables
310
        for i, output in enumerate(outputs):
311
            if isinstance(output, tensor.Variable):
312
                try:
313
                    name = bound_application.outputs[i]
314
                except AttributeError:
315
                    name = "output_{}".format(i)
316
                except IndexError:
317
                    reraise_as(ValueError("Unexpected outputs"))
318
                # TODO Tag with dimensions, axes, etc. for error-checking
319
                outputs[i] = copy_and_tag(outputs[i],
320
                                          OUTPUT, name)
321
322
        # Return values
323
        if as_list:
324
            return outputs
325
        if as_dict:
326
            return OrderedDict(zip(bound_application.outputs, outputs))
327
        return unpack(outputs)
328
329
    # Application instances are used instead of usual methods in bricks.
330
    # The usual methods are not pickled per-se, similarly to classes
331
    # and modules. Instead, a reference to the method is put into the pickle.
332
    # Here, we ensure the same behaviour for Application instances.
333
    def __reduce__(self):
334
        return (getattr, (self.brick, self.application_name))
335
336
337
class BoundApplication(object):
338
    """An application method bound to a :class:`Brick` instance."""
339
    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 901).

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...
340
        self.application = application
341
        self.brick = brick
342
343
    def __getattr__(self, name):
344
        # Prevent infinite loops
345
        if name == 'application':
346
            raise AttributeError
347
        # These always belong to the parent (the unbound application)
348
        if name in ('delegate_function', 'properties'):
349
            return getattr(self.application, name)
350
        if name in self.properties.values():
351
            return getattr(self.application.brick, name)
352
        if name in self.properties:
353
            return getattr(self, self.properties[name])(self.brick)
354
        # First try the parent (i.e. class level), before trying the delegate
355
        try:
356
            return getattr(self.application, name)
357
        except AttributeError:
358
            if self.delegate_function:
359
                return getattr(getattr(self.brick,
360
                                       self.delegate_function)(),
361
                               name)
362
            raise
363
364
    @property
365
    def name(self):
366
        return self.application.name
367
368
    def __call__(self, *args, **kwargs):
369
        return self.application.apply(self, *args, **kwargs)
370
371
372
def rename_function(function, new_name):
373
    old_name = function.__name__
374
    function.__name__ = new_name
375
    if six.PY3:
376
        function.__qualname__ = \
377
            function.__qualname__[:-len(old_name)] + new_name
378
    return function
379
380
381
class _Brick(ABCMeta):
382
    """Metaclass which attaches brick instances to the applications.
383
384
    In addition picklability of :class:`Application` objects is ensured.
385
    This means that :class:`Application` objects can not be added to a
386
    brick class after it is created. To allow adding application methods
387
    programatically, the following hook is supported: the class namespace
388
    is searched for `decorators` attribute, which can contain a
389
    list of functions to be applied to the namespace of the class being
390
    created. These functions can arbitratily modify this namespace.
391
392
    """
393
    def __new__(mcs, name, bases, namespace):
394
        decorators = namespace.get('decorators', [])
395
        for decorator in decorators:
396
            decorator(mcs, name, bases, namespace)
397
        for attr in list(namespace.values()):
398
            if (isinstance(attr, Application) and
399
                    hasattr(attr, '_application_function')):
400
                namespace['_' + attr.application_name] = \
401
                    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...
402
                                    '_' + attr.application_name)
403
                del attr._application_function
404
        brick = super(_Brick, mcs).__new__(mcs, name, bases, namespace)
405
        for attr in namespace.values():
406
            if isinstance(attr, Application):
407
                attr.brick = brick
408
        return brick
409
410
411
@add_metaclass(_Brick)
412
class Brick(Annotation):
413
    """A brick encapsulates Theano operations with parameters.
414
415
    A brick goes through the following stages:
416
417
    1. Construction: The call to :meth:`__init__` constructs a
418
       :class:`Brick` instance with a name and creates any child bricks as
419
       well.
420
    2. Allocation of parameters:
421
422
       a) Allocation configuration of children: The
423
          :meth:`push_allocation_config` method configures any children of
424
          this block.
425
       b) Allocation: The :meth:`allocate` method allocates the shared
426
          Theano variables required for the parameters. Also allocates
427
          parameters for all children.
428
429
    3. The following can be done in either order:
430
431
       a) Application: By applying the brick to a set of Theano
432
          variables a part of the computational graph of the final model is
433
          constructed.
434
       b) The initialization of parameters:
435
436
          1. Initialization configuration of children: The
437
             :meth:`push_initialization_config` method configures any
438
             children of this block.
439
          2. Initialization: This sets the initial values of the
440
             parameters by a call to :meth:`initialize`, which is needed
441
             to call the final compiled Theano function.  Also initializes
442
             all children.
443
444
    Not all stages need to be called explicitly. Step 3(a) will
445
    automatically allocate the parameters if needed. Similarly, step
446
    3(b.2) and 2(b) will automatically perform steps 3(b.1) and 2(a) if
447
    needed. They only need to be called separately if greater control is
448
    required. The only two methods which always need to be called are an
449
    application method to construct the computational graph, and the
450
    :meth:`initialize` method in order to initialize the parameters.
451
452
    At each different stage, a brick might need a certain set of
453
    configuration settings. All of these settings can be passed to the
454
    :meth:`__init__` constructor. However, by default many bricks support
455
    *lazy initialization*. This means that the configuration settings can
456
    be set later.
457
458
    .. note::
459
460
       Some arguments to :meth:`__init__` are *always* required, even when
461
       lazy initialization is enabled. Other arguments must be given before
462
       calling :meth:`allocate`, while others yet only need to be given in
463
       order to call :meth:`initialize`. Always read the documentation of
464
       each brick carefully.
465
466
    Lazy initialization can be turned off by setting ``Brick.lazy =
467
    False``. In this case, there is no need to call :meth:`initialize`
468
    manually anymore, but all the configuration must be passed to the
469
    :meth:`__init__` method.
470
471
    Parameters
472
    ----------
473
    name : str, optional
474
        The name of this brick. This can be used to filter the application
475
        of certain modifications by brick names. By default, the brick
476
        receives the name of its class (lowercased).
477
478
    Attributes
479
    ----------
480
    name : str
481
        The name of this brick.
482
    print_shapes : bool
483
        ``False`` by default. If ``True`` it logs the shapes of all the
484
        input and output variables, which can be useful for debugging.
485
    parameters : list of :class:`~tensor.TensorSharedVariable` and ``None``
486
        After calling the :meth:`allocate` method this attribute will be
487
        populated with the shared variables storing this brick's
488
        parameters. Allows for ``None`` so that parameters can always be
489
        accessed at the same index, even if some parameters are only
490
        defined given a particular configuration.
491
    children : list of bricks
492
        The children of this brick.
493
    allocated : bool
494
        ``False`` if :meth:`allocate` has not been called yet. ``True``
495
        otherwise.
496
    initialized : bool
497
        ``False`` if :meth:`allocate` has not been called yet. ``True``
498
        otherwise.
499
    allocation_config_pushed : bool
500
        ``False`` if :meth:`allocate` or :meth:`push_allocation_config`
501
        hasn't been called yet. ``True`` otherwise.
502
    initialization_config_pushed : bool
503
        ``False`` if :meth:`initialize` or
504
        :meth:`push_initialization_config` hasn't been called yet. ``True``
505
        otherwise.
506
507
    Notes
508
    -----
509
    To provide support for lazy initialization, apply the :meth:`lazy`
510
    decorator to the :meth:`__init__` method.
511
512
    Brick implementations *must* call the :meth:`__init__` constructor of
513
    their parent using `super(BlockImplementation,
514
    self).__init__(**kwargs)` at the *beginning* of the overriding
515
    `__init__`.
516
517
    The methods :meth:`_allocate` and :meth:`_initialize` need to be
518
    overridden if the brick needs to allocate shared variables and
519
    initialize their values in order to function.
520
521
    A brick can have any number of methods which apply the brick on Theano
522
    variables. These methods should be decorated with the
523
    :func:`application` decorator.
524
525
    If a brick has children, they must be listed in the :attr:`children`
526
    attribute. Moreover, if the brick wants to control the configuration of
527
    its children, the :meth:`_push_allocation_config` and
528
    :meth:`_push_initialization_config` methods need to be overridden.
529
530
    Examples
531
    --------
532
    Most bricks have lazy initialization enabled.
533
534
    >>> import theano
535
    >>> from blocks.initialization import IsotropicGaussian, Constant
536
    >>> from blocks.bricks import Linear
537
    >>> linear = Linear(input_dim=5, output_dim=3,
538
    ...                 weights_init=IsotropicGaussian(),
539
    ...                 biases_init=Constant(0))
540
    >>> x = theano.tensor.vector()
541
    >>> linear.apply(x)  # Calls linear.allocate() automatically
542
    linear_apply_output
543
    >>> linear.initialize()  # Initializes the weight matrix
544
545
    """
546
    #: See :attr:`Brick.print_shapes`
547
    print_shapes = False
548
549
    def __init__(self, name=None):
550
        if name is None:
551
            name = self.__class__.__name__.lower()
552
        self.name = name
553
554
        self.children = []
555
        self.parents = []
556
557
        self.allocated = False
558
        self.allocation_config_pushed = False
559
        self.initialized = False
560
        self.initialization_config_pushed = False
561
        super(Brick, self).__init__()
562
563
    def __repr__(self):
564
        return repr_attrs(self, 'name')
565
566
    @property
567
    def parameters(self):
568
        return self._parameters
569
570
    @parameters.setter
571
    def parameters(self, value):
572
        self._parameters = Parameters(self, value)
573
574
    @property
575
    def children(self):
576
        return self._children
577
578
    @children.setter
579
    def children(self, value):
580
        self._children = Children(self, value)
581
582
    def allocate(self):
583
        """Allocate shared variables for parameters.
584
585
        Based on the current configuration of this :class:`Brick` create
586
        Theano shared variables to store the parameters.  After allocation,
587
        parameters are accessible through the :attr:`parameters` attribute.
588
589
        This method calls the :meth:`allocate` method of all children
590
        first, allowing the :meth:`_allocate` method to override the
591
        parameters of the children if needed.
592
593
        Raises
594
        ------
595
        ValueError
596
            If the configuration of this brick is insufficient to determine
597
            the number of parameters or their dimensionality to be
598
            initialized.
599
600
        Notes
601
        -----
602
        This method sets the :attr:`parameters` attribute to an empty list.
603
        This is in order to ensure that calls to this method completely
604
        reset the parameters.
605
606
        """
607
        if hasattr(self, 'allocation_args'):
608
            missing_config = [arg for arg in self.allocation_args
0 ignored issues
show
Bug introduced by
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...
609
                              if getattr(self, arg) is NoneAllocation]
610
            if missing_config:
611
                raise ValueError('allocation config not set: '
612
                                 '{}'.format(', '.join(missing_config)))
613
        if not self.allocation_config_pushed:
614
            self.push_allocation_config()
615
        for child in self.children:
616
            if not child.allocated:
617
                child.allocate()
618
        self.parameters = []
619
        self._allocate()
620
        self.allocated = True
621
622
    def _allocate(self):
623
        """Brick implementation of parameter initialization.
624
625
        Implement this if your brick needs to allocate its parameters.
626
627
        .. warning::
628
629
           This method should never be called directly. Call
630
           :meth:`initialize` instead.
631
632
        """
633
        pass
634
635
    def initialize(self):
636
        """Initialize parameters.
637
638
        Intialize parameters, such as weight matrices and biases.
639
640
        Notes
641
        -----
642
        If the brick has not allocated its parameters yet, this method will
643
        call the :meth:`allocate` method in order to do so.
644
645
        """
646
        if hasattr(self, 'initialization_args'):
647
            missing_config = [arg for arg in self.initialization_args
0 ignored issues
show
Bug introduced by
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...
648
                              if getattr(self, arg) is NoneInitialization]
649
            if missing_config:
650
                raise ValueError('initialization config not set: '
651
                                 '{}'.format(', '.join(missing_config)))
652
        if not self.allocated:
653
            self.allocate()
654
        if not self.initialization_config_pushed:
655
            self.push_initialization_config()
656
        for child in self.children:
657
            if not child.initialized:
658
                child.initialize()
659
        self._initialize()
660
        self.initialized = True
661
662
    def _initialize(self):
663
        """Brick implementation of parameter initialization.
664
665
        Implement this if your brick needs to initialize its parameters.
666
667
        .. warning::
668
669
           This method should never be called directly. Call
670
           :meth:`initialize` instead.
671
672
        """
673
        pass
674
675
    def push_allocation_config(self):
676
        """Push the configuration for allocation to child bricks.
677
678
        Bricks can configure their children, based on their own current
679
        configuration. This will be automatically done by a call to
680
        :meth:`allocate`, but if you want to override the configuration of
681
        child bricks manually, then you can call this function manually.
682
683
        """
684
        self._push_allocation_config()
685
        self.allocation_config_pushed = True
686
        for child in self.children:
687
            if not child.allocated:
688
                try:
689
                    child.push_allocation_config()
690
                except Exception:
691
                    self.allocation_config_pushed = False
692
                    raise
693
694
    def _push_allocation_config(self):
695
        """Brick implementation of configuring child before allocation.
696
697
        Implement this if your brick needs to set the configuration of its
698
        children before allocation.
699
700
        .. warning::
701
702
           This method should never be called directly. Call
703
           :meth:`push_allocation_config` instead.
704
705
        """
706
        pass
707
708
    def push_initialization_config(self):
709
        """Push the configuration for initialization to child bricks.
710
711
        Bricks can configure their children, based on their own current
712
        configuration. This will be automatically done by a call to
713
        :meth:`initialize`, but if you want to override the configuration
714
        of child bricks manually, then you can call this function manually.
715
716
        """
717
        self._push_initialization_config()
718
        self.initialization_config_pushed = True
719
        for child in self.children:
720
            if not child.initialized:
721
                try:
722
                    child.push_initialization_config()
723
                except Exception:
724
                    self.initialization_config_pushed = False
725
                    raise
726
727
    def _push_initialization_config(self):
728
        """Brick implementation of configuring child before initialization.
729
730
        Implement this if your brick needs to set the configuration of its
731
        children before initialization.
732
733
        .. warning::
734
735
           This method should never be called directly. Call
736
           :meth:`push_initialization_config` instead.
737
738
        """
739
        pass
740
741
    def get_dim(self, name):
742
        """Get dimension of an input/output variable of a brick.
743
744
        Parameters
745
        ----------
746
        name : str
747
            The name of the variable.
748
749
        """
750
        raise ValueError("No dimension information for {} available"
751
                         .format(name))
752
753
    def get_dims(self, names):
754
        """Get list of dimensions for a set of input/output variables.
755
756
        Parameters
757
        ----------
758
        names : list
759
            The variable names.
760
761
        Returns
762
        -------
763
        dims : list
764
            The dimensions of the sources.
765
766
        """
767
        return [self.get_dim(name) for name in names]
768
769
    def get_unique_path(self):
770
        """Returns unique path to this brick in the application graph."""
771
        if self.parents:
772
            parent = min(self.parents, key=attrgetter('name'))
773
            return parent.get_unique_path() + [self]
774
        else:
775
            return [self]
776
777
778
def args_to_kwargs(args, f):
779
    arg_names, vararg_names, _, _ = inspect.getargspec(f)
780
    return dict((arg_name, arg) for arg_name, arg
781
                in zip(arg_names + [vararg_names], args))
782
783
784
class LazyNone(object):
785
    def __init__(self, name):
786
        self.name = name
787
788
    def __repr__(self):
789
        return self.name
790
791
    def __bool__(self):
792
        return False
793
794
    __nonzero__ = __bool__
795
796
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...
797
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...
798
799
800
def lazy(allocation=None, initialization=None):
801
    """Makes the initialization lazy.
802
803
    This decorator allows the user to define positional arguments which
804
    will not be needed until the allocation or initialization stage of the
805
    brick. If these arguments are not passed, it will automatically replace
806
    them with a custom ``None`` object. It is assumed that the missing
807
    arguments can be set after initialization by setting attributes with
808
    the same name.
809
810
    Parameters
811
    ----------
812
    allocation : list
813
        A list of argument names that are needed for allocation.
814
    initialization : list
815
        A list of argument names that are needed for initialization.
816
817
    Examples
818
    --------
819
    >>> class SomeBrick(Brick):
820
    ...     @lazy(allocation=['a'], initialization=['b'])
821
    ...     def __init__(self, a, b, c='c', d=None):
822
    ...         print(a, b, c, d)
823
    >>> brick = SomeBrick('a')
824
    a NoneInitialization c None
825
    >>> brick = SomeBrick(d='d', b='b')
826
    NoneAllocation b c d
827
828
    """
829
    if not allocation:
830
        allocation = []
831
    if not initialization:
832
        initialization = []
833
834
    def lazy_wrapper(init):
835
        def lazy_init(*args, **kwargs):
836
            self = args[0]
837
            self.allocation_args = (getattr(self, 'allocation_args',
838
                                            []) + allocation)
839
            self.initialization_args = (getattr(self, 'initialization_args',
840
                                                []) + initialization)
841
            kwargs = dict_union(args_to_kwargs(args, init), kwargs)
842
            for allocation_arg in allocation:
843
                kwargs.setdefault(allocation_arg, NoneAllocation)
844
            for initialization_arg in initialization:
845
                kwargs.setdefault(initialization_arg, NoneInitialization)
846
            return init(**kwargs)
847
        wraps(init)(lazy_init)
848
        return lazy_init
849
    return lazy_wrapper
850
851
852
class ApplicationCall(Annotation):
853
    """A link between the variable tags and bricks.
854
855
    The application call can be used to attach to an apply call auxiliary
856
    variables (e.g. monitors or regularizers) that do not form part of the
857
    main computation graph.
858
859
    The application call object is created before the call to the
860
    application method and can be accessed by specifying an
861
    application_call argument.
862
863
    Also see :class:`.Annotation`.
864
865
    Parameters
866
    ----------
867
    application : :class:`BoundApplication` instance
868
        The bound application (i.e. belong to a brick instance) object
869
        being called
870
871
    Examples
872
    --------
873
    >>> class Foo(Brick):
874
    ...     @application
875
    ...     def apply(self, x, application_call):
876
    ...         application_call.add_auxiliary_variable(x.mean())
877
    ...         return x + 1
878
    >>> x = tensor.vector()
879
    >>> y = Foo().apply(x)
880
    >>> from blocks.filter import get_application_call
881
    >>> get_application_call(y) # doctest: +ELLIPSIS
882
    <blocks.bricks.base.ApplicationCall object at ...>
883
884
    """
885
    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 901).

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...
886
        self.application = application
887
        self.metadata = {}
888
        super(ApplicationCall, self).__init__()
889
890
    def add_auxiliary_variable(self, variable, roles=None, name=None):
891
        if name:
892
            variable.name = _variable_name(
893
                self.application.brick.name, self.application.name, name)
894
            variable.tag.name = name
895
            name = None
896
        add_annotation(variable, self.application.brick)
897
        return super(ApplicationCall, self).add_auxiliary_variable(
898
            variable, roles, name)
899
900
901
def application(*args, **kwargs):
902
    r"""Decorator for methods that apply a brick to inputs.
903
904
    Parameters
905
    ----------
906
    \*args, optional
907
        The application method to wrap.
908
    \*\*kwargs, optional
909
        Attributes to attach to this application.
910
911
    Notes
912
    -----
913
    This decorator replaces application methods with :class:`Application`
914
    instances. It also sets the attributes given as keyword arguments to
915
    the decorator.
916
917
    Note that this decorator purposely does not wrap the original method
918
    using e.g. :func:`~functools.wraps` or
919
    :func:`~functools.update_wrapper`, since that would make the class
920
    impossible to pickle (see notes at :class:`Application`).
921
922
    Examples
923
    --------
924
    >>> class Foo(Brick):
925
    ...     @application(inputs=['x'], outputs=['y'])
926
    ...     def apply(self, x):
927
    ...         return x + 1
928
    ...     @application
929
    ...     def other_apply(self, x):
930
    ...         return x - 1
931
    >>> foo = Foo()
932
    >>> Foo.apply.inputs
933
    ['x']
934
    >>> foo.apply.outputs
935
    ['y']
936
    >>> Foo.other_apply # doctest: +ELLIPSIS
937
    <blocks.bricks.base.Application object at ...>
938
939
    """
940
    if not ((args and not kwargs) or (not args and kwargs)):
941
        raise ValueError
942
    if args:
943
        application_function, = args
944
        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 901).

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...
945
        return application
946
    else:
947
        def wrap_application(application_function):
948
            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 901).

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...
949
            for key, value in kwargs.items():
950
                setattr(application, key, value)
951
            return application
952
        return wrap_application
953
954
955
def _variable_name(brick_name, application_name, name):
956
    return "{}_{}_{}".format(brick_name, application_name, name)
957