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

Sequence.__delitem__()   A

Complexity

Conditions 4

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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