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

Initializable.__getattr__()   B

Complexity

Conditions 5

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
dl 0
loc 8
rs 8.5454
c 1
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,
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
        initialization_to_role = {"weights_init": WEIGHT, 'biases_init': BIAS,
146
                                  'initial_state_init': INITIAL_STATE}
147
        for key in list(kwargs.keys()):
148
            if key[-5:] == "_init":
149
                if key not in initialization_to_role:
150
                    raise ValueError("The initlization scheme: {}".format(key),
151
                                     "is not defined by default, pass it"
152
                                     "via initialization_schemes")
153
                if initialization_to_role[key] in \
154
                        self.initialization_schemes.keys():
155
                    raise ValueError("All initializations are accepted either"
156
                                     "through initialization schemes or "
157
                                     "corresponding attribute but not both")
158
                else:
159
                    self.initialization_schemes[initialization_to_role[
160
                                                key]] = kwargs[key]
161
                kwargs.pop(key)
162
163
        super(Initializable, self).__init__(**kwargs)
164
165
    def _validate_roles(self):
166
        high_level_roles = []
167
        for role in self.parameter_roles:
168
            if role not in self.initialization_schemes.keys():
169
                for key in list(self.initialization_schemes.keys()):
170
                    if isinstance(role, type(key)):
171
                        self.initialization_schemes[role] = \
172
                                            self.initialization_schemes[key]
173
                        high_level_roles.append(key)
174
175
        for key in high_level_roles:
176
            if key not in self.parameter_roles:
177
                self.initialization_schemes.pop(key)
178
179
        for key in self.initialization_schemes:
180
            if key not in self.parameter_roles:
181
                raise ValueError("{} is not member of ".format(key) +
182
                                 "parameter_roles")
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
        for param in self.parameters:
213
            for role in param.tag.roles:
214
                if role in self.parameter_roles:
215
                    self.initialization_schemes[role].initialize(param,
216
                                                                 self.rng)
217
218
    def __getattr__(self, name):
219
        if name == "weights_init":
220
            if WEIGHT in self.initialization_schemes:
221
                return self.initialization_schemes[WEIGHT]
222
        elif name == "biases_init":
223
            if BIAS in self.initialization_schemes:
224
                return self.initialization_schemes[BIAS]
225
        super(Initializable, self).__getattr__(name)
226
227
    def __setattr__(self, name, value):
228
        if name == 'weights_init':
229
            self.initialization_schemes[WEIGHT] = value
230
        elif name == 'biases_init':
231
            self.initialization_schemes[BIAS] = value
232
        else:
233
            super(Initializable, self).__setattr__(name, value)
234
235
236
class LinearLike(Initializable):
237
    """Initializable subclass with logic for :class:`Linear`-like classes.
238
239
    Notes
240
    -----
241
    Provides `W` and `b` properties that can be overridden in subclasses
242
    to implement pre-application transformations on the weights and
243
    biases.  Application methods should refer to ``self.W`` and ``self.b``
244
    rather than accessing the parameters list directly.
245
246
    This assumes a layout of the parameters list with the weights coming
247
    first and biases (if ``use_bias`` is True) coming second.
248
249
    """
250
251
    @property
252
    def W(self):
253
        return self.parameters[0]
254
255
    @property
256
    def b(self):
257
        if getattr(self, 'use_bias', True):
258
            return self.parameters[1]
259
        else:
260
            raise AttributeError('use_bias is False')
261
262
263
class Random(Brick):
264
    """A mixin class for Bricks which need Theano RNGs.
265
266
    Parameters
267
    ----------
268
    theano_seed : int or list, optional
269
        Seed to use for a
270
        :class:`~theano.sandbox.rng_mrg.MRG_RandomStreams` object.
271
272
    """
273
    seed_rng = numpy.random.RandomState(config.default_seed)
274
275
    def __init__(self, theano_seed=None, **kwargs):
276
        super(Random, self).__init__(**kwargs)
277
        self.theano_seed = theano_seed
278
279
    @property
280
    def theano_seed(self):
281
        if getattr(self, '_theano_seed', None) is not None:
282
            return self._theano_seed
283
        else:
284
            self._theano_seed = self.seed_rng.randint(
285
                numpy.iinfo(numpy.int32).max)
286
            return self._theano_seed
287
288
    @theano_seed.setter
289
    def theano_seed(self, value):
290
        if hasattr(self, '_theano_seed'):
291
            raise AttributeError("seed already set")
292
        self._theano_seed = value
293
294
    @property
295
    def theano_rng(self):
296
        """Returns Brick's Theano RNG, or a default one.
297
298
        The default seed can be set through ``blocks.config``.
299
300
        """
301
        if not hasattr(self, '_theano_rng'):
302
            self._theano_rng = MRG_RandomStreams(self.theano_seed)
303
        return self._theano_rng
304
305
    @theano_rng.setter
306
    def theano_rng(self, theano_rng):
307
        self._theano_rng = theano_rng
308