Completed
Pull Request — master (#1009)
by David
01:30
created

blocks.bricks.LinearLike   A

Complexity

Total Complexity 5

Size/Duplication

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

3 Methods

Rating   Name   Duplication   Size   Complexity  
A _initialize() 0 4 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
    """
181
    @property
182
    def W(self):
183
        return self.parameters[0]
184
185
    @property
186
    def b(self):
187
        if self.use_bias:
188
            return self.parameters[1]
189
        else:
190
            raise AttributeError('use_bias is False')
191
192
    def _initialize(self):
193
        if self.use_bias:
194
            self.biases_init.initialize(self.b, self.rng)
195
        self.weights_init.initialize(self.W, self.rng)
196
197
198
class Random(Brick):
199
    """A mixin class for Bricks which need Theano RNGs.
200
201
    Parameters
202
    ----------
203
    theano_seed : int or list, optional
204
        Seed to use for a
205
        :class:`~theano.sandbox.rng_mrg.MRG_RandomStreams` object.
206
207
    """
208
    seed_rng = numpy.random.RandomState(config.default_seed)
209
210
    def __init__(self, theano_seed=None, **kwargs):
211
        super(Random, self).__init__(**kwargs)
212
        self.theano_seed = theano_seed
213
214
    @property
215
    def theano_seed(self):
216
        if getattr(self, '_theano_seed', None) is not None:
217
            return self._theano_seed
218
        else:
219
            self._theano_seed = self.seed_rng.randint(
220
                numpy.iinfo(numpy.int32).max)
221
            return self._theano_seed
222
223
    @theano_seed.setter
224
    def theano_seed(self, value):
225
        if hasattr(self, '_theano_seed'):
226
            raise AttributeError("seed already set")
227
        self._theano_seed = value
228
229
    @property
230
    def theano_rng(self):
231
        """Returns Brick's Theano RNG, or a default one.
232
233
        The default seed can be set through ``blocks.config``.
234
235
        """
236
        if not hasattr(self, '_theano_rng'):
237
            self._theano_rng = MRG_RandomStreams(self.theano_seed)
238
        return self._theano_rng
239
240
    @theano_rng.setter
241
    def theano_rng(self, theano_rng):
242
        self._theano_rng = theano_rng
243