Completed
Push — master ( df5c55...3eddcf )
by Dmitry
03:41
created

blocks.bricks.ConvolutionalSequence.__init__()   A

Complexity

Conditions 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 13
rs 9.4286
1
from theano.tensor.nnet.conv import conv2d, ConvOp
2
from theano.tensor.signal.downsample import max_pool_2d, DownsampleFactorMax
3
4
from blocks.bricks import Initializable, Feedforward, Sequence
5
from blocks.bricks.base import application, Brick, lazy
6
from blocks.roles import add_role, FILTER, BIAS
7
from blocks.utils import shared_floatx_nans
8
9
10
class Convolutional(Initializable):
11
    """Performs a 2D convolution.
12
13
    Parameters
14
    ----------
15
    filter_size : tuple
16
        The height and width of the filter (also called *kernels*).
17
    num_filters : int
18
        Number of filters per channel.
19
    num_channels : int
20
        Number of input channels in the image. For the first layer this is
21
        normally 1 for grayscale images and 3 for color (RGB) images. For
22
        subsequent layers this is equal to the number of filters output by
23
        the previous convolutional layer. The filters are pooled over the
24
        channels.
25
    batch_size : int, optional
26
        Number of examples per batch. If given, this will be passed to
27
        Theano convolution operator, possibly resulting in faster
28
        execution.
29
    image_size : tuple, optional
30
        The height and width of the input (image or feature map). If given,
31
        this will be passed to the Theano convolution operator, resulting
32
        in possibly faster execution times.
33
    step : tuple, optional
34
        The step (or stride) with which to slide the filters over the
35
        image. Defaults to (1, 1).
36
    border_mode : {'valid', 'full'}, optional
37
        The border mode to use, see :func:`scipy.signal.convolve2d` for
38
        details. Defaults to 'valid'.
39
    tied_biases : bool
40
        If ``True``, it indicates that the biases of every filter in this
41
        layer should be shared amongst all applications of that filter.
42
        Setting this to ``False`` will untie the biases, yielding a
43
        separate bias for every location at which the filter is applied.
44
        Defaults to ``False``.
45
46
    """
47
    # Make it possible to override the implementation of conv2d that gets
48
    # used, i.e. to use theano.sandbox.cuda.dnn.dnn_conv directly in order
49
    # to leverage features not yet available in Theano's standard conv2d.
50
    # The function you override with here should accept at least the
51
    # input and the kernels as positionals, and the keyword arguments
52
    # image_shape, subsample, border_mode, and filter_shape. If some of
53
    # these are unsupported they should still be accepted and ignored,
54
    # e.g. with a wrapper function that swallows **kwargs.
55
    conv2d_impl = staticmethod(conv2d)
56
57
    # Used to override the output shape computation for a given value of
58
    # conv2d_impl. Should accept 4 positional arguments: the image size,
59
    # the filter size, the step (strides), and the border mode.
60
    get_output_shape = staticmethod(ConvOp.getOutputShape)
61
62
    @lazy(allocation=['filter_size', 'num_filters', 'num_channels'])
63
    def __init__(self, filter_size, num_filters, num_channels, batch_size=None,
64
                 image_size=(None, None), step=(1, 1), border_mode='valid',
65
                 tied_biases=False, **kwargs):
66
        super(Convolutional, self).__init__(**kwargs)
67
68
        self.filter_size = filter_size
69
        self.num_filters = num_filters
70
        self.batch_size = batch_size
71
        self.num_channels = num_channels
72
        self.image_size = image_size
73
        self.step = step
74
        self.border_mode = border_mode
75
        self.tied_biases = tied_biases
76
77
    def _allocate(self):
78
        W = shared_floatx_nans((self.num_filters, self.num_channels) +
79
                               self.filter_size, name='W')
80
        add_role(W, FILTER)
81
        self.parameters.append(W)
82
        self.add_auxiliary_variable(W.norm(2), name='W_norm')
83
        if self.use_bias:
84
            if self.tied_biases:
85
                b = shared_floatx_nans((self.num_filters,), name='b')
86
            else:
87
                # this error is raised here instead of during initializiation
88
                # because ConvolutionalSequence may specify the image size
89
                if self.image_size == (None, None) and not self.tied_biases:
90
                    raise ValueError('Cannot infer bias size without '
91
                                     'image_size specified. If you use '
92
                                     'variable image_size, you should use '
93
                                     'tied_biases=True.')
94
95
                b = shared_floatx_nans(self.get_dim('output'), name='b')
96
            add_role(b, BIAS)
97
98
            self.parameters.append(b)
99
            self.add_auxiliary_variable(b.norm(2), name='b_norm')
100
101
    def _initialize(self):
102
        if self.use_bias:
103
            W, b = self.parameters
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 610 of blocks.bricks.base seems to be unbalanced; 2 value(s) for 0 label(s)

This happens when the amount of values does not equal the amount of labels:

a, b = ("a", "b", "c")  # only 2 labels for 3 values
Loading history...
104
            self.biases_init.initialize(b, self.rng)
105
        else:
106
            W, = self.parameters
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 610 of blocks.bricks.base seems to be unbalanced; 1 value(s) for 0 label(s)

This happens when the amount of values does not equal the amount of labels:

a, b = ("a", "b", "c")  # only 2 labels for 3 values
Loading history...
107
        self.weights_init.initialize(W, self.rng)
108
109
    @application(inputs=['input_'], outputs=['output'])
110
    def apply(self, input_):
111
        """Perform the convolution.
112
113
        Parameters
114
        ----------
115
        input_ : :class:`~tensor.TensorVariable`
116
            A 4D tensor with the axes representing batch size, number of
117
            channels, image height, and image width.
118
119
        Returns
120
        -------
121
        output : :class:`~tensor.TensorVariable`
122
            A 4D tensor of filtered images (feature maps) with dimensions
123
            representing batch size, number of filters, feature map height,
124
            and feature map width.
125
126
            The height and width of the feature map depend on the border
127
            mode. For 'valid' it is ``image_size - filter_size + 1`` while
128
            for 'full' it is ``image_size + filter_size - 1``.
129
130
        """
131
        if self.use_bias:
132
            W, b = self.parameters
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 610 of blocks.bricks.base seems to be unbalanced; 2 value(s) for 0 label(s)

This happens when the amount of values does not equal the amount of labels:

a, b = ("a", "b", "c")  # only 2 labels for 3 values
Loading history...
133
        else:
134
            W, = self.parameters
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 610 of blocks.bricks.base seems to be unbalanced; 1 value(s) for 0 label(s)

This happens when the amount of values does not equal the amount of labels:

a, b = ("a", "b", "c")  # only 2 labels for 3 values
Loading history...
135
136
        if self.image_size == (None, None):
137
            image_shape = None
138
        else:
139
            image_shape = (self.batch_size, self.num_channels)
140
            image_shape += self.image_size
141
142
        output = self.conv2d_impl(
143
            input_, W,
144
            image_shape=image_shape,
145
            subsample=self.step,
146
            border_mode=self.border_mode,
147
            filter_shape=((self.num_filters, self.num_channels) +
148
                          self.filter_size))
149
        if self.use_bias:
150
            if self.tied_biases:
151
                output += b.dimshuffle('x', 0, 'x', 'x')
152
            else:
153
                output += b.dimshuffle('x', 0, 1, 2)
154
        return output
155
156
    def get_dim(self, name):
157
        if name == 'input_':
158
            return (self.num_channels,) + self.image_size
159
        if name == 'output':
160
            return ((self.num_filters,) +
161
                    self.get_output_shape(self.image_size, self.filter_size,
162
                                          self.step, self.border_mode))
163
        return super(Convolutional, self).get_dim(name)
164
165
    @property
166
    def num_output_channels(self):
167
        return self.num_filters
168
169
170
class Pooling(Initializable, Feedforward):
171
    """Base Brick for pooling operations.
172
173
    This should generally not be instantiated directly; see
174
    :class:`MaxPooling`.
175
176
    """
177
    @lazy(allocation=['mode', 'pooling_size'])
178
    def __init__(self, mode, pooling_size, step, input_dim, ignore_border,
179
                 padding, **kwargs):
180
        super(Pooling, self).__init__(**kwargs)
181
        self.pooling_size = pooling_size
182
        self.mode = mode
183
        self.step = step
184
        self.input_dim = input_dim if input_dim is not None else (None,) * 3
185
        self.ignore_border = ignore_border
186
        self.padding = padding
187
188
    @property
189
    def image_size(self):
190
        return self.input_dim[-2:]
191
192
    @image_size.setter
193
    def image_size(self, value):
194
        self.input_dim = self.input_dim[:-2] + value
195
196
    @property
197
    def num_channels(self):
198
        return self.input_dim[0]
199
200
    @num_channels.setter
201
    def num_channels(self, value):
202
        self.input_dim = (value,) + self.input_dim[1:]
203
204
    @application(inputs=['input_'], outputs=['output'])
205
    def apply(self, input_):
206
        """Apply the pooling (subsampling) transformation.
207
208
        Parameters
209
        ----------
210
        input_ : :class:`~tensor.TensorVariable`
211
            An tensor with dimension greater or equal to 2. The last two
212
            dimensions will be downsampled. For example, with images this
213
            means that the last two dimensions should represent the height
214
            and width of your image.
215
216
        Returns
217
        -------
218
        output : :class:`~tensor.TensorVariable`
219
            A tensor with the same number of dimensions as `input_`, but
220
            with the last two dimensions downsampled.
221
222
        """
223
        output = max_pool_2d(input_, self.pooling_size, st=self.step,
224
                             mode=self.mode, padding=self.padding,
225
                             ignore_border=self.ignore_border)
226
        return output
227
228
    def get_dim(self, name):
229
        if name == 'input_':
230
            return self.input_dim
231
        if name == 'output':
232
            return tuple(DownsampleFactorMax.out_shape(
233
                self.input_dim, self.pooling_size, st=self.step,
234
                ignore_border=self.ignore_border, padding=self.padding))
235
236
    @property
237
    def num_output_channels(self):
238
        return self.input_dim[0]
239
240
241
class MaxPooling(Pooling):
242
    """Max pooling layer.
243
244
    Parameters
245
    ----------
246
    pooling_size : tuple
247
        The height and width of the pooling region i.e. this is the factor
248
        by which your input's last two dimensions will be downscaled.
249
    step : tuple, optional
250
        The vertical and horizontal shift (stride) between pooling regions.
251
        By default this is equal to `pooling_size`. Setting this to a lower
252
        number results in overlapping pooling regions.
253
    input_dim : tuple, optional
254
        A tuple of integers representing the shape of the input. The last
255
        two dimensions will be used to calculate the output dimension.
256
    padding : tuple, optional
257
        A tuple of integers representing the vertical and horizontal
258
        zero-padding to be applied to each of the top and bottom
259
        (vertical) and left and right (horizontal) edges. For example,
260
        an argument of (4, 3) will apply 4 pixels of padding to the
261
        top edge, 4 pixels of padding to the bottom edge, and 3 pixels
262
        each for the left and right edge. By default, no padding is
263
        performed.
264
    ignore_border : bool, optional
265
        Whether or not to do partial downsampling based on borders where
266
        the extent of the pooling region reaches beyond the edge of the
267
        image. If `True`, a (5, 5) image with (2, 2) pooling regions
268
        and (2, 2) step will be downsampled to shape (2, 2), otherwise
269
        it will be downsampled to (3, 3). `True` by default.
270
271
    Notes
272
    -----
273
    .. warning::
274
        As of this writing, setting `ignore_border` to `False` with a step
275
        not equal to the pooling size will force Theano to perform pooling
276
        computations on CPU rather than GPU, even if you have specified
277
        a GPU as your computation device. Additionally, Theano will only
278
        use [cuDNN]_ (if available) for pooling computations with
279
        `ignure_border` set to `True`. You can ensure that the entire
280
        input is captured by at least one pool by using the `padding`
281
        argument to add zero padding prior to pooling being performed.
282
283
    .. [cuDNN]: `NVIDIA cuDNN <https://developer.nvidia.com/cudnn>`_.
284
285
    """
286
    @lazy(allocation=['pooling_size'])
287
    def __init__(self, pooling_size, step=None, input_dim=None,
288
                 ignore_border=True, padding=(0, 0),
289
                 **kwargs):
290
        super(MaxPooling, self).__init__('max', pooling_size,
291
                                         step=step, input_dim=input_dim,
292
                                         ignore_border=ignore_border,
293
                                         padding=padding, **kwargs)
294
295
    def __setstate__(self, state):
296
        self.__dict__.update(state)
297
        # Fix objects created before pull request #899.
298
        self.mode = getattr(self, 'mode', 'max')
299
        self.padding = getattr(self, 'padding', (0, 0))
300
        self.ignore_border = getattr(self, 'ignore_border', False)
301
302
303
class AveragePooling(Pooling):
304
    """Average pooling layer.
305
306
    Parameters
307
    ----------
308
    include_padding : bool, optional
309
        When calculating an average, include zeros that are the
310
        result of zero padding added by the `padding` argument.
311
        A value of `True` is only accepted if `ignore_border`
312
        is also `True`. `False` by default.
313
314
    Notes
315
    -----
316
    For documentation on the remainder of the arguments to this
317
    class, see :class:`MaxPooling`.
318
319
    """
320
    @lazy(allocation=['pooling_size'])
321
    def __init__(self, pooling_size, step=None, input_dim=None,
322
                 ignore_border=True, padding=(0, 0),
323
                 include_padding=False, **kwargs):
324
        mode = 'average_inc_pad' if include_padding else 'average_exc_pad'
325
        super(AveragePooling, self).__init__(mode, pooling_size,
326
                                             step=step, input_dim=input_dim,
327
                                             ignore_border=ignore_border,
328
                                             padding=padding, **kwargs)
329
330
331
class _AllocationMixin(object):
332
    def _push_allocation_config(self):
333
        for attr in ['filter_size', 'num_filters', 'border_mode',
334
                     'batch_size', 'num_channels', 'image_size',
335
                     'tied_biases', 'use_bias']:
336
            setattr(self.convolution, attr, getattr(self, attr))
337
338
    @property
339
    def num_output_channels(self):
340
        # Assumes an elementwise activation function. Would need to
341
        # change to support e.g. maxout, but that would also require
342
        # a way of querying the activation function for this kind of
343
        # information.
344
        return self.num_filters
345
346
347
class ConvolutionalActivation(_AllocationMixin, Sequence, Initializable):
348
    """A convolution followed by an activation function.
349
350
    Parameters
351
    ----------
352
    activation : :class:`.BoundApplication`
353
        The application method to apply after convolution (i.e.
354
        the nonlinear activation function)
355
356
    See Also
357
    --------
358
    :class:`Convolutional` : For the documentation of other parameters.
359
360
    """
361
    @lazy(allocation=['filter_size', 'num_filters', 'num_channels'])
362
    def __init__(self, activation, filter_size, num_filters, num_channels,
363
                 batch_size=None, image_size=None, step=(1, 1),
364
                 border_mode='valid', tied_biases=False, **kwargs):
365
        self.convolution = Convolutional()
366
367
        self.filter_size = filter_size
368
        self.num_filters = num_filters
369
        self.num_channels = num_channels
370
        self.batch_size = batch_size
371
        self.image_size = image_size
372
        self.step = step
373
        self.border_mode = border_mode
374
        self.tied_biases = tied_biases
375
376
        super(ConvolutionalActivation, self).__init__(
377
            application_methods=[self.convolution.apply, activation],
378
            **kwargs)
379
380
    def get_dim(self, name):
381
        # TODO The name of the activation output doesn't need to be `output`
382
        return self.convolution.get_dim(name)
383
384
    def _push_allocation_config(self):
385
        super(ConvolutionalActivation, self)._push_allocation_config()
386
        self.convolution.step = self.step
387
388
389
class ConvolutionalSequence(Sequence, Initializable, Feedforward):
390
    """A sequence of convolutional operations.
391
392
    Parameters
393
    ----------
394
    layers : list
395
        List of convolutional bricks (i.e. :class:`Convolutional` or
396
        :class:`ConvolutionalActivation`).
397
    num_channels : int
398
        Number of input channels in the image. For the first layer this is
399
        normally 1 for grayscale images and 3 for color (RGB) images. For
400
        subsequent layers this is equal to the number of filters output by
401
        the previous convolutional layer.
402
    batch_size : int, optional
403
        Number of images in batch. If given, will be passed to
404
        theano's convolution operator resulting in possibly faster
405
        execution.
406
    image_size : tuple, optional
407
        Width and height of the input (image/featuremap). If given,
408
        will be passed to theano's convolution operator resulting in
409
        possibly faster execution.
410
    border_mode : 'valid', 'full' or None, optional
411
        The border mode to use, see :func:`scipy.signal.convolve2d` for
412
        details. Unlike with :class:`Convolutional`, this defaults to
413
        None, in which case no default value is pushed down to child
414
        bricks at allocation time. Child bricks will in this case
415
        need to rely on either a default border mode (usually valid)
416
        or one provided at construction and/or after construction
417
        (but before allocation).
418
419
    Notes
420
    -----
421
    The passed convolutional operators should be 'lazy' constructed, that
422
    is, without specifying the batch_size, num_channels and image_size. The
423
    main feature of :class:`ConvolutionalSequence` is that it will set the
424
    input dimensions of a layer to the output dimensions of the previous
425
    layer by the :meth:`~.Brick.push_allocation_config` method.
426
427
    The reason the `border_mode` parameter behaves the way it does is that
428
    pushing a single default `border_mode` makes it very difficult to
429
    have child bricks with different border modes. Normally, such things
430
    would be overridden after `push_allocation_config()`, but this is
431
    a particular hassle as the border mode affects the allocation
432
    parameters of every subsequent child brick in the sequence. Thus, only
433
    an explicitly specified border mode will be pushed down the hierarchy.
434
435
    """
436
    @lazy(allocation=['num_channels'])
437
    def __init__(self, layers, num_channels, batch_size=None, image_size=None,
438
                 border_mode=None, tied_biases=False, **kwargs):
439
        self.layers = layers
440
        self.image_size = image_size
441
        self.num_channels = num_channels
442
        self.batch_size = batch_size
443
        self.border_mode = border_mode
444
        self.tied_biases = tied_biases
445
446
        application_methods = [brick.apply for brick in layers]
447
        super(ConvolutionalSequence, self).__init__(
448
            application_methods=application_methods, **kwargs)
449
450
    def get_dim(self, name):
451
        if name == 'input_':
452
            return ((self.num_channels,) + self.image_size)
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after return.
Loading history...
453
        if name == 'output':
454
            return self.layers[-1].get_dim(name)
455
        return super(ConvolutionalSequence, self).get_dim(name)
456
457
    def _push_allocation_config(self):
458
        num_channels = self.num_channels
459
        image_size = self.image_size
460
        for layer in self.layers:
461
            if self.border_mode is not None:
462
                layer.border_mode = self.border_mode
463
            layer.tied_biases = self.tied_biases
464
            layer.image_size = image_size
465
            layer.num_channels = num_channels
466
            layer.batch_size = self.batch_size
467
            layer.use_bias = self.use_bias
468
469
            # Push input dimensions to children
470
            layer._push_allocation_config()
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _push_allocation_config 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...
471
472
            # Retrieve output dimensions
473
            # and set it for next layer
474
            if layer.image_size is not None:
475
                output_shape = layer.get_dim('output')
476
                image_size = output_shape[1:]
477
            num_channels = layer.num_output_channels
478
479
480
class Flattener(Brick):
481
    """Flattens the input.
482
483
    It may be used to pass multidimensional objects like images or feature
484
    maps of convolutional bricks into bricks which allow only two
485
    dimensional input (batch, features) like MLP.
486
487
    """
488
    @application(inputs=['input_'], outputs=['output'])
489
    def apply(self, input_):
490
        return input_.flatten(ndim=2)
491