Completed
Pull Request — master (#1163)
by David
174:59 queued 118:32
created

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