Skip to content

Commit

Permalink
Merge pull request #52 from f0k/input-shape
Browse files Browse the repository at this point in the history
Allow Layers to be constructed from input shapes
  • Loading branch information
benanne committed Jan 30, 2015
2 parents 193e404 + 8a5a8f9 commit 720be55
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 80 deletions.
58 changes: 46 additions & 12 deletions lasagne/layers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,26 @@ class Layer(object):
The :class:`Layer` class represents a single layer of a neural network.
It should be subclassed when implementing new types of layers.
Because each layer keeps track of the layer(s) feeding into it, a
network's output :class:`Layer` instance doubles as a handle to the full
network.
Because each layer can keep track of the layer(s) feeding into it, a
network's output :class:`Layer` instance can double as a handle to the
full network.
"""
def __init__(self, input_layer, name=None):
self.input_layer = input_layer
def __init__(self, incoming, name=None):
"""
Instantiates the layer.
:parameters:
- incoming : a :class:`Layer` instance or a tuple
the layer feeding into this layer, or the expected input shape
- name : a string or None
an optional name to attach to this layer
"""
if isinstance(incoming, tuple):
self.input_shape = incoming
self.input_layer = None
else:
self.input_shape = incoming.get_output_shape()
self.input_layer = incoming
self.name = name

def get_params(self):
Expand Down Expand Up @@ -74,10 +88,9 @@ def get_output_shape(self):
:note:
When implementing a new :class:`Layer` class, you will usually
keep this unchanged and just override `get_output_shape_for()`.
keep this unchanged and just override `get_output_shape_for()`.
"""
input_shape = self.input_layer.get_output_shape()
return self.get_output_shape_for(input_shape)
return self.get_output_shape_for(self.input_shape)

def get_output(self, input=None, *args, **kwargs):
"""
Expand Down Expand Up @@ -108,6 +121,10 @@ def get_output(self, input=None, *args, **kwargs):
if isinstance(input, dict) and (self in input):
# this layer is mapped to an expression or numpy array
return utils.as_theano_expression(input[self])
elif self.input_layer is None:
raise RuntimeError("get_output() called on a free-floating layer; "
"there isn't anything to get its input from. "
"Did you mean get_output_for()?")
else: # in all other cases, just pass the input on to the next layer.
layer_input = self.input_layer.get_output(input, *args, **kwargs)
return self.get_output_for(layer_input, *args, **kwargs)
Expand Down Expand Up @@ -229,18 +246,35 @@ class MultipleInputsLayer(Layer):
It should be subclassed when implementing new types of layers that
obtain their input from multiple layers.
"""
def __init__(self, input_layers, name=None):
self.input_layers = input_layers
def __init__(self, incomings, name=None):
"""
Instantiates the layer.
:parameters:
- incomings : a list of :class:`Layer` instances or tuples
the layers feeding into this layer, or expected input shapes
- name : a string or None
an optional name to attach to this layer
"""
self.input_shapes = [incoming if isinstance(incoming, tuple)
else incoming.get_output_shape()
for incoming in incomings]
self.input_layers = [None if isinstance(incoming, tuple)
else incoming
for incoming in incomings]
self.name = name

def get_output_shape(self):
input_shapes = [input_layer.get_output_shape() for input_layer in self.input_layers]
return self.get_output_shape_for(input_shapes)
return self.get_output_shape_for(self.input_shapes)

def get_output(self, input=None, *args, **kwargs):
if isinstance(input, dict) and (self in input):
# this layer is mapped to an expression or numpy array
return utils.as_theano_expression(input[self])
elif any(input_layer is None for input_layer in self.input_layers):
raise RuntimeError("get_output() called on a free-floating layer; "
"there isn't anything to get its inputs from. "
"Did you mean get_output_for()?")
else: # in all other cases, just pass the network input on to the next layers.
layer_inputs = [input_layer.get_output(input, *args, **kwargs) for input_layer in self.input_layers]
return self.get_output_for(layer_inputs, *args, **kwargs)
Expand Down
20 changes: 10 additions & 10 deletions lasagne/layers/conv.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@


class Conv1DLayer(Layer):
def __init__(self, input_layer, num_filters, filter_length, stride=1, border_mode="valid", untie_biases=False,
def __init__(self, incoming, num_filters, filter_length, stride=1, border_mode="valid", untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify,
convolution=conv.conv1d_mc0, **kwargs):
super(Conv1DLayer, self).__init__(input_layer, **kwargs)
super(Conv1DLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand All @@ -42,7 +42,7 @@ def __init__(self, input_layer, num_filters, filter_length, stride=1, border_mod
self.b = self.create_param(b, (num_filters,), name="b")

def get_W_shape(self):
num_input_channels = self.input_layer.get_output_shape()[1]
num_input_channels = self.input_shape[1]
return (self.num_filters, num_input_channels, self.filter_length)

def get_params(self):
Expand All @@ -65,9 +65,9 @@ def get_output_shape_for(self, input_shape):

def get_output_for(self, input, input_shape=None, *args, **kwargs):
# the optional input_shape argument is for when get_output_for is called
# directly with a different shape than the output_shape of self.input_layer.
# directly with a different shape than self.input_shape.
if input_shape is None:
input_shape = self.input_layer.get_output_shape()
input_shape = self.input_shape

filter_shape = self.get_W_shape()

Expand All @@ -93,10 +93,10 @@ def get_output_for(self, input, input_shape=None, *args, **kwargs):


class Conv2DLayer(Layer):
def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border_mode="valid", untie_biases=False,
def __init__(self, incoming, num_filters, filter_size, strides=(1, 1), border_mode="valid", untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify,
convolution=T.nnet.conv2d, **kwargs):
super(Conv2DLayer, self).__init__(input_layer, **kwargs)
super(Conv2DLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand All @@ -119,7 +119,7 @@ def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border
self.b = self.create_param(b, (num_filters,), name="b")

def get_W_shape(self):
num_input_channels = self.input_layer.get_output_shape()[1]
num_input_channels = self.input_shape[1]
return (self.num_filters, num_input_channels, self.filter_size[0], self.filter_size[1])

def get_params(self):
Expand All @@ -145,9 +145,9 @@ def get_output_shape_for(self, input_shape):

def get_output_for(self, input, input_shape=None, *args, **kwargs):
# the optional input_shape argument is for when get_output_for is called
# directly with a different shape than the output_shape of self.input_layer.
# directly with a different shape than self.input_shape.
if input_shape is None:
input_shape = self.input_layer.get_output_shape()
input_shape = self.input_shape

filter_shape = self.get_W_shape()

Expand Down
6 changes: 3 additions & 3 deletions lasagne/layers/corrmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class MMLayer(Layer):


class Conv2DMMLayer(MMLayer):
def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border_mode=None, untie_biases=False,
def __init__(self, incoming, num_filters, filter_size, strides=(1, 1), border_mode=None, untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify, pad=None,
flip_filters=False, **kwargs):
super(Conv2DMMLayer, self).__init__(input_layer, **kwargs)
super(Conv2DMMLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand Down Expand Up @@ -76,7 +76,7 @@ def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border
self.corr_mm_op = GpuCorrMM(subsample=self.strides, pad=self.pad)

def get_W_shape(self):
num_input_channels = self.input_layer.get_output_shape()[1]
num_input_channels = self.input_shape[1]
return (self.num_filters, num_input_channels, self.filter_size[0], self.filter_size[1])

def get_params(self):
Expand Down
19 changes: 9 additions & 10 deletions lasagne/layers/cuda_convnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ class CCLayer(Layer):


class Conv2DCCLayer(CCLayer):
def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border_mode=None, untie_biases=False,
def __init__(self, incoming, num_filters, filter_size, strides=(1, 1), border_mode=None, untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify, pad=None,
dimshuffle=True, flip_filters=False, partial_sum=1, **kwargs):
super(Conv2DCCLayer, self).__init__(input_layer, **kwargs)
super(Conv2DCCLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand Down Expand Up @@ -97,10 +97,10 @@ def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border

def get_W_shape(self):
if self.dimshuffle:
num_input_channels = self.input_layer.get_output_shape()[1]
num_input_channels = self.input_shape[1]
return (self.num_filters, num_input_channels, self.filter_size, self.filter_size)
else:
num_input_channels = self.input_layer.get_output_shape()[0]
num_input_channels = self.input_shape[0]
return (num_input_channels, self.filter_size, self.filter_size, self.num_filters)

def get_params(self):
Expand Down Expand Up @@ -155,10 +155,10 @@ def get_output_for(self, input, *args, **kwargs):


class MaxPool2DCCLayer(CCLayer):
def __init__(self, input_layer, ds, ignore_border=False, strides=None, dimshuffle=True, **kwargs):
def __init__(self, incoming, ds, ignore_border=False, strides=None, dimshuffle=True, **kwargs):
from pylearn2.sandbox.cuda_convnet.pool import MaxPool

super(MaxPool2DCCLayer, self).__init__(input_layer, **kwargs)
super(MaxPool2DCCLayer, self).__init__(incoming, **kwargs)
if ds[0] != ds[1]:
raise RuntimeError("MaxPool2DCCLayer only supports square pooling regions, but ds=(%d, %d)" % ds)

Expand Down Expand Up @@ -252,10 +252,10 @@ class NINLayer_c01b(Layer):
axis arrangement instead of bc01. This reduces the number of shuffles
and reshapes required and might be faster as a result.
"""
def __init__(self, input_layer, num_units, untie_biases=False,
def __init__(self, incoming, num_units, untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify,
**kwargs):
super(NINLayer_c01b, self).__init__(input_layer, **kwargs)
super(NINLayer_c01b, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand All @@ -264,8 +264,7 @@ def __init__(self, input_layer, num_units, untie_biases=False,
self.num_units = num_units
self.untie_biases = untie_biases

output_shape = self.input_layer.get_output_shape()
num_input_channels = output_shape[0]
num_input_channels = self.input_shape[0]

self.W = self.create_param(W, (num_units, num_input_channels))
if b is None:
Expand Down
14 changes: 6 additions & 8 deletions lasagne/layers/dense.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,17 @@ class DenseLayer(Layer):
>>> l_in = InputLayer((100, 20))
>>> l1 = DenseLayer(l_in, num_units=50)
"""
def __init__(self, input_layer, num_units, W=init.Uniform(), b=init.Constant(0.),
def __init__(self, incoming, num_units, W=init.Uniform(), b=init.Constant(0.),
nonlinearity=nonlinearities.rectify, **kwargs):
super(DenseLayer, self).__init__(input_layer, **kwargs)
super(DenseLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
self.nonlinearity = nonlinearity

self.num_units = num_units

output_shape = self.input_layer.get_output_shape()
num_inputs = int(np.prod(output_shape[1:]))
num_inputs = int(np.prod(self.input_shape[1:]))

self.W = self.create_param(W, (num_inputs, num_units), name="W")
self.b = self.create_param(b, (num_units,), name="b") if b is not None else None
Expand Down Expand Up @@ -97,10 +96,10 @@ class NINLayer(Layer):
Any number of trailing dimensions is supported, so NINLayer can be used to implement
1D, 2D, 3D, ... convolutions.
"""
def __init__(self, input_layer, num_units, untie_biases=False,
def __init__(self, incoming, num_units, untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify,
**kwargs):
super(NINLayer, self).__init__(input_layer, **kwargs)
super(NINLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand All @@ -109,8 +108,7 @@ def __init__(self, input_layer, num_units, untie_biases=False,
self.num_units = num_units
self.untie_biases = untie_biases

output_shape = self.input_layer.get_output_shape()
num_input_channels = output_shape[1]
num_input_channels = self.input_shape[1]

self.W = self.create_param(W, (num_input_channels, num_units), name="W")
if b is None:
Expand Down
14 changes: 7 additions & 7 deletions lasagne/layers/dnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class DNNLayer(Layer):


class Pool2DDNNLayer(DNNLayer):
def __init__(self, input_layer, ds, strides=None, mode='max', **kwargs):
super(Pool2DDNNLayer, self).__init__(input_layer, **kwargs)
def __init__(self, incoming, ds, strides=None, mode='max', **kwargs):
super(Pool2DDNNLayer, self).__init__(incoming, **kwargs)
self.ds = ds # a tuple
self.mode = mode
self.strides = strides if strides is not None else ds
Expand All @@ -42,14 +42,14 @@ def get_output_for(self, input, *args, **kwargs):


class MaxPool2DDNNLayer(Pool2DDNNLayer): # for consistency
def __init__(self, input_layer, ds, strides=None, **kwargs):
super(MaxPool2DDNNLayer, self).__init__(input_layer, ds, strides, mode='max', **kwargs)
def __init__(self, incoming, ds, strides=None, **kwargs):
super(MaxPool2DDNNLayer, self).__init__(incoming, ds, strides, mode='max', **kwargs)

class Conv2DDNNLayer(DNNLayer):
def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border_mode=None, untie_biases=False,
def __init__(self, incoming, num_filters, filter_size, strides=(1, 1), border_mode=None, untie_biases=False,
W=init.Uniform(), b=init.Constant(0.), nonlinearity=nonlinearities.rectify, pad=None,
flip_filters=False, **kwargs):
super(Conv2DDNNLayer, self).__init__(input_layer, **kwargs)
super(Conv2DDNNLayer, self).__init__(incoming, **kwargs)
if nonlinearity is None:
self.nonlinearity = nonlinearities.identity
else:
Expand Down Expand Up @@ -98,7 +98,7 @@ def __init__(self, input_layer, num_filters, filter_size, strides=(1, 1), border
self.b = self.create_param(b, (num_filters,), name="b")

def get_W_shape(self):
num_input_channels = self.input_layer.get_output_shape()[1]
num_input_channels = self.input_shape[1]
return (self.num_filters, num_input_channels, self.filter_size[0], self.filter_size[1])

def get_params(self):
Expand Down
21 changes: 11 additions & 10 deletions lasagne/layers/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@


class ConcatLayer(MultipleInputsLayer):
def __init__(self, input_layers, axis=1, **kwargs):
super(ConcatLayer, self).__init__(input_layers, **kwargs)
def __init__(self, incomings, axis=1, **kwargs):
super(ConcatLayer, self).__init__(incomings, **kwargs)
self.axis = axis

def get_output_shape_for(self, input_shapes):
Expand Down Expand Up @@ -45,25 +45,26 @@ class ElemwiseSumLayer(MultipleInputsLayer):
the copy operations in concatenation, but splits up the dot product.)
"""

def __init__(self, input_layers, coeffs=1, **kwargs):
def __init__(self, incomings, coeffs=1, **kwargs):
"""
Creates a layer perfoming an elementwise sum of its input layers.
:parameters:
- input_layers: list
A list of :class:`Layer` instances of same output shape to sum
- incomings : a list of :class:`Layer` instances or tuples
the layers feeding into this layer, or expected input shapes,
with all incoming shapes being equal
- coeffs: list or scalar
A same-sized list of coefficients, or a single coefficient that
is to be applied to all instances. By default, these will not
be included in the learnable parameters of this layer.
"""
super(ElemwiseSumLayer, self).__init__(input_layers, **kwargs)
super(ElemwiseSumLayer, self).__init__(incomings, **kwargs)
if isinstance(coeffs, list):
if len(coeffs) != len(input_layers):
raise ValueError("Mismatch: got %d coeffs for %d input_layers" %
(len(coeffs), len(input_layers)))
if len(coeffs) != len(incomings):
raise ValueError("Mismatch: got %d coeffs for %d incomings" %
(len(coeffs), len(incomings)))
else:
coeffs = [coeffs] * len(input_layers)
coeffs = [coeffs] * len(incomings)
self.coeffs = coeffs

def get_output_shape_for(self, input_shapes):
Expand Down
Loading

0 comments on commit 720be55

Please sign in to comment.