Completed
Pull Request — master (#1163)
by David
56:20
created

Sequence.__len__()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
"""Bricks that compose together other bricks in linear sequences."""
2
from collections import MutableSequence
3
import copy
4
from toolz import interleave, unique
5
from picklable_itertools.extras import equizip
6
7
from ..utils import pack
8
from .base import Brick, application, lazy
9
from .interfaces import Feedforward, Initializable
10
from .simple import Linear
11
12
13
class Sequence(Brick, MutableSequence):
14
    """A sequence of bricks.
15
16
    This brick applies a sequence of bricks, assuming that their in- and
17
    outputs are compatible.
18
19
    Parameters
20
    ----------
21
    application_methods : list
22
        List of :class:`.BoundApplication` or :class:`.Brick` to apply.
23
        For :class:`.Brick`s, the ``.apply`` method is used.
24
25
    """
26
    def __init__(self, application_methods, **kwargs):
27
        pairs = ((a.apply, a) if isinstance(a, Brick) else (a, a.brick)
28
                 for a in application_methods)
29
        self.application_methods, bricks = [list(s) for s in zip(*pairs)]
30
        kwargs.setdefault('children', []).extend(unique(bricks))
31
        super(Sequence, self).__init__(**kwargs)
32
33
    @application
34
    def apply(self, *args):
35
        child_input = args
36
        for application_method in self.application_methods:
37
            output = application_method(*pack(child_input))
38
            child_input = output
39
        return output
40
41
    @apply.property('inputs')
42
    def apply_inputs(self):
43
        return self.application_methods[0].inputs
44
45
    @apply.property('outputs')
46
    def apply_outputs(self):
47
        return self.application_methods[-1].outputs
48
49
    def __delitem__(self, index):
50
        application_method = self.application_methods[index]
51
        brick = application_method.brick
52
        try:
53
            del self.application_methods[index]
54
        finally:
55
            apps_with_brick = [a for a in self.application_methods
56
                               if a.brick is brick]
57
            if len(apps_with_brick) == 0:
58
                self.children.remove(brick)
59
60
    def __getitem__(self, index):
61
        return self.application_methods[index]
62
63
    def __setitem__(self, index, new_application):
64
        del self[index]
65
        self.insert(index, new_application)
66
67
    def insert(self, index, new_application):
68
        if new_application.brick not in self.children:
69
            self.children.append(new_application.brick)
70
        self.application_methods.insert(index, new_application)
71
72
    def __len__(self):
73
        return len(self.application_methods)
74
75
76
class FeedforwardSequence(Sequence, Feedforward):
77
    """A sequence where the first and last bricks are feedforward.
78
79
    Parameters
80
    ----------
81
    application_methods : list
82
        List of :class:`.BoundApplication` to apply. The first and last
83
        application method should belong to a :class:`Feedforward` brick.
84
85
    """
86
    @property
87
    def input_dim(self):
88
        return self.children[0].input_dim
89
90
    @input_dim.setter
91
    def input_dim(self, value):
92
        self.children[0].input_dim = value
93
94
    @property
95
    def output_dim(self):
96
        return self.children[-1].output_dim
97
98
    @output_dim.setter
99
    def output_dim(self, value):
100
        self.children[-1].output_dim = value
101
102
103
class MLP(FeedforwardSequence, Initializable):
104
    """A simple multi-layer perceptron.
105
106
    Parameters
107
    ----------
108
    activations : list of :class:`.Brick`, :class:`.BoundApplication`,
109
                  or ``None``
110
        A list of activations to apply after each linear transformation.
111
        Give ``None`` to not apply any activation. It is assumed that the
112
        application method to use is ``apply``. Required for
113
        :meth:`__init__`.
114
    dims : list of ints
115
        A list of input dimensions, as well as the output dimension of the
116
        last layer. Required for :meth:`~.Brick.allocate`.
117
    prototype : :class:`.Brick`, optional
118
        The transformation prototype. A copy will be created for every
119
        activation. If not provided, an instance of :class:`~simple.Linear`
120
        will be used.
121
122
    Notes
123
    -----
124
    See :class:`Initializable` for initialization parameters.
125
126
    Note that the ``weights_init``, ``biases_init`` (as well as
127
    ``use_bias`` if set to a value other than the default of ``None``)
128
    configurations will overwrite those of the layers each time the
129
    :class:`MLP` is re-initialized. For more fine-grained control, push the
130
    configuration to the child layers manually before initialization.
131
132
    >>> from blocks.bricks import Tanh
133
    >>> from blocks.initialization import IsotropicGaussian, Constant
134
    >>> mlp = MLP(activations=[Tanh(), None], dims=[30, 20, 10],
135
    ...           weights_init=IsotropicGaussian(),
136
    ...           biases_init=Constant(1))
137
    >>> mlp.push_initialization_config()  # Configure children
138
    >>> mlp.children[0].weights_init = IsotropicGaussian(0.1)
139
    >>> mlp.initialize()
140
141
    """
142
    @lazy(allocation=['dims'])
143
    def __init__(self, activations, dims, prototype=None, **kwargs):
144
        self.activations = activations
145
        self.prototype = Linear() if prototype is None else prototype
146
        self.linear_transformations = []
147
        for i in range(len(activations)):
148
            linear = copy.deepcopy(self.prototype)
149
            name = self.prototype.__class__.__name__.lower()
150
            linear.name = '{}_{}'.format(name, i)
151
            self.linear_transformations.append(linear)
152
        if not dims:
153
            dims = [None] * (len(activations) + 1)
154
        self.dims = dims
155
        # Interleave the transformations and activations
156
        applications = [a for a in interleave([self.linear_transformations,
157
                                               activations]) if a is not None]
158
        super(MLP, self).__init__(applications, **kwargs)
159
160
    @property
161
    def input_dim(self):
162
        return self.dims[0]
163
164
    @input_dim.setter
165
    def input_dim(self, value):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'input_dim' method
Loading history...
166
        self.dims[0] = value
167
168
    @property
169
    def output_dim(self):
170
        return self.dims[-1]
171
172
    @output_dim.setter
173
    def output_dim(self, value):
0 ignored issues
show
Bug introduced by
Arguments number differs from overridden 'output_dim' method
Loading history...
174
        self.dims[-1] = value
175
176
    def _push_allocation_config(self):
177
        if not len(self.dims) - 1 == len(self.linear_transformations):
178
            raise ValueError
179
        for input_dim, output_dim, layer in \
180
                equizip(self.dims[:-1], self.dims[1:],
181
                        self.linear_transformations):
182
            layer.input_dim = input_dim
183
            layer.output_dim = output_dim
184
            if getattr(self, 'use_bias', None) is not None:
185
                layer.use_bias = self.use_bias
186