Completed
Pull Request — master (#974)
by Dmitry
01:22
created

blocks.bricks.Application.__reduce__()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 2
rs 10
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 by
333
    # registering a custom pickler and unpickler for them.
334
    def __reduce__(self):
335
        return (getattr, (self.brick, self.application_name))
336
337
338
class BoundApplication(object):
339
    """An application method bound to a :class:`Brick` instance."""
340
    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 898).

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

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

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...
942
        return application
943
    else:
944
        def wrap_application(application_function):
945
            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 898).

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