Completed
Pull Request — master (#1064)
by Dmitry
04:46
created

Application.apply()   F

Complexity

Conditions 22

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 22
dl 0
loc 84
rs 2.1001

1 Method

Rating   Name   Duplication   Size   Complexity  
A Application.copy_and_tag() 0 11 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Application.apply() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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 900).

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, children=None):
550
        if name is None:
551
            name = self.__class__.__name__.lower()
552
553
        if children is None:
554
            children = []
555
556
        self.name = name
557
        self.children = children
558
        self.parents = []
559
560
        self.allocated = False
561
        self.allocation_config_pushed = False
562
        self.initialized = False
563
        self.initialization_config_pushed = False
564
        super(Brick, self).__init__()
565
566
    def __repr__(self):
567
        return repr_attrs(self, 'name')
568
569
    @property
570
    def parameters(self):
571
        return self._parameters
572
573
    @parameters.setter
574
    def parameters(self, value):
575
        self._parameters = Parameters(self, value)
576
577
    @property
578
    def children(self):
579
        return self._children
580
581
    @children.setter
582
    def children(self, value):
583
        self._children = Children(self, value)
584
585
    def allocate(self):
586
        """Allocate shared variables for parameters.
587
588
        Based on the current configuration of this :class:`Brick` create
589
        Theano shared variables to store the parameters.  After allocation,
590
        parameters are accessible through the :attr:`parameters` attribute.
591
592
        This method calls the :meth:`allocate` method of all children
593
        first, allowing the :meth:`_allocate` method to override the
594
        parameters of the children if needed.
595
596
        Raises
597
        ------
598
        ValueError
599
            If the configuration of this brick is insufficient to determine
600
            the number of parameters or their dimensionality to be
601
            initialized.
602
603
        Notes
604
        -----
605
        This method sets the :attr:`parameters` attribute to an empty list.
606
        This is in order to ensure that calls to this method completely
607
        reset the parameters.
608
609
        """
610
        if hasattr(self, 'allocation_args'):
611
            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...
612
                              if getattr(self, arg) is NoneAllocation]
613
            if missing_config:
614
                raise ValueError('allocation config not set: '
615
                                 '{}'.format(', '.join(missing_config)))
616
        if not self.allocation_config_pushed:
617
            self.push_allocation_config()
618
        for child in self.children:
619
            child.allocate()
620
        self.parameters = []
621
        self._allocate()
622
        self.allocated = True
623
624
    def _allocate(self):
625
        """Brick implementation of parameter initialization.
626
627
        Implement this if your brick needs to allocate its parameters.
628
629
        .. warning::
630
631
           This method should never be called directly. Call
632
           :meth:`initialize` instead.
633
634
        """
635
        pass
636
637
    def initialize(self):
638
        """Initialize parameters.
639
640
        Intialize parameters, such as weight matrices and biases.
641
642
        Notes
643
        -----
644
        If the brick has not allocated its parameters yet, this method will
645
        call the :meth:`allocate` method in order to do so.
646
647
        """
648
        if hasattr(self, 'initialization_args'):
649
            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...
650
                              if getattr(self, arg) is NoneInitialization]
651
            if missing_config:
652
                raise ValueError('initialization config not set: '
653
                                 '{}'.format(', '.join(missing_config)))
654
        if not self.allocated:
655
            self.allocate()
656
        if not self.initialization_config_pushed:
657
            self.push_initialization_config()
658
        for child in self.children:
659
            child.initialize()
660
        self._initialize()
661
        self.initialized = True
662
663
    def _initialize(self):
664
        """Brick implementation of parameter initialization.
665
666
        Implement this if your brick needs to initialize its parameters.
667
668
        .. warning::
669
670
           This method should never be called directly. Call
671
           :meth:`initialize` instead.
672
673
        """
674
        pass
675
676
    def push_allocation_config(self):
677
        """Push the configuration for allocation to child bricks.
678
679
        Bricks can configure their children, based on their own current
680
        configuration. This will be automatically done by a call to
681
        :meth:`allocate`, but if you want to override the configuration of
682
        child bricks manually, then you can call this function manually.
683
684
        """
685
        self._push_allocation_config()
686
        self.allocation_config_pushed = True
687
        for child in self.children:
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
            try:
721
                child.push_initialization_config()
722
            except Exception:
723
                self.initialization_config_pushed = False
724
                raise
725
726
    def _push_initialization_config(self):
727
        """Brick implementation of configuring child before initialization.
728
729
        Implement this if your brick needs to set the configuration of its
730
        children before initialization.
731
732
        .. warning::
733
734
           This method should never be called directly. Call
735
           :meth:`push_initialization_config` instead.
736
737
        """
738
        pass
739
740
    def get_dim(self, name):
741
        """Get dimension of an input/output variable of a brick.
742
743
        Parameters
744
        ----------
745
        name : str
746
            The name of the variable.
747
748
        """
749
        raise ValueError("No dimension information for {} available"
750
                         .format(name))
751
752
    def get_dims(self, names):
753
        """Get list of dimensions for a set of input/output variables.
754
755
        Parameters
756
        ----------
757
        names : list
758
            The variable names.
759
760
        Returns
761
        -------
762
        dims : list
763
            The dimensions of the sources.
764
765
        """
766
        return [self.get_dim(name) for name in names]
767
768
    def get_unique_path(self):
769
        """Returns unique path to this brick in the application graph."""
770
        if self.parents:
771
            parent = min(self.parents, key=attrgetter('name'))
772
            return parent.get_unique_path() + [self]
773
        else:
774
            return [self]
775
776
777
def args_to_kwargs(args, f):
778
    arg_names, vararg_names, _, _ = inspect.getargspec(f)
779
    return dict((arg_name, arg) for arg_name, arg
780
                in zip(arg_names + [vararg_names], args))
781
782
783
class LazyNone(object):
784
    def __init__(self, name):
785
        self.name = name
786
787
    def __repr__(self):
788
        return self.name
789
790
    def __bool__(self):
791
        return False
792
793
    __nonzero__ = __bool__
794
795
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...
796
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...
797
798
799
def lazy(allocation=None, initialization=None):
800
    """Makes the initialization lazy.
801
802
    This decorator allows the user to define positional arguments which
803
    will not be needed until the allocation or initialization stage of the
804
    brick. If these arguments are not passed, it will automatically replace
805
    them with a custom ``None`` object. It is assumed that the missing
806
    arguments can be set after initialization by setting attributes with
807
    the same name.
808
809
    Parameters
810
    ----------
811
    allocation : list
812
        A list of argument names that are needed for allocation.
813
    initialization : list
814
        A list of argument names that are needed for initialization.
815
816
    Examples
817
    --------
818
    >>> class SomeBrick(Brick):
819
    ...     @lazy(allocation=['a'], initialization=['b'])
820
    ...     def __init__(self, a, b, c='c', d=None):
821
    ...         print(a, b, c, d)
822
    >>> brick = SomeBrick('a')
823
    a NoneInitialization c None
824
    >>> brick = SomeBrick(d='d', b='b')
825
    NoneAllocation b c d
826
827
    """
828
    if not allocation:
829
        allocation = []
830
    if not initialization:
831
        initialization = []
832
833
    def lazy_wrapper(init):
834
        def lazy_init(*args, **kwargs):
835
            self = args[0]
836
            self.allocation_args = (getattr(self, 'allocation_args',
837
                                            []) + allocation)
838
            self.initialization_args = (getattr(self, 'initialization_args',
839
                                                []) + initialization)
840
            kwargs = dict_union(args_to_kwargs(args, init), kwargs)
841
            for allocation_arg in allocation:
842
                kwargs.setdefault(allocation_arg, NoneAllocation)
843
            for initialization_arg in initialization:
844
                kwargs.setdefault(initialization_arg, NoneInitialization)
845
            return init(**kwargs)
846
        wraps(init)(lazy_init)
847
        return lazy_init
848
    return lazy_wrapper
849
850
851
class ApplicationCall(Annotation):
852
    """A link between the variable tags and bricks.
853
854
    The application call can be used to attach to an apply call auxiliary
855
    variables (e.g. monitors or regularizers) that do not form part of the
856
    main computation graph.
857
858
    The application call object is created before the call to the
859
    application method and can be accessed by specifying an
860
    application_call argument.
861
862
    Also see :class:`.Annotation`.
863
864
    Parameters
865
    ----------
866
    application : :class:`BoundApplication` instance
867
        The bound application (i.e. belong to a brick instance) object
868
        being called
869
870
    Examples
871
    --------
872
    >>> class Foo(Brick):
873
    ...     @application
874
    ...     def apply(self, x, application_call):
875
    ...         application_call.add_auxiliary_variable(x.mean())
876
    ...         return x + 1
877
    >>> x = tensor.vector()
878
    >>> y = Foo().apply(x)
879
    >>> from blocks.filter import get_application_call
880
    >>> get_application_call(y) # doctest: +ELLIPSIS
881
    <blocks.bricks.base.ApplicationCall object at ...>
882
883
    """
884
    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 900).

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

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

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