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

blocks.bricks.LinearLike   A

Complexity

Total Complexity 5

Size/Duplication

Total Lines 31
Duplicated Lines 0 %
Metric Value
dl 0
loc 31
rs 10
wmc 5

3 Methods

Rating   Name   Duplication   Size   Complexity  
A _initialize() 0 6 2
A W() 0 3 1
A b() 0 6 2
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
9
10
class ActivationDocumentation(_Brick):
11
    """Dynamically adds documentation to activations.
12
13
    Notes
14
    -----
15
    See http://bugs.python.org/issue12773.
16
17
    """
18
    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...
19
        classdict['__doc__'] = \
20
            """Elementwise application of {0} function.""".format(name.lower())
21
        if 'apply' in classdict:
22
            classdict['apply'].__doc__ = \
23
                """Apply the {0} function element-wise.
24
25
                Parameters
26
                ----------
27
                input_ : :class:`~tensor.TensorVariable`
28
                    Theano variable to apply {0} to, element-wise.
29
30
                Returns
31
                -------
32
                output : :class:`~tensor.TensorVariable`
33
                    The input with the activation function applied.
34
35
                """.format(name.lower())
36
        return super(ActivationDocumentation, cls).__new__(cls, name, bases,
37
                                                           classdict)
38
39
40
@add_metaclass(ActivationDocumentation)
41
class Activation(Brick):
42
    """A base class for simple, element-wise activation functions.
43
44
    This base class ensures that activation functions are automatically
45
    documented using the :class:`ActivationDocumentation` metaclass.
46
47
    """
48
    pass
49
50
51
class Feedforward(Brick):
52
    """Declares an interface for bricks with one input and one output.
53
54
    Many bricks have just one input and just one output (activations,
55
    :class:`Linear`, :class:`MLP`). To make such bricks interchangable
56
    in most contexts they should share an interface for configuring
57
    their input and output dimensions. This brick declares such an
58
    interface.
59
60
    Attributes
61
    ----------
62
    input_dim : int
63
        The input dimension of the brick.
64
    output_dim : int
65
        The output dimension of the brick.
66
67
    """
68
    def __getattr__(self, name):
69
        message = ("'{}' object does not have an attribute '{}'"
70
                   .format(self.__class__.__name__, name))
71
        if name in ('input_dim', 'output_dim'):
72
            message += (" (which is a part of 'Feedforward' interface it"
73
                        " claims to support)")
74
        raise AttributeError(message)
75
76
77
class RNGMixin(object):
78
    """Mixin for initialization random number generators."""
79
    seed_rng = numpy.random.RandomState(config.default_seed)
80
81
    @property
82
    def seed(self):
83
        if getattr(self, '_seed', None) is not None:
84
            return self._seed
85
        else:
86
            self._seed = self.seed_rng.randint(
87
                numpy.iinfo(numpy.int32).max)
88
            return self._seed
89
90
    @seed.setter
91
    def seed(self, value):
92
        if hasattr(self, '_seed'):
93
            raise AttributeError("seed already set")
94
        self._seed = value
95
96
    @property
97
    def rng(self):
98
        if getattr(self, '_rng', None) is not None:
99
            return self._rng
100
        else:
101
            self._rng = numpy.random.RandomState(self.seed)
102
            return self._rng
103
104
    @rng.setter
105
    def rng(self, rng):
106
        self._rng = rng
107
108
109
class Initializable(RNGMixin, Brick):
110
    """Base class for bricks which push parameter initialization.
111
112
    Many bricks will initialize children which perform a linear
113
    transformation, often with biases. This brick allows the weights
114
    and biases initialization to be configured in the parent brick and
115
    pushed down the hierarchy.
116
117
    Parameters
118
    ----------
119
    weights_init : object
120
        A `NdarrayInitialization` instance which will be used by to
121
        initialize the weight matrix. Required by
122
        :meth:`~.Brick.initialize`.
123
    biases_init : :obj:`object`, optional
124
        A `NdarrayInitialization` instance that will be used to initialize
125
        the biases. Required by :meth:`~.Brick.initialize` when `use_bias`
126
        is `True`. Only supported by bricks for which :attr:`has_biases` is
127
        ``True``.
128
    use_bias : :obj:`bool`, optional
129
        Whether to use a bias. Defaults to `True`. Required by
130
        :meth:`~.Brick.initialize`. Only supported by bricks for which
131
        :attr:`has_biases` is ``True``.
132
    rng : :class:`numpy.random.RandomState`
133
134
    Attributes
135
    ----------
136
    has_biases : bool
137
        ``False`` if the brick does not support biases, and only has
138
        :attr:`weights_init`.  For an example of this, see
139
        :class:`.Bidirectional`. If this is ``False``, the brick does not
140
        support the arguments ``biases_init`` or ``use_bias``.
141
142
    """
143
    has_biases = True
144
145
    @lazy()
146
    def __init__(self, weights_init=None, biases_init=None, use_bias=True,
147
                 seed=None, **kwargs):
148
        super(Initializable, self).__init__(**kwargs)
149
        self.weights_init = weights_init
150
        if self.has_biases:
151
            self.biases_init = biases_init
152
        elif biases_init is not None or not use_bias:
153
            raise ValueError("This brick does not support biases config")
154
        self.use_bias = use_bias
155
        self.seed = seed
156
157
    def _push_initialization_config(self):
158
        for child in self.children:
159
            if isinstance(child, Initializable):
160
                child.rng = self.rng
161
                if self.weights_init:
162
                    child.weights_init = self.weights_init
163
        if hasattr(self, 'biases_init') and self.biases_init:
164
            for child in self.children:
165
                if (isinstance(child, Initializable) and
166
                        hasattr(child, 'biases_init')):
167
                    child.biases_init = self.biases_init
168
169
170
class LinearLike(Initializable):
171
    """Initializable subclass with logic for :class:`Linear`-like classes.
172
173
    Notes
174
    -----
175
    Provides `W` and `b` properties that can be overridden in subclasses
176
    to implement pre-application transformations on the weights and
177
    biases.  Application methods should refer to ``self.W`` and ``self.b``
178
    rather than accessing the parameters list directly.
179
180
    This assumes a layout of the parameters list with the weights coming
181
    first and biases (if ``use_bias`` is True) coming second.
182
183
    """
184
    @property
185
    def W(self):
186
        return self.parameters[0]
187
188
    @property
189
    def b(self):
190
        if self.use_bias:
191
            return self.parameters[1]
192
        else:
193
            raise AttributeError('use_bias is False')
194
195
    def _initialize(self):
196
        # Use self.parameters[] references in case W and b are overridden
197
        # to return non-shared-variables.
198
        if self.use_bias:
199
            self.biases_init.initialize(self.parameters[1], self.rng)
200
        self.weights_init.initialize(self.parameters[0], self.rng)
201
202
203
class Random(Brick):
204
    """A mixin class for Bricks which need Theano RNGs.
205
206
    Parameters
207
    ----------
208
    theano_seed : int or list, optional
209
        Seed to use for a
210
        :class:`~theano.sandbox.rng_mrg.MRG_RandomStreams` object.
211
212
    """
213
    seed_rng = numpy.random.RandomState(config.default_seed)
214
215
    def __init__(self, theano_seed=None, **kwargs):
216
        super(Random, self).__init__(**kwargs)
217
        self.theano_seed = theano_seed
218
219
    @property
220
    def theano_seed(self):
221
        if getattr(self, '_theano_seed', None) is not None:
222
            return self._theano_seed
223
        else:
224
            self._theano_seed = self.seed_rng.randint(
225
                numpy.iinfo(numpy.int32).max)
226
            return self._theano_seed
227
228
    @theano_seed.setter
229
    def theano_seed(self, value):
230
        if hasattr(self, '_theano_seed'):
231
            raise AttributeError("seed already set")
232
        self._theano_seed = value
233
234
    @property
235
    def theano_rng(self):
236
        """Returns Brick's Theano RNG, or a default one.
237
238
        The default seed can be set through ``blocks.config``.
239
240
        """
241
        if not hasattr(self, '_theano_rng'):
242
            self._theano_rng = MRG_RandomStreams(self.theano_seed)
243
        return self._theano_rng
244
245
    @theano_rng.setter
246
    def theano_rng(self, theano_rng):
247
        self._theano_rng = theano_rng
248