Completed
Pull Request — master (#1163)
by David
53:37
created

Sequence.__delitem__()   A

Complexity

Conditions 4

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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