Completed
Pull Request — master (#977)
by Frédéric
02:51 queued 01:10
created

blocks.bricks.MLP.__init__()   C

Complexity

Conditions 7

Size

Total Lines 23

Duplication

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