Completed
Pull Request — master (#1097)
by Dmitry
04:44
created

Brick.parameters()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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...
964
        return application
965
    else:
966
        def wrap_application(application_function):
967
            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 920).

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...
968
            for key, value in kwargs.items():
969
                setattr(application, key, value)
970
            return application
971
        return wrap_application
972
973
974
def _variable_name(brick_name, application_name, name):
975
    return "{}_{}_{}".format(brick_name, application_name, name)
976