Completed
Pull Request — master (#1030)
by
unknown
09:15 queued 04:36
created

Initializable.__init__()   D

Complexity

Conditions 8

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
dl 0
loc 41
rs 4
c 0
b 0
f 0
1
"""Bricks that are interfaces and/or mixins."""
2
import numpy
3
import logging
4
from six import add_metaclass
5
from theano.sandbox.rng_mrg import MRG_RandomStreams
6
7
from ..config import config
8
from .base import _Brick, Brick, lazy
9
from blocks.roles import WEIGHT, BIAS, FILTER, INITIAL_STATE
0 ignored issues
show
Unused Code introduced by
Unused FILTER imported from blocks.roles
Loading history...
10
11
logger = logging.getLogger(__name__)
12
13
14
class ActivationDocumentation(_Brick):
15
    """Dynamically adds documentation to activations.
16
17
    Notes
18
    -----
19
    See http://bugs.python.org/issue12773.
20
21
    """
22
    def __new__(cls, name, bases, classdict):
0 ignored issues
show
Coding Style Best Practice introduced by
The first argument of the __new__ class method of metaclasses should be named mcs by convention.
Loading history...
23
        classdict['__doc__'] = \
24
            """Elementwise application of {0} function.""".format(name.lower())
25
        if 'apply' in classdict:
26
            classdict['apply'].__doc__ = \
27
                """Apply the {0} function element-wise.
28
29
                Parameters
30
                ----------
31
                input_ : :class:`~tensor.TensorVariable`
32
                    Theano variable to apply {0} to, element-wise.
33
34
                Returns
35
                -------
36
                output : :class:`~tensor.TensorVariable`
37
                    The input with the activation function applied.
38
39
                """.format(name.lower())
40
        return super(ActivationDocumentation, cls).__new__(cls, name, bases,
41
                                                           classdict)
42
43
44
@add_metaclass(ActivationDocumentation)
45
class Activation(Brick):
46
    """A base class for simple, element-wise activation functions.
47
48
    This base class ensures that activation functions are automatically
49
    documented using the :class:`ActivationDocumentation` metaclass.
50
51
    """
52
    pass
53
54
55
class Feedforward(Brick):
56
    """Declares an interface for bricks with one input and one output.
57
58
    Many bricks have just one input and just one output (activations,
59
    :class:`Linear`, :class:`MLP`). To make such bricks interchangable
60
    in most contexts they should share an interface for configuring
61
    their input and output dimensions. This brick declares such an
62
    interface.
63
64
    Attributes
65
    ----------
66
    input_dim : int
67
        The input dimension of the brick.
68
    output_dim : int
69
        The output dimension of the brick.
70
71
    """
72
    def __getattr__(self, name):
73
        message = ("'{}' object does not have an attribute '{}'"
74
                   .format(self.__class__.__name__, name))
75
        if name in ('input_dim', 'output_dim'):
76
            message += (" (which is a part of 'Feedforward' interface it"
77
                        " claims to support)")
78
        raise AttributeError(message)
79
80
81
class RNGMixin(object):
82
    """Mixin for initialization random number generators."""
83
    seed_rng = numpy.random.RandomState(config.default_seed)
84
85
    @property
86
    def seed(self):
87
        if getattr(self, '_seed', None) is not None:
88
            return self._seed
89
        else:
90
            self._seed = self.seed_rng.randint(
91
                numpy.iinfo(numpy.int32).max)
92
            return self._seed
93
94
    @seed.setter
95
    def seed(self, value):
96
        if hasattr(self, '_seed'):
97
            raise AttributeError("seed already set")
98
        self._seed = value
99
100
    @property
101
    def rng(self):
102
        if getattr(self, '_rng', None) is not None:
103
            return self._rng
104
        else:
105
            self._rng = numpy.random.RandomState(self.seed)
106
            return self._rng
107
108
    @rng.setter
109
    def rng(self, rng):
110
        self._rng = rng
111
112
113
class Initializable(RNGMixin, Brick):
114
    """Base class for bricks which push parameter initialization.
115
116
    Many bricks will initialize children which perform a linear
117
    transformation, often with biases. This brick allows the weights
118
    and biases initialization to be configured in the parent brick and
119
    pushed down the hierarchy.
120
121
    Parameters
122
    ----------
123
    weights_init : object
124
        A `NdarrayInitialization` instance which will be used by to
125
        initialize the weight matrix. Required by
126
        :meth:`~.Brick.initialize`.
127
    biases_init : :obj:`object`, optional
128
        A `NdarrayInitialization` instance that will be used to initialize
129
        the biases. Required by :meth:`~.Brick.initialize` when `use_bias`
130
        is `True`. Only supported by bricks for which :attr:`has_biases` is
131
        ``True``.
132
    use_bias : :obj:`bool`, optional
133
        Whether to use a bias. Defaults to `True`. Required by
134
        :meth:`~.Brick.initialize`.
135
    rng : :class:`numpy.random.RandomState`
136
137
    """
138
139
    @lazy()
140
    def __init__(self, initialization_schemes=None, parameter_roles=None,
141
                 use_bias=True, seed=None, **kwargs):
142
        self.use_bias = use_bias
143
        self.seed = seed
144
        self.initialization_schemes = initialization_schemes
145
        if self.initialization_schemes is None:
146
            self.initialization_schemes = {}
147
148
        if parameter_roles:
149
            self.parameter_roles = parameter_roles
150
        else:
151
            # logger.warning("The block has not received parameter_roles, "
152
            #                "hence only the default WEIGHT and BIAS are set."
153
            #                "It's a good idea to manually set the roles "
154
            #                "of all the initlizable parameters inside "
155
            #                "parameter_roles")
156
            self.parameter_roles = set([WEIGHT])
157
            if use_bias:
158
                self.parameter_roles.update(set([BIAS]))
159
160
        initialization_to_role = {"weights_init": WEIGHT, 'biases_init': BIAS,
161
                                  'initial_state_init': INITIAL_STATE}
162
        for key in list(kwargs.keys()):
163
            if key[-5:] == "_init":
164
                if key not in initialization_to_role:
165
                    raise ValueError("The initlization scheme: {}".format(key),
166
                                     "is not defined by default, pass it"
167
                                     "via initialization_schemes")
168
                if initialization_to_role[key] in \
169
                        self.initialization_schemes.keys():
170
                    raise ValueError("All initializations are accepted either"
171
                                     "through initialization schemes or "
172
                                     "corresponding attribute but not both")
173
                else:
174
                    self.initialization_schemes[initialization_to_role[
175
                                                key]] = kwargs[key]
176
                kwargs.pop(key)
177
178
        super(Initializable, self).__init__(**kwargs)
179
        self._collect_roles()
180
181
    def _validate_roles(self):
182
        high_level_roles = []
183
        for role in self.parameter_roles:
184
            if role not in self.initialization_schemes.keys():
185
                for key in self.initialization_schemes.keys():
186
                    if isinstance(role, type(key)):
187
                        self.initialization_schemes[role] = \
188
                                            self.initialization_schemes[key]
189
                        high_level_roles.append(key)
190
191
        for key in high_level_roles:
192
            if key not in self.parameter_roles:
193
                self.initialization_schemes.pop(key)
194
195
        for key in self.initialization_schemes:
196
            if key not in self.parameter_roles:
197
                raise ValueError("{} is not member of ".format(key) +
198
                                 "parameter_roles")
199
200
    def _push_initialization_config(self):
201
        self._validate_roles()
202
        for child in self.children:
203
            if (isinstance(child, Initializable) and
204
                    hasattr(child, 'initialization_schemes')):
205
                child.rng = self.rng
206
                for role, scheme in self.initialization_schemes.items():
207
                    if role in child.parameter_roles:
208
                        child.initialization_schemes[role] = scheme
209
210
    def _collect_roles(self):
211
        for child in self.children:
212
            if isinstance(child, Initializable):
213
                self.parameter_roles.update(child.parameter_roles)
214
215
    def _initialize(self):
216
        for param in self.parameters:
217
            for role in param.tag.roles:
218
                if role in self.parameter_roles:
219
                    self.initialization_schemes[role].initialize(param,
220
                                                                 self.rng)
221
222
    def __getattr__(self, name):
223
        if name == "weights_init":
224
            if WEIGHT in self.initialization_schemes:
225
                return self.initialization_schemes[WEIGHT]
226
        elif name == "biases_init":
227
            if BIAS in self.initialization_schemes:
228
                return self.initialization_schemes[BIAS]
229
        super(Initializable, self).__getattr__(name)
230
231
    def __setattr__(self, name, value):
232
        if name == 'weights_init':
233
            self.initialization_schemes[WEIGHT] = value
234
        elif name == 'biases_init':
235
            self.initialization_schemes[BIAS] = value
236
        else:
237
            super(Initializable, self).__setattr__(name, value)
238
239
240
class LinearLike(Initializable):
241
    """Initializable subclass with logic for :class:`Linear`-like classes.
242
243
    Notes
244
    -----
245
    Provides `W` and `b` properties that can be overridden in subclasses
246
    to implement pre-application transformations on the weights and
247
    biases.  Application methods should refer to ``self.W`` and ``self.b``
248
    rather than accessing the parameters list directly.
249
250
    This assumes a layout of the parameters list with the weights coming
251
    first and biases (if ``use_bias`` is True) coming second.
252
253
    """
254
255
    def __init__(self, **kwargs):
256
        if 'parameter_roles' in kwargs:
257
            kwargs['parameter_roles'].update(set([WEIGHT, BIAS]))
258
        else:
259
            kwargs['parameter_roles'] = set([WEIGHT, BIAS])
260
        super(LinearLike, self).__init__(**kwargs)
261
262
    @property
263
    def W(self):
264
        return self.parameters[0]
265
266
    @property
267
    def b(self):
268
        if getattr(self, 'use_bias', True):
269
            return self.parameters[1]
270
        else:
271
            raise AttributeError('use_bias is False')
272
273
274
class Random(Brick):
275
    """A mixin class for Bricks which need Theano RNGs.
276
277
    Parameters
278
    ----------
279
    theano_seed : int or list, optional
280
        Seed to use for a
281
        :class:`~theano.sandbox.rng_mrg.MRG_RandomStreams` object.
282
283
    """
284
    seed_rng = numpy.random.RandomState(config.default_seed)
285
286
    def __init__(self, theano_seed=None, **kwargs):
287
        super(Random, self).__init__(**kwargs)
288
        self.theano_seed = theano_seed
289
290
    @property
291
    def theano_seed(self):
292
        if getattr(self, '_theano_seed', None) is not None:
293
            return self._theano_seed
294
        else:
295
            self._theano_seed = self.seed_rng.randint(
296
                numpy.iinfo(numpy.int32).max)
297
            return self._theano_seed
298
299
    @theano_seed.setter
300
    def theano_seed(self, value):
301
        if hasattr(self, '_theano_seed'):
302
            raise AttributeError("seed already set")
303
        self._theano_seed = value
304
305
    @property
306
    def theano_rng(self):
307
        """Returns Brick's Theano RNG, or a default one.
308
309
        The default seed can be set through ``blocks.config``.
310
311
        """
312
        if not hasattr(self, '_theano_rng'):
313
            self._theano_rng = MRG_RandomStreams(self.theano_seed)
314
        return self._theano_rng
315
316
    @theano_rng.setter
317
    def theano_rng(self, theano_rng):
318
        self._theano_rng = theano_rng
319