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

blocks.bricks.Convolutional._initialize()   A

Complexity

Conditions 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 7
rs 9.4285
1
from theano.tensor.nnet import conv2d
2
from theano.tensor.nnet.abstract_conv import (AbstractConv2d_gradInputs,
3
                                              get_conv_output_shape)
4
from theano.tensor.signal.pool import pool_2d, Pool
5
6
from blocks.bricks import Initializable, Feedforward, Sequence, Activation
7
from blocks.bricks.base import application, Brick, lazy
8
from blocks.roles import add_role, FILTER, BIAS
9
from blocks.utils import shared_floatx_nans
10
11
12
class Convolutional(LinearLike):
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'LinearLike'
Loading history...
13
    """Performs a 2D convolution.
14
15
    Parameters
16
    ----------
17
    filter_size : tuple
18
        The height and width of the filter (also called *kernels*).
19
    num_filters : int
20
        Number of filters per channel.
21
    num_channels : int
22
        Number of input channels in the image. For the first layer this is
23
        normally 1 for grayscale images and 3 for color (RGB) images. For
24
        subsequent layers this is equal to the number of filters output by
25
        the previous convolutional layer. The filters are pooled over the
26
        channels.
27
    batch_size : int, optional
28
        Number of examples per batch. If given, this will be passed to
29
        Theano convolution operator, possibly resulting in faster
30
        execution.
31
    image_size : tuple, optional
32
        The height and width of the input (image or feature map). If given,
33
        this will be passed to the Theano convolution operator, resulting
34
        in possibly faster execution times.
35
    step : tuple, optional
36
        The step (or stride) with which to slide the filters over the
37
        image. Defaults to (1, 1).
38
    border_mode : {'valid', 'full'}, optional
39
        The border mode to use, see :func:`scipy.signal.convolve2d` for
40
        details. Defaults to 'valid'.
41
    tied_biases : bool
42
        If ``True``, it indicates that the biases of every filter in this
43
        layer should be shared amongst all applications of that filter.
44
        Setting this to ``False`` will untie the biases, yielding a
45
        separate bias for every location at which the filter is applied.
46
        Defaults to ``False``.
47
48
    """
49
    # Make it possible to override the implementation of conv2d that gets
50
    # used, i.e. to use theano.sandbox.cuda.dnn.dnn_conv directly in order
51
    # to leverage features not yet available in Theano's standard conv2d.
52
    # The function you override with here should accept at least the
53
    # input and the kernels as positionals, and the keyword arguments
54
    # input_shape, subsample, border_mode, and filter_shape. If some of
55
    # these are unsupported they should still be accepted and ignored,
56
    # e.g. with a wrapper function that swallows **kwargs.
57
    conv2d_impl = staticmethod(conv2d)
58
59
    # Used to override the output shape computation for a given value of
60
    # conv2d_impl. Should accept 4 positional arguments: the shape of an
61
    # image minibatch (with 4 elements: batch size, number of channels,
62
    # height, and width), the shape of the filter bank (number of filters,
63
    # number of output channels, filter height, filter width), the border
64
    # mode, and the step (vertical and horizontal strides). It is expected
65
    # to return a 4-tuple of (batch size, number of channels, output
66
    # height, output width). The first element of this tuple is not used
67
    # for anything by this brick.
68
    get_output_shape = staticmethod(get_conv_output_shape)
69
70
    @lazy(allocation=['filter_size', 'num_filters', 'num_channels'])
71
    def __init__(self, filter_size, num_filters, num_channels, batch_size=None,
72
                 image_size=(None, None), step=(1, 1), border_mode='valid',
73
                 tied_biases=False, **kwargs):
74
        super(Convolutional, self).__init__(**kwargs)
75
76
        self.filter_size = filter_size
77
        self.num_filters = num_filters
78
        self.batch_size = batch_size
79
        self.num_channels = num_channels
80
        self.image_size = image_size
81
        self.step = step
82
        self.border_mode = border_mode
83
        self.tied_biases = tied_biases
84
85
    def _allocate(self):
86
        W = shared_floatx_nans((self.num_filters, self.num_channels) +
87
                               self.filter_size, name='W')
88
        add_role(W, FILTER)
89
        self.parameters.append(W)
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named parameters.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
90
        self.add_auxiliary_variable(W.norm(2), name='W_norm')
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named add_auxiliary_variable.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
91
        if self.use_bias:
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named use_bias.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
92
            if self.tied_biases:
93
                b = shared_floatx_nans((self.num_filters,), name='b')
94
            else:
95
                # this error is raised here instead of during initializiation
96
                # because ConvolutionalSequence may specify the image size
97
                if self.image_size == (None, None) and not self.tied_biases:
98
                    raise ValueError('Cannot infer bias size without '
99
                                     'image_size specified. If you use '
100
                                     'variable image_size, you should use '
101
                                     'tied_biases=True.')
102
103
                b = shared_floatx_nans(self.get_dim('output'), name='b')
104
            add_role(b, BIAS)
105
106
            self.parameters.append(b)
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named parameters.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
107
            self.add_auxiliary_variable(b.norm(2), name='b_norm')
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named add_auxiliary_variable.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
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.image_size == (None, None):
132
            input_shape = None
133
        else:
134
            input_shape = (self.batch_size, self.num_channels)
135
            input_shape += self.image_size
136
137
        output = self.conv2d_impl(
138
            input_, self.W,
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named W.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
139
            input_shape=input_shape,
140
            subsample=self.step,
141
            border_mode=self.border_mode,
142
            filter_shape=((self.num_filters, self.num_channels) +
143
                          self.filter_size))
144
        if self.use_bias:
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named use_bias.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
145
            if self.tied_biases:
146
                output += self.b.dimshuffle('x', 0, 'x', 'x')
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named b.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
147
            else:
148
                output += self.b.dimshuffle('x', 0, 1, 2)
0 ignored issues
show
Bug introduced by
The Instance of Convolutional does not seem to have a member named b.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

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