Completed
Pull Request — master (#1030)
by
unknown
04:44
created

Initializable._validate_roles()   C

Complexity

Conditions 9

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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