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

Initializable._collect_roles()   B

Complexity

Conditions 6

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
dl 0
loc 15
rs 8
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Initializable.get_param_roles() 0 8 3
1
"""Bricks that are interfaces and/or mixins."""
2
import numpy
3
import inspect
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
12
class ActivationDocumentation(_Brick):
13
    """Dynamically adds documentation to activations.
14
15
    Notes
16
    -----
17
    See http://bugs.python.org/issue12773.
18
19
    """
20
    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...
21
        classdict['__doc__'] = \
22
            """Elementwise application of {0} function.""".format(name.lower())
23
        if 'apply' in classdict:
24
            classdict['apply'].__doc__ = \
25
                """Apply the {0} function element-wise.
26
27
                Parameters
28
                ----------
29
                input_ : :class:`~tensor.TensorVariable`
30
                    Theano variable to apply {0} to, element-wise.
31
32
                Returns
33
                -------
34
                output : :class:`~tensor.TensorVariable`
35
                    The input with the activation function applied.
36
37
                """.format(name.lower())
38
        return super(ActivationDocumentation, cls).__new__(cls, name, bases,
39
                                                           classdict)
40
41
42
@add_metaclass(ActivationDocumentation)
43
class Activation(Brick):
44
    """A base class for simple, element-wise activation functions.
45
46
    This base class ensures that activation functions are automatically
47
    documented using the :class:`ActivationDocumentation` metaclass.
48
49
    """
50
    pass
51
52
53
class Feedforward(Brick):
54
    """Declares an interface for bricks with one input and one output.
55
56
    Many bricks have just one input and just one output (activations,
57
    :class:`Linear`, :class:`MLP`). To make such bricks interchangable
58
    in most contexts they should share an interface for configuring
59
    their input and output dimensions. This brick declares such an
60
    interface.
61
62
    Attributes
63
    ----------
64
    input_dim : int
65
        The input dimension of the brick.
66
    output_dim : int
67
        The output dimension of the brick.
68
69
    """
70
    def __getattr__(self, name):
71
        message = ("'{}' object does not have an attribute '{}'"
72
                   .format(self.__class__.__name__, name))
73
        if name in ('input_dim', 'output_dim'):
74
            message += (" (which is a part of 'Feedforward' interface it"
75
                        " claims to support)")
76
        raise AttributeError(message)
77
78
79
class RNGMixin(object):
80
    """Mixin for initialization random number generators."""
81
    seed_rng = numpy.random.RandomState(config.default_seed)
82
83
    @property
84
    def seed(self):
85
        if getattr(self, '_seed', None) is not None:
86
            return self._seed
87
        else:
88
            self._seed = self.seed_rng.randint(
89
                numpy.iinfo(numpy.int32).max)
90
            return self._seed
91
92
    @seed.setter
93
    def seed(self, value):
94
        if hasattr(self, '_seed'):
95
            raise AttributeError("seed already set")
96
        self._seed = value
97
98
    @property
99
    def rng(self):
100
        if getattr(self, '_rng', None) is not None:
101
            return self._rng
102
        else:
103
            self._rng = numpy.random.RandomState(self.seed)
104
            return self._rng
105
106
    @rng.setter
107
    def rng(self, rng):
108
        self._rng = rng
109
110
111
class Initializable(RNGMixin, Brick):
112
    """Base class for bricks which push parameter initialization.
113
114
    Many bricks will initialize children which perform a linear
115
    transformation, often with biases. This brick allows the weights
116
    and biases initialization to be configured in the parent brick and
117
    pushed down the hierarchy.
118
119
    Parameters
120
    ----------
121
    weights_init : object
122
        A `NdarrayInitialization` instance which will be used by to
123
        initialize the weight matrix. Required by
124
        :meth:`~.Brick.initialize`.
125
    biases_init : :obj:`object`, optional
126
        A `NdarrayInitialization` instance that will be used to initialize
127
        the biases. Required by :meth:`~.Brick.initialize` when `use_bias`
128
        is `True`. Only supported by bricks for which :attr:`has_biases` is
129
        ``True``.
130
    use_bias : :obj:`bool`, optional
131
        Whether to use a bias. Defaults to `True`. Required by
132
        :meth:`~.Brick.initialize`.
133
    rng : :class:`numpy.random.RandomState`
134
135
    """
136
137
    @lazy()
138
    def __init__(self, initialization_schemes=None,
139
                 use_bias=True, seed=None, **kwargs):
140
        self.use_bias = use_bias
141
        self.seed = seed
142
        self.initialization_schemes = initialization_schemes
143
        if self.initialization_schemes is None:
144
            self.initialization_schemes = {}
145
146
        initialization_to_role = {"weights_init": WEIGHT, 'biases_init': BIAS,
147
                                  'initial_state_init': INITIAL_STATE}
148
        for key in list(kwargs.keys()):
149
            if key[-5:] == "_init":
150
                if key not in initialization_to_role:
151
                    raise ValueError("The initlization scheme: {}".format(key),
152
                                     "is not defined by default, pass it"
153
                                     "via initialization_schemes")
154
                if initialization_to_role[key] in \
155
                        self.initialization_schemes.keys():
156
                    raise ValueError("All initializations are accepted either"
157
                                     "through initialization schemes or "
158
                                     "corresponding attribute but not both")
159
                else:
160
                    self.initialization_schemes[initialization_to_role[
161
                                                key]] = kwargs[key]
162
                kwargs.pop(key)
163
164
        super(Initializable, self).__init__(**kwargs)
165
166
    def get_scheme(role, schemes):
0 ignored issues
show
Coding Style Best Practice introduced by
Methods should have self as first argument.

It is a widespread convention and generally a good practice to name the first argument of methods self.

class SomeClass:
    def some_method(self):
        # ... do something
Loading history...
167
        for key in schemes:
168
            if role == type(key):
169
                return key
170
        for key in schemes:
171
            if isinstance(role, type(key)):
172
                return key
173
174
    def _validate_roles(self):
175
        all_parent_roles = []
176
        for role in self.parameter_roles:
177
            all_parent_roles += list(inspect.getmro(type(role)))
178
179
        for key in self.initialization_schemes:
180
            if type(key) not in all_parent_roles:
181
                raise ValueError("There is no parameter role"
182
                                 "for initlization sheme {}".format(key))
183
184
    def _push_initialization_config(self):
185
        self._collect_roles()
186
        self._validate_roles()
187
        for child in self.children:
188
            if (isinstance(child, Initializable) and
189
                    hasattr(child, 'initialization_schemes')):
190
                child.rng = self.rng
191
                for role, scheme in self.initialization_schemes.items():
192
                    if role in child.parameter_roles:
193
                        child.initialization_schemes[role] = scheme
194
195
    def _collect_roles(self):
196
        def get_param_roles(obj):
197
            all_roles = []
198
            for param in obj.parameters:
199
                roles = param.tag.roles
200
                # TODO do something smarter
201
                if len(roles) > 0:
202
                    all_roles.append(roles[0])
203
            return all_roles
204
205
        self.parameter_roles = set(get_param_roles(self))
206
        for child in self.children:
207
            if isinstance(child, Initializable):
208
                child._collect_roles()
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _collect_roles was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
209
                self.parameter_roles.update(child.parameter_roles)
210
211
    def _initialize(self):
212
        def get_scheme(role, schemes):
213
            if role in schemes:
214
                return role
215
            for key in schemes:
216
                if role == type(key):
217
                    return key
218
            for key in schemes:
219
                if isinstance(role, type(key)):
220
                    return key
221
222
        for param in self.parameters:
223
            for role in param.tag.roles:
224
                if role in self.parameter_roles:
225
                    key = get_scheme(role, self.initialization_schemes.keys())
226
                    if key is not None:
227
                        self.initialization_schemes[key].initialize(param,
228
                                                                    self.rng)
229
                        continue
230
231
    def __getattr__(self, name):
232
        if name == "weights_init":
233
            if WEIGHT in self.initialization_schemes:
234
                return self.initialization_schemes[WEIGHT]
235
        elif name == "biases_init":
236
            if BIAS in self.initialization_schemes:
237
                return self.initialization_schemes[BIAS]
238
        super(Initializable, self).__getattr__(name)
239
240
    def __setattr__(self, name, value):
241
        if name == 'weights_init':
242
            self.initialization_schemes[WEIGHT] = value
243
        elif name == 'biases_init':
244
            self.initialization_schemes[BIAS] = value
245
        else:
246
            super(Initializable, self).__setattr__(name, value)
247
248
249
class LinearLike(Initializable):
250
    """Initializable subclass with logic for :class:`Linear`-like classes.
251
252
    Notes
253
    -----
254
    Provides `W` and `b` properties that can be overridden in subclasses
255
    to implement pre-application transformations on the weights and
256
    biases.  Application methods should refer to ``self.W`` and ``self.b``
257
    rather than accessing the parameters list directly.
258
259
    This assumes a layout of the parameters list with the weights coming
260
    first and biases (if ``use_bias`` is True) coming second.
261
262
    """
263
264
    @property
265
    def W(self):
266
        return self.parameters[0]
267
268
    @property
269
    def b(self):
270
        if getattr(self, 'use_bias', True):
271
            return self.parameters[1]
272
        else:
273
            raise AttributeError('use_bias is False')
274
275
276
class Random(Brick):
277
    """A mixin class for Bricks which need Theano RNGs.
278
279
    Parameters
280
    ----------
281
    theano_seed : int or list, optional
282
        Seed to use for a
283
        :class:`~theano.sandbox.rng_mrg.MRG_RandomStreams` object.
284
285
    """
286
    seed_rng = numpy.random.RandomState(config.default_seed)
287
288
    def __init__(self, theano_seed=None, **kwargs):
289
        super(Random, self).__init__(**kwargs)
290
        self.theano_seed = theano_seed
291
292
    @property
293
    def theano_seed(self):
294
        if getattr(self, '_theano_seed', None) is not None:
295
            return self._theano_seed
296
        else:
297
            self._theano_seed = self.seed_rng.randint(
298
                numpy.iinfo(numpy.int32).max)
299
            return self._theano_seed
300
301
    @theano_seed.setter
302
    def theano_seed(self, value):
303
        if hasattr(self, '_theano_seed'):
304
            raise AttributeError("seed already set")
305
        self._theano_seed = value
306
307
    @property
308
    def theano_rng(self):
309
        """Returns Brick's Theano RNG, or a default one.
310
311
        The default seed can be set through ``blocks.config``.
312
313
        """
314
        if not hasattr(self, '_theano_rng'):
315
            self._theano_rng = MRG_RandomStreams(self.theano_seed)
316
        return self._theano_rng
317
318
    @theano_rng.setter
319
    def theano_rng(self, theano_rng):
320
        self._theano_rng = theano_rng
321