Completed
Push — master ( 4ae4a9...00281d )
by Dmitry
01:29
created

blocks.bricks.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 blocks.bricks.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 blocks.bricks.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 897).

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
            child.allocate()
617
        self.parameters = []
618
        self._allocate()
619
        self.allocated = True
620
621
    def _allocate(self):
622
        """Brick implementation of parameter initialization.
623
624
        Implement this if your brick needs to allocate its parameters.
625
626
        .. warning::
627
628
           This method should never be called directly. Call
629
           :meth:`initialize` instead.
630
631
        """
632
        pass
633
634
    def initialize(self):
635
        """Initialize parameters.
636
637
        Intialize parameters, such as weight matrices and biases.
638
639
        Notes
640
        -----
641
        If the brick has not allocated its parameters yet, this method will
642
        call the :meth:`allocate` method in order to do so.
643
644
        """
645
        if hasattr(self, 'initialization_args'):
646
            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...
647
                              if getattr(self, arg) is NoneInitialization]
648
            if missing_config:
649
                raise ValueError('initialization config not set: '
650
                                 '{}'.format(', '.join(missing_config)))
651
        if not self.allocated:
652
            self.allocate()
653
        if not self.initialization_config_pushed:
654
            self.push_initialization_config()
655
        for child in self.children:
656
            child.initialize()
657
        self._initialize()
658
        self.initialized = True
659
660
    def _initialize(self):
661
        """Brick implementation of parameter initialization.
662
663
        Implement this if your brick needs to initialize its parameters.
664
665
        .. warning::
666
667
           This method should never be called directly. Call
668
           :meth:`initialize` instead.
669
670
        """
671
        pass
672
673
    def push_allocation_config(self):
674
        """Push the configuration for allocation to child bricks.
675
676
        Bricks can configure their children, based on their own current
677
        configuration. This will be automatically done by a call to
678
        :meth:`allocate`, but if you want to override the configuration of
679
        child bricks manually, then you can call this function manually.
680
681
        """
682
        self._push_allocation_config()
683
        self.allocation_config_pushed = True
684
        for child in self.children:
685
            try:
686
                child.push_allocation_config()
687
            except Exception:
688
                self.allocation_config_pushed = False
689
                raise
690
691
    def _push_allocation_config(self):
692
        """Brick implementation of configuring child before allocation.
693
694
        Implement this if your brick needs to set the configuration of its
695
        children before allocation.
696
697
        .. warning::
698
699
           This method should never be called directly. Call
700
           :meth:`push_allocation_config` instead.
701
702
        """
703
        pass
704
705
    def push_initialization_config(self):
706
        """Push the configuration for initialization to child bricks.
707
708
        Bricks can configure their children, based on their own current
709
        configuration. This will be automatically done by a call to
710
        :meth:`initialize`, but if you want to override the configuration
711
        of child bricks manually, then you can call this function manually.
712
713
        """
714
        self._push_initialization_config()
715
        self.initialization_config_pushed = True
716
        for child in self.children:
717
            try:
718
                child.push_initialization_config()
719
            except Exception:
720
                self.initialization_config_pushed = False
721
                raise
722
723
    def _push_initialization_config(self):
724
        """Brick implementation of configuring child before initialization.
725
726
        Implement this if your brick needs to set the configuration of its
727
        children before initialization.
728
729
        .. warning::
730
731
           This method should never be called directly. Call
732
           :meth:`push_initialization_config` instead.
733
734
        """
735
        pass
736
737
    def get_dim(self, name):
738
        """Get dimension of an input/output variable of a brick.
739
740
        Parameters
741
        ----------
742
        name : str
743
            The name of the variable.
744
745
        """
746
        raise ValueError("No dimension information for {} available"
747
                         .format(name))
748
749
    def get_dims(self, names):
750
        """Get list of dimensions for a set of input/output variables.
751
752
        Parameters
753
        ----------
754
        names : list
755
            The variable names.
756
757
        Returns
758
        -------
759
        dims : list
760
            The dimensions of the sources.
761
762
        """
763
        return [self.get_dim(name) for name in names]
764
765
    def get_unique_path(self):
766
        """Returns unique path to this brick in the application graph."""
767
        if self.parents:
768
            parent = min(self.parents, key=attrgetter('name'))
769
            return parent.get_unique_path() + [self]
770
        else:
771
            return [self]
772
773
774
def args_to_kwargs(args, f):
775
    arg_names, vararg_names, _, _ = inspect.getargspec(f)
776
    return dict((arg_name, arg) for arg_name, arg
777
                in zip(arg_names + [vararg_names], args))
778
779
780
class LazyNone(object):
781
    def __init__(self, name):
782
        self.name = name
783
784
    def __repr__(self):
785
        return self.name
786
787
    def __bool__(self):
788
        return False
789
790
    __nonzero__ = __bool__
791
792
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...
793
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...
794
795
796
def lazy(allocation=None, initialization=None):
797
    """Makes the initialization lazy.
798
799
    This decorator allows the user to define positional arguments which
800
    will not be needed until the allocation or initialization stage of the
801
    brick. If these arguments are not passed, it will automatically replace
802
    them with a custom ``None`` object. It is assumed that the missing
803
    arguments can be set after initialization by setting attributes with
804
    the same name.
805
806
    Parameters
807
    ----------
808
    allocation : list
809
        A list of argument names that are needed for allocation.
810
    initialization : list
811
        A list of argument names that are needed for initialization.
812
813
    Examples
814
    --------
815
    >>> class SomeBrick(Brick):
816
    ...     @lazy(allocation=['a'], initialization=['b'])
817
    ...     def __init__(self, a, b, c='c', d=None):
818
    ...         print(a, b, c, d)
819
    >>> brick = SomeBrick('a')
820
    a NoneInitialization c None
821
    >>> brick = SomeBrick(d='d', b='b')
822
    NoneAllocation b c d
823
824
    """
825
    if not allocation:
826
        allocation = []
827
    if not initialization:
828
        initialization = []
829
830
    def lazy_wrapper(init):
831
        def lazy_init(*args, **kwargs):
832
            self = args[0]
833
            self.allocation_args = (getattr(self, 'allocation_args',
834
                                            []) + allocation)
835
            self.initialization_args = (getattr(self, 'initialization_args',
836
                                                []) + initialization)
837
            kwargs = dict_union(args_to_kwargs(args, init), kwargs)
838
            for allocation_arg in allocation:
839
                kwargs.setdefault(allocation_arg, NoneAllocation)
840
            for initialization_arg in initialization:
841
                kwargs.setdefault(initialization_arg, NoneInitialization)
842
            return init(**kwargs)
843
        wraps(init)(lazy_init)
844
        return lazy_init
845
    return lazy_wrapper
846
847
848
class ApplicationCall(Annotation):
849
    """A link between the variable tags and bricks.
850
851
    The application call can be used to attach to an apply call auxiliary
852
    variables (e.g. monitors or regularizers) that do not form part of the
853
    main computation graph.
854
855
    The application call object is created before the call to the
856
    application method and can be accessed by specifying an
857
    application_call argument.
858
859
    Also see :class:`.Annotation`.
860
861
    Parameters
862
    ----------
863
    application : :class:`BoundApplication` instance
864
        The bound application (i.e. belong to a brick instance) object
865
        being called
866
867
    Examples
868
    --------
869
    >>> class Foo(Brick):
870
    ...     @application
871
    ...     def apply(self, x, application_call):
872
    ...         application_call.add_auxiliary_variable(x.mean())
873
    ...         return x + 1
874
    >>> x = tensor.vector()
875
    >>> y = Foo().apply(x)
876
    >>> from blocks.filter import get_application_call
877
    >>> get_application_call(y) # doctest: +ELLIPSIS
878
    <blocks.bricks.base.ApplicationCall object at ...>
879
880
    """
881
    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 897).

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

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

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