Skip to content

Commit

Permalink
Update convolutional layers to make use of Theano's new conv2d() inte…
Browse files Browse the repository at this point in the history
…rface

* requirements have been updated to a suitable Theano version
* Conv2DLayer, Conv1DLayer do not pad or crop inputs manually
* flip_filters is moved to BaseConvLayer and supported by all implementations
* conv1d implementations in lasagne.theano_extensions support padding and flipping
* all convolutional tests include custom padding and flip_filters
* untie_biases is a single test case rather than added to every test case
  • Loading branch information
f0k committed Dec 28, 2015
1 parent f32e9ef commit 0ce713f
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 230 deletions.
2 changes: 1 addition & 1 deletion lasagne/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
except ImportError: # pragma: no cover
raise ImportError("Could not import Theano." + install_instr)
else:
if not hasattr(theano.tensor.nnet, 'h_softmax'): # pragma: no cover
if not hasattr(theano.tensor.nnet, 'abstract_conv'): # pragma: no cover
raise ImportError("Your Theano version is too old." + install_instr)
del install_instr
del theano
Expand Down
135 changes: 47 additions & 88 deletions lasagne/layers/conv.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class BaseConvLayer(Layer):
lasagne.layers.BaseConvLayer(incoming, num_filters, filter_size,
stride=1, pad=0, untie_biases=False,
W=lasagne.init.GlorotUniform(), b=lasagne.init.Constant(0.),
nonlinearity=lasagne.nonlinearities.rectify,
nonlinearity=lasagne.nonlinearities.rectify, flip_filters=True,
n=None, **kwargs)
Convolutional layer base class
Expand Down Expand Up @@ -163,6 +163,13 @@ class BaseConvLayer(Layer):
The nonlinearity that is applied to the layer activations. If None
is provided, the layer will be linear.
flip_filters : bool (default: True)
Whether to flip the filters before sliding them over the input,
performing a convolution (this is the default), or not to flip them and
perform a correlation. Note that for some other convolutional layers in
Lasagne, flipping incurs an overhead and is disabled by default --
check the documentation when using learned weights from another layer.
n : int or None
The dimensionality of the convolution (i.e., the number of spatial
dimensions of each feature map and each convolutional filter). If
Expand All @@ -182,7 +189,7 @@ class BaseConvLayer(Layer):
def __init__(self, incoming, num_filters, filter_size, stride=1, pad=0,
untie_biases=False,
W=init.GlorotUniform(), b=init.Constant(0.),
nonlinearity=nonlinearities.rectify,
nonlinearity=nonlinearities.rectify, flip_filters=True,
n=None, **kwargs):
super(BaseConvLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
Expand All @@ -200,6 +207,7 @@ def __init__(self, incoming, num_filters, filter_size, stride=1, pad=0,
self.n = n
self.num_filters = num_filters
self.filter_size = as_tuple(filter_size, n, int)
self.flip_filters = flip_filters
self.stride = as_tuple(stride, n, int)
self.untie_biases = untie_biases

Expand Down Expand Up @@ -285,7 +293,8 @@ class Conv1DLayer(BaseConvLayer):
lasagne.layers.Conv1DLayer(incoming, num_filters, filter_size, stride=1,
pad=0, untie_biases=False, W=lasagne.init.GlorotUniform(),
b=lasagne.init.Constant(0.), nonlinearity=lasagne.nonlinearities.rectify,
convolution=lasagne.theano_extensions.conv.conv1d_mc0, **kwargs)
flip_filters=True, convolution=lasagne.theano_extensions.conv.conv1d_mc0,
**kwargs)
1D convolutional layer
Expand Down Expand Up @@ -356,12 +365,20 @@ class Conv1DLayer(BaseConvLayer):
The nonlinearity that is applied to the layer activations. If None
is provided, the layer will be linear.
flip_filters : bool (default: True)
Whether to flip the filters before sliding them over the input,
performing a convolution (this is the default), or not to flip them and
perform a correlation. Note that for some other convolutional layers in
Lasagne, flipping incurs an overhead and is disabled by default --
check the documentation when using learned weights from another layer.
convolution : callable
The convolution implementation to use. The
`lasagne.theano_extensions.conv` module provides some alternative
implementations for 1D convolutions, because the Theano API only
features a 2D convolution implementation. Usually it should be fine
to leave this at the default value.
to leave this at the default value. Note that not all implementations
support all settings for `pad` and `subsample`.
**kwargs
Any additional keyword arguments are passed to the `Layer` superclass.
Expand All @@ -373,54 +390,25 @@ class Conv1DLayer(BaseConvLayer):
b : Theano shared variable or expression
Variable or expression representing the biases.
Notes
-----
Theano's underlying convolution (:func:`theano.tensor.nnet.conv.conv2d`)
only supports ``pad=0`` and ``pad='full'``. This layer emulates other modes
by cropping a full convolution or explicitly padding the input with zeros.
"""
def __init__(self, incoming, num_filters, filter_size, stride=1,
pad=0, untie_biases=False,
W=init.GlorotUniform(), b=init.Constant(0.),
nonlinearity=nonlinearities.rectify,
nonlinearity=nonlinearities.rectify, flip_filters=True,
convolution=conv.conv1d_mc0, **kwargs):
super(Conv1DLayer, self).__init__(incoming, num_filters, filter_size,
stride, pad, untie_biases, W, b,
nonlinearity, n=1, **kwargs)
nonlinearity, flip_filters, n=1,
**kwargs)
self.convolution = convolution

def convolve(self, input, **kwargs):
if self.stride == (1,) and self.pad == 'same':
# simulate same convolution by cropping a full convolution
conved = self.convolution(input, self.W, subsample=self.stride,
image_shape=self.input_shape,
filter_shape=self.get_W_shape(),
border_mode='full')
crop = self.filter_size[0] // 2
conved = conved[:, :, crop:-crop or None]
else:
# no padding needed, or explicit padding of input needed
if self.pad == 'full':
border_mode = 'full'
pad = (0, 0)
elif self.pad == 'same':
border_mode = 'valid'
pad = self.filter_size[0] // 2, (self.filter_size[0] - 1) // 2
else:
border_mode = 'valid'
pad = (self.pad[0], self.pad[0])
if pad != (0, 0):
input = padding.pad(input, [pad], batch_ndim=2)
input_shape = (self.input_shape[0], self.input_shape[1],
None if self.input_shape[2] is None else
self.input_shape[2] + pad[0] + pad[1])
else:
input_shape = self.input_shape
conved = self.convolution(input, self.W, subsample=self.stride,
image_shape=input_shape,
filter_shape=self.get_W_shape(),
border_mode=border_mode)
border_mode = 'half' if self.pad == 'same' else self.pad
conved = self.convolution(input, self.W,
self.input_shape, self.get_W_shape(),
subsample=self.stride,
border_mode=border_mode,
filter_flip=self.flip_filters)
return conved


Expand All @@ -429,7 +417,7 @@ class Conv2DLayer(BaseConvLayer):
lasagne.layers.Conv2DLayer(incoming, num_filters, filter_size,
stride=(1, 1), pad=0, untie_biases=False,
W=lasagne.init.GlorotUniform(), b=lasagne.init.Constant(0.),
nonlinearity=lasagne.nonlinearities.rectify,
nonlinearity=lasagne.nonlinearities.rectify, flip_filters=True,
convolution=theano.tensor.nnet.conv2d, **kwargs)
2D convolutional layer
Expand Down Expand Up @@ -505,6 +493,13 @@ class Conv2DLayer(BaseConvLayer):
The nonlinearity that is applied to the layer activations. If None
is provided, the layer will be linear.
flip_filters : bool (default: True)
Whether to flip the filters before sliding them over the input,
performing a convolution (this is the default), or not to flip them and
perform a correlation. Note that for some other convolutional layers in
Lasagne, flipping incurs an overhead and is disabled by default --
check the documentation when using learned weights from another layer.
convolution : callable
The convolution implementation to use. Usually it should be fine to
leave this at the default value.
Expand All @@ -519,61 +514,25 @@ class Conv2DLayer(BaseConvLayer):
b : Theano shared variable or expression
Variable or expression representing the biases.
Notes
-----
Theano's underlying convolution (:func:`theano.tensor.nnet.conv.conv2d`)
only supports ``pad=0`` and ``pad='full'``. This layer emulates other modes
by cropping a full convolution or explicitly padding the input with zeros.
"""
def __init__(self, incoming, num_filters, filter_size, stride=(1, 1),
pad=0, untie_biases=False,
W=init.GlorotUniform(), b=init.Constant(0.),
nonlinearity=nonlinearities.rectify,
nonlinearity=nonlinearities.rectify, flip_filters=True,
convolution=T.nnet.conv2d, **kwargs):
super(Conv2DLayer, self).__init__(incoming, num_filters, filter_size,
stride, pad, untie_biases, W, b,
nonlinearity, n=2, **kwargs)
nonlinearity, flip_filters, n=2,
**kwargs)
self.convolution = convolution

def convolve(self, input, **kwargs):
if self.stride == (1, 1) and self.pad == 'same':
# simulate same convolution by cropping a full convolution
conved = self.convolution(input, self.W, subsample=self.stride,
image_shape=self.input_shape,
filter_shape=self.get_W_shape(),
border_mode='full')
crop_x = self.filter_size[0] // 2
crop_y = self.filter_size[1] // 2
conved = conved[:, :, crop_x:-crop_x or None,
crop_y:-crop_y or None]
else:
# no padding needed, or explicit padding of input needed
if self.pad == 'full':
border_mode = 'full'
pad = [(0, 0), (0, 0)]
elif self.pad == 'same':
border_mode = 'valid'
pad = [(self.filter_size[0] // 2,
self.filter_size[0] // 2),
(self.filter_size[1] // 2,
self.filter_size[1] // 2)]
else:
border_mode = 'valid'
pad = [(self.pad[0], self.pad[0]), (self.pad[1], self.pad[1])]
if pad != [(0, 0), (0, 0)]:
input = padding.pad(input, pad, batch_ndim=2)
input_shape = (self.input_shape[0], self.input_shape[1],
None if self.input_shape[2] is None else
self.input_shape[2] + pad[0][0] + pad[0][1],
None if self.input_shape[3] is None else
self.input_shape[3] + pad[1][0] + pad[1][1])
else:
input_shape = self.input_shape
conved = self.convolution(input, self.W, subsample=self.stride,
image_shape=input_shape,
filter_shape=self.get_W_shape(),
border_mode=border_mode)
border_mode = 'half' if self.pad == 'same' else self.pad
conved = self.convolution(input, self.W,
self.input_shape, self.get_W_shape(),
subsample=self.stride,
border_mode=border_mode,
filter_flip=self.flip_filters)
return conved

# TODO: add Conv3DLayer
10 changes: 2 additions & 8 deletions lasagne/layers/corrmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,21 +121,15 @@ class Conv2DMMLayer(BaseConvLayer):
b : Theano shared variable
Variable representing the biases.
Notes
-----
Unlike :class:`lasagne.layers.Conv2DLayer`, this layer properly supports
``pad='same'``. It is not emulated. This should result in better
performance.
"""
def __init__(self, incoming, num_filters, filter_size, stride=(1, 1),
pad=0, untie_biases=False, W=init.GlorotUniform(),
b=init.Constant(0.), nonlinearity=nonlinearities.rectify,
flip_filters=False, **kwargs):
super(Conv2DMMLayer, self).__init__(incoming, num_filters, filter_size,
stride, pad, untie_biases, W, b,
nonlinearity, n=2, **kwargs)
self.flip_filters = flip_filters
nonlinearity, flip_filters, n=2,
**kwargs)
border_mode = 'half' if self.pad == 'same' else self.pad
self.corr_mm_op = GpuCorrMM(subsample=self.stride,
border_mode=border_mode)
Expand Down
8 changes: 2 additions & 6 deletions lasagne/layers/cuda_convnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,6 @@ class Conv2DCCLayer(BaseConvLayer):
Notes
-----
Unlike :class:`lasagne.layers.Conv2DLayer`, this layer properly supports
``pad='same'``. It is not emulated. This should result in better
performance.
The cuda-convnet convolution implementation has several limitations:
* only square filters are supported.
Expand Down Expand Up @@ -211,8 +207,8 @@ def __init__(self, incoming, num_filters, filter_size, stride=(1, 1),

super(Conv2DCCLayer, self).__init__(incoming, num_filters, filter_size,
stride, pad, untie_biases, W, b,
nonlinearity, n=2, **kwargs)
self.flip_filters = flip_filters
nonlinearity, flip_filters, n=2,
**kwargs)
self.partial_sum = partial_sum

if self.filter_size[0] != self.filter_size[1]:
Expand Down
12 changes: 2 additions & 10 deletions lasagne/layers/dnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,6 @@ class Conv2DDNNLayer(BaseConvLayer):
b : Theano shared variable or expression
Variable or expression representing the biases.
Notes
-----
Unlike :class:`lasagne.layers.Conv2DLayer`, this layer properly supports
``pad='same'``. It is not emulated. This should result in better
performance.
"""
def __init__(self, incoming, num_filters, filter_size, stride=(1, 1),
pad=0, untie_biases=False, W=init.GlorotUniform(),
Expand All @@ -239,8 +233,7 @@ def __init__(self, incoming, num_filters, filter_size, stride=(1, 1),
super(Conv2DDNNLayer, self).__init__(incoming, num_filters,
filter_size, stride, pad,
untie_biases, W, b, nonlinearity,
n=2, **kwargs)
self.flip_filters = flip_filters
flip_filters, n=2, **kwargs)

def convolve(self, input, **kwargs):
# by default we assume 'cross', consistent with corrmm.
Expand Down Expand Up @@ -365,8 +358,7 @@ def __init__(self, incoming, num_filters, filter_size, stride=(1, 1, 1),
super(Conv3DDNNLayer, self).__init__(incoming, num_filters,
filter_size, stride, pad,
untie_biases, W, b, nonlinearity,
n=3, **kwargs)
self.flip_filters = flip_filters
flip_filters, n=3, **kwargs)

def convolve(self, input, **kwargs):
# by default we assume 'cross', consistent with corrmm.
Expand Down
Loading

0 comments on commit 0ce713f

Please sign in to comment.