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

blocks.bricks._pickle_application()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 2
rs 10
1
import inspect
2
from abc import ABCMeta
3
from collections import OrderedDict
4
from functools import wraps
5
from operator import attrgetter
6
from types import MethodType
7
8
import six
9
from six import add_metaclass
10
from six.moves import copyreg
11
from theano import tensor
12
from theano.gof import Variable
13
14
from blocks.graph import add_annotation, Annotation
15
from blocks.roles import add_role, PARAMETER, INPUT, OUTPUT
16
from blocks.utils import dict_union, pack, repr_attrs, reraise_as, unpack
17
from blocks.utils.containers import AnnotatingList
18
19
20
def create_unbound_method(func, cls):
21
    """Create an unbounded method from a function and a class.
22
23
    Notes
24
    -----
25
    See https://bitbucket.org/gutworth/six/pull-request/64.
26
27
    """
28
    if six.PY2:
29
        return MethodType(func, None, cls)
30
    if six.PY3:
31
        return func
32
33
# Rename built-in property to avoid conflict with Application.property
34
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...
35
36
37
class Parameters(AnnotatingList):
38
    """Adds the PARAMETER role to parameters automatically."""
39
    def __init__(self, brick, *args, **kwargs):
40
        self.brick = brick
41
        super(Parameters, self).__init__(*args, **kwargs)
42
43
    def _setitem(self, key, value):
44
        if isinstance(value, Variable):
45
            add_role(value, PARAMETER)
46
            add_annotation(value, self.brick)
47
48
49
class Children(AnnotatingList):
50
    """Adds the brick to the list of parents of its children."""
51
    def __init__(self, brick, *args, **kwargs):
52
        self.brick = brick
53
        super(Children, self).__init__(*args, **kwargs)
54
55
    def _setitem(self, key, value):
56
        if value is not None:
57
            value.parents.append(self.brick)
58
59
    def _delitem(self, key):
60
        child = self._items[key]
61
        if child is not None:
62
            child.parents.remove(self.brick)
63
64
65
class Application(object):
66
    """An application method belonging to a particular type of brick.
67
68
    The application methods of each :class:`Brick` class are automatically
69
    replaced by an instance of :class:`Application`. This allows us to
70
    store metadata about particular application methods (such as their in-
71
    and outputs) easily.
72
73
    Attributes
74
    ----------
75
    application : callable
76
        The original (unbounded) application function defined on the
77
        :class:`Brick`.
78
    delegate_function : callable
79
        A function that takes a :class:`Brick` instance as an argument and
80
        returns a :class:`BoundApplication` object to which attribute
81
        requests should be routed.
82
    properties : :obj:`dict` (:obj:`str`, :obj:`callable`)
83
        A dictionary of property getters that should be called when an
84
        attribute with the given name is requested.
85
    instances : :obj:`dict` (:class:`Brick`, :class:`BoundApplication`)
86
        A record of bound application instances created by the descriptor
87
        protocol.
88
    call_stack : :obj:`list` of :class:`Brick`
89
        The call stack of brick application methods. Used to check whether
90
        the current call was made by a parent brick.
91
    brick : type
92
        The brick class to which this instance belongs.
93
94
    Raises
95
    ------
96
    ValueError
97
        If a brick's application method is applied by another brick which
98
        does not list the former as a child.
99
    ValueError
100
        If the application method's inputs and/or outputs don't match with
101
        the function signature or the values returned (respectively).
102
103
    Notes
104
    -----
105
    When a :class:`Brick` is instantiated and its application method (i.e.
106
    an instance of this class) requested, the descriptor protocol (through
107
    the :meth:`__get__` method) automatically instantiates a
108
    :class:`BoundApplication` class and returns this. This bound
109
    application class can be used to store application information
110
    particular to a brick instance. Any attributes unknown to the bounded
111
    application are automatically routed to the application that
112
    instantiated it.
113
114
    """
115
    call_stack = []
116
117
    def __init__(self, application_function):
118
        self.__doc__ = application_function.__doc__
119
        self._application_function = application_function
120
        self.application_name = application_function.__name__
121
        self.delegate_function = None
122
        self.properties = {}
123
124
    @property
125
    def application_function(self):
126
        if hasattr(self, '_application_function'):
127
            return self._application_function
128
        return getattr(self.brick, '_' + self.application_name)
129
130
    def property(self, name):
131
        """Decorator to make application properties.
132
133
        Parameters
134
        ----------
135
        name : str
136
            The name the property should take.
137
138
        Examples
139
        --------
140
        >>> class Foo(Brick):
141
        ...     @application
142
        ...     def apply(self, x):
143
        ...         return x + 1
144
        ...
145
        ...     @apply.property('inputs')
146
        ...     def apply_inputs(self):
147
        ...         return ['foo', 'bar']
148
        >>> foo = Foo()
149
        >>> foo.apply.inputs
150
        ['foo', 'bar']
151
152
        """
153
        if not isinstance(name, six.string_types):
154
            raise ValueError
155
156
        def wrap_property(application_property):
157
            self.properties[name] = application_property.__name__
158
            return application_property
159
        return wrap_property
160
161
    def delegate(self, f):
162
        """Decorator to assign a delegate application.
163
164
        An application method can assign a delegate application. Whenever
165
        an attribute is not available, it will be requested from the
166
        delegate instead.
167
168
        Examples
169
        --------
170
        >>> class Foo(Brick):
171
        ...     @application(outputs=['baz'])
172
        ...     def apply(self, x):
173
        ...         return x + 1
174
        ...
175
        ...     @apply.property('inputs')
176
        ...     def apply_inputs(self):
177
        ...         return ['foo', 'bar']
178
        >>> class Bar(Brick):
179
        ...     def __init__(self, foo):
180
        ...         self.foo = foo
181
        ...
182
        ...     @application(outputs=['foo'])
183
        ...     def apply(self, x):
184
        ...         return x + 1
185
        ...
186
        ...     @apply.delegate
187
        ...     def apply_delegate(self):
188
        ...         return self.foo.apply
189
        >>> foo = Foo()
190
        >>> bar = Bar(foo)
191
        >>> bar.apply.outputs
192
        ['foo']
193
        >>> bar.apply.inputs
194
        ['foo', 'bar']
195
196
        """
197
        self.delegate_function = f.__name__
198
        return f
199
200
    def __get__(self, instance, owner):
201
        """Instantiate :class:`BoundApplication` for each :class:`Brick`."""
202
        if instance is None:
203
            return self
204
        if not hasattr(instance, "_bound_applications"):
205
            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...
206
        key = "{}.{}".format(self.brick.__name__, self.application_name)
207
        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...
208
            key, BoundApplication(self, instance))
209
210
    def __getattr__(self, name):
211
        # Mimic behavior of properties
212
        if 'properties' in self.__dict__ and name in self.properties:
213
            return property(create_unbound_method(
214
                getattr(self, self.properties[name]), self.brick))
215
        raise AttributeError
216
217
    def __setattr__(self, name, value):
218
        # Mimic behavior of read-only properties
219
        if 'properties' in self.__dict__ and name in self.properties:
220
            raise AttributeError("can't set attribute")
221
        super(Application, self).__setattr__(name, value)
222
223
    @property_
224
    def inputs(self):
225
        return self._inputs
226
227
    @inputs.setter
228
    def inputs(self, inputs):
229
        args_names, varargs_name, _, _ = inspect.getargspec(
230
            self.application_function)
231
        if not all(input_ in args_names + [varargs_name] for input_ in inputs):
232
            raise ValueError("Unexpected inputs")
233
        self._inputs = inputs
234
235
    @property_
236
    def name(self):
237
        return self.application_name
238
239
    def __call__(self, brick, *args, **kwargs):
240
        if not isinstance(brick, Brick) and six.PY2:
241
            raise TypeError
242
        bound_application = self.__get__(brick, brick.__class__)
243
        return self.apply(bound_application, *args, **kwargs)
244
245
    def apply(self, bound_application, *args, **kwargs):
246
        as_dict = kwargs.pop('as_dict', False)
247
        as_list = kwargs.pop('as_list', False)
248
        if as_list and as_dict:
249
            raise ValueError
250
251
        brick = bound_application.brick
252
253
        # Find the names of the inputs to the application method
254
        args_names, varargs_name, _, _ = inspect.getargspec(
255
            self.application_function)
256
        args_names = args_names[1:]
257
258
        # Construct the ApplicationCall, used to store data in for this call
259
        call = ApplicationCall(bound_application)
260
        args = list(args)
261
        if 'application' in args_names:
262
            args.insert(args_names.index('application'), bound_application)
263
        if 'application_call' in args_names:
264
            args.insert(args_names.index('application_call'), call)
265
266
        # Allocate before applying, and optionally initialize
267
        if not brick.allocated:
268
            brick.allocate()
269
270
        # Annotate all the input variables which are Theano variables
271
        def copy_and_tag(variable, role, name):
272
            """Helper method to copy a variable and annotate it."""
273
            copy = variable.copy()
274
            # Theano name
275
            copy.name = _variable_name(brick.name, self.name, name)
276
            add_annotation(copy, brick)
277
            add_annotation(copy, call)
278
            # Blocks name
279
            copy.tag.name = name
280
            add_role(copy, role)
281
            return copy
282
283
        for i, input_ in enumerate(args):
284
            if isinstance(input_, tensor.Variable):
285
                if i < len(args_names):
286
                    name = args_names[i]
287
                else:
288
                    name = "{}_{}".format(varargs_name, i - len(args_names))
289
                args[i] = copy_and_tag(input_, INPUT, name)
290
        for name, input_ in kwargs.items():
291
            if isinstance(input_, tensor.Variable):
292
                kwargs[name] = copy_and_tag(input_, INPUT, name)
293
294
        # Run the application method on the annotated variables
295
        last_brick = self.call_stack[-1] if self.call_stack else None
296
        if (last_brick and brick is not last_brick and
297
                brick not in last_brick.children):
298
            raise ValueError('Brick ' + str(self.call_stack[-1]) + ' tries '
299
                             'to call brick ' + str(self.brick) + ' which '
300
                             'is not in the list of its children. This could '
301
                             'be caused because an @application decorator is '
302
                             'missing.')
303
        self.call_stack.append(brick)
304
        try:
305
            outputs = self.application_function(brick, *args, **kwargs)
306
            outputs = pack(outputs)
307
        finally:
308
            self.call_stack.pop()
309
310
        # Rename and annotate output variables
311
        for i, output in enumerate(outputs):
312
            if isinstance(output, tensor.Variable):
313
                try:
314
                    name = bound_application.outputs[i]
315
                except AttributeError:
316
                    name = "output_{}".format(i)
317
                except IndexError:
318
                    reraise_as(ValueError("Unexpected outputs"))
319
                # TODO Tag with dimensions, axes, etc. for error-checking
320
                outputs[i] = copy_and_tag(outputs[i],
321
                                          OUTPUT, name)
322
323
        # Return values
324
        if as_list:
325
            return outputs
326
        if as_dict:
327
            return OrderedDict(zip(bound_application.outputs, outputs))
328
        return unpack(outputs)
329
330
# Ensure that Application instance are not pickled, and that when
331
# unpickling a reference to an existing Application object is
332
# substituted instead of creating a new one
333
def _pickle_application(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...
334
    return (getattr, (application.brick, application.application_name))
335
336
337
copyreg.pickle(Application, _pickle_application)
338
339
340
class BoundApplication(object):
341
    """An application method bound to a :class:`Brick` instance."""
342
    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...
343
        self.application = application
344
        self.brick = brick
345
346
    def __getattr__(self, name):
347
        # Prevent infinite loops
348
        if name == 'application':
349
            raise AttributeError
350
        # These always belong to the parent (the unbound application)
351
        if name in ('delegate_function', 'properties'):
352
            return getattr(self.application, name)
353
        if name in self.properties.values():
354
            return getattr(self.application.brick, name)
355
        if name in self.properties:
356
            return getattr(self, self.properties[name])(self.brick)
357
        # First try the parent (i.e. class level), before trying the delegate
358
        try:
359
            return getattr(self.application, name)
360
        except AttributeError:
361
            if self.delegate_function:
362
                return getattr(getattr(self.brick,
363
                                       self.delegate_function)(),
364
                               name)
365
            raise
366
367
    @property
368
    def name(self):
369
        return self.application.name
370
371
    def __call__(self, *args, **kwargs):
372
        return self.application.apply(self, *args, **kwargs)
373
374
375
def rename_function(function, new_name):
376
    old_name = function.__name__
377
    function.__name__ = new_name
378
    if six.PY3:
379
        function.__qualname__ = \
380
            function.__qualname__[:-len(old_name)] + new_name
381
    return function
382
383
384
class _Brick(ABCMeta):
385
    """Metaclass which attaches brick instances to the applications.
386
387
    In addition picklability of :class:`Application` objects is ensured.
388
    This means that :class:`Application` objects can not be added to a
389
    brick class after it is created. To allow adding application methods
390
    programatically, the following hook is supported: the class namespace
391
    is searched for `decorators` attribute, which can contain a
392
    list of functions to be applied to the namespace of the class being
393
    created. These functions can arbitratily modify this namespace.
394
395
    """
396
    def __new__(mcs, name, bases, namespace):
397
        decorators = namespace.get('decorators', [])
398
        for decorator in decorators:
399
            decorator(mcs, name, bases, namespace)
400
        for attr in list(namespace.values()):
401
            if (isinstance(attr, Application) and
402
                    hasattr(attr, '_application_function')):
403
                namespace['_' + attr.application_name] = \
404
                    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...
405
                                    '_' + attr.application_name)
406
                del attr._application_function
407
        brick = super(_Brick, mcs).__new__(mcs, name, bases, namespace)
408
        for attr in namespace.values():
409
            if isinstance(attr, Application):
410
                attr.brick = brick
411
        return brick
412
413
414
@add_metaclass(_Brick)
415
class Brick(Annotation):
416
    """A brick encapsulates Theano operations with parameters.
417
418
    A brick goes through the following stages:
419
420
    1. Construction: The call to :meth:`__init__` constructs a
421
       :class:`Brick` instance with a name and creates any child bricks as
422
       well.
423
    2. Allocation of parameters:
424
425
       a) Allocation configuration of children: The
426
          :meth:`push_allocation_config` method configures any children of
427
          this block.
428
       b) Allocation: The :meth:`allocate` method allocates the shared
429
          Theano variables required for the parameters. Also allocates
430
          parameters for all children.
431
432
    3. The following can be done in either order:
433
434
       a) Application: By applying the brick to a set of Theano
435
          variables a part of the computational graph of the final model is
436
          constructed.
437
       b) The initialization of parameters:
438
439
          1. Initialization configuration of children: The
440
             :meth:`push_initialization_config` method configures any
441
             children of this block.
442
          2. Initialization: This sets the initial values of the
443
             parameters by a call to :meth:`initialize`, which is needed
444
             to call the final compiled Theano function.  Also initializes
445
             all children.
446
447
    Not all stages need to be called explicitly. Step 3(a) will
448
    automatically allocate the parameters if needed. Similarly, step
449
    3(b.2) and 2(b) will automatically perform steps 3(b.1) and 2(a) if
450
    needed. They only need to be called separately if greater control is
451
    required. The only two methods which always need to be called are an
452
    application method to construct the computational graph, and the
453
    :meth:`initialize` method in order to initialize the parameters.
454
455
    At each different stage, a brick might need a certain set of
456
    configuration settings. All of these settings can be passed to the
457
    :meth:`__init__` constructor. However, by default many bricks support
458
    *lazy initialization*. This means that the configuration settings can
459
    be set later.
460
461
    .. note::
462
463
       Some arguments to :meth:`__init__` are *always* required, even when
464
       lazy initialization is enabled. Other arguments must be given before
465
       calling :meth:`allocate`, while others yet only need to be given in
466
       order to call :meth:`initialize`. Always read the documentation of
467
       each brick carefully.
468
469
    Lazy initialization can be turned off by setting ``Brick.lazy =
470
    False``. In this case, there is no need to call :meth:`initialize`
471
    manually anymore, but all the configuration must be passed to the
472
    :meth:`__init__` method.
473
474
    Parameters
475
    ----------
476
    name : str, optional
477
        The name of this brick. This can be used to filter the application
478
        of certain modifications by brick names. By default, the brick
479
        receives the name of its class (lowercased).
480
481
    Attributes
482
    ----------
483
    name : str
484
        The name of this brick.
485
    print_shapes : bool
486
        ``False`` by default. If ``True`` it logs the shapes of all the
487
        input and output variables, which can be useful for debugging.
488
    parameters : list of :class:`~tensor.TensorSharedVariable` and ``None``
489
        After calling the :meth:`allocate` method this attribute will be
490
        populated with the shared variables storing this brick's
491
        parameters. Allows for ``None`` so that parameters can always be
492
        accessed at the same index, even if some parameters are only
493
        defined given a particular configuration.
494
    children : list of bricks
495
        The children of this brick.
496
    allocated : bool
497
        ``False`` if :meth:`allocate` has not been called yet. ``True``
498
        otherwise.
499
    initialized : bool
500
        ``False`` if :meth:`allocate` has not been called yet. ``True``
501
        otherwise.
502
    allocation_config_pushed : bool
503
        ``False`` if :meth:`allocate` or :meth:`push_allocation_config`
504
        hasn't been called yet. ``True`` otherwise.
505
    initialization_config_pushed : bool
506
        ``False`` if :meth:`initialize` or
507
        :meth:`push_initialization_config` hasn't been called yet. ``True``
508
        otherwise.
509
510
    Notes
511
    -----
512
    To provide support for lazy initialization, apply the :meth:`lazy`
513
    decorator to the :meth:`__init__` method.
514
515
    Brick implementations *must* call the :meth:`__init__` constructor of
516
    their parent using `super(BlockImplementation,
517
    self).__init__(**kwargs)` at the *beginning* of the overriding
518
    `__init__`.
519
520
    The methods :meth:`_allocate` and :meth:`_initialize` need to be
521
    overridden if the brick needs to allocate shared variables and
522
    initialize their values in order to function.
523
524
    A brick can have any number of methods which apply the brick on Theano
525
    variables. These methods should be decorated with the
526
    :func:`application` decorator.
527
528
    If a brick has children, they must be listed in the :attr:`children`
529
    attribute. Moreover, if the brick wants to control the configuration of
530
    its children, the :meth:`_push_allocation_config` and
531
    :meth:`_push_initialization_config` methods need to be overridden.
532
533
    Examples
534
    --------
535
    Most bricks have lazy initialization enabled.
536
537
    >>> import theano
538
    >>> from blocks.initialization import IsotropicGaussian, Constant
539
    >>> from blocks.bricks import Linear
540
    >>> linear = Linear(input_dim=5, output_dim=3,
541
    ...                 weights_init=IsotropicGaussian(),
542
    ...                 biases_init=Constant(0))
543
    >>> x = theano.tensor.vector()
544
    >>> linear.apply(x)  # Calls linear.allocate() automatically
545
    linear_apply_output
546
    >>> linear.initialize()  # Initializes the weight matrix
547
548
    """
549
    #: See :attr:`Brick.print_shapes`
550
    print_shapes = False
551
552
    def __init__(self, name=None):
553
        if name is None:
554
            name = self.__class__.__name__.lower()
555
        self.name = name
556
557
        self.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