Repository: lisa-lab/pylearn2

Issue:
CompositeLayer fails if number of inputs is larger than number of layers
In my MLP model I have a composite space with two components as input:

```
input_space: !obj:pylearn2.space.CompositeSpace {
  components: [
    !obj:pylearn2.space.VectorSpace {
      dim: 50, 
    },
    !obj:pylearn2.space.IndexSpace {
      dim: 1, 
      max_labels: 100
    }
  ]
},
input_source: ['features', 'additional_stuff'']
```

In my model, I want to discard the second component, so I use a CompositeLayer with only one layer, and specify that input 0 is routed to layer 0 and input 1 to nothing:

```
!obj:pylearn2.models.mlp.CompositeLayer {
      layer_name: 'composite_layer',
      inputs_to_layers: {0: [0], 1: []},
      layers: [
        !obj:pylearn2.models.mlp.RectifiedLinear {
                       layer_name: 'h0',
                       dim: 300,
                       sparse_init: 15,
                       max_col_norm: 5.0,
        },
      ],
    },
```

This causes an assert at https://github.com/lisa-lab/pylearn2/blob/master/pylearn2/models/mlp.py#L4071 to fail. The assertion is invalid, as it checks, whether the ID of an _input_ is less than the number of _layers_. 

I think the number of actual inputs is not known at that point, so I would recommend to just remove that line (works according to my experiments).



Discussion:
I think a different check should happen in CompositeLayer.set_input_space(). Here the model can actually confirm that all of the keys are smaller than the number of components in the input space.

I agree that it doesn't make sense to assert that the value of the key is less than the number of layers. Your example is one instance of this and one could also build a network where a bunch of inputs go into a nested CompositeLayer and again the number of keys could be larger that the number of layers.


Resolving patch:
diff --git a/pylearn2/models/mlp.py b/pylearn2/models/mlp.py
--- a/pylearn2/models/mlp.py
+++ b/pylearn2/models/mlp.py
@@ -4135,7 +4135,6 @@ def __init__(self, layer_name, layers, inputs_to_layers=None):
             self.inputs_to_layers = OrderedDict()
             for key in sorted(inputs_to_layers):
                 assert isinstance(key, py_integer_types)
-                assert 0 <= key < self.num_layers
                 value = inputs_to_layers[key]
                 assert is_iterable(value)
                 assert all(isinstance(v, py_integer_types) for v in value)


Test patch:
diff --git a/pylearn2/models/tests/test_mlp.py b/pylearn2/models/tests/test_mlp.py
--- a/pylearn2/models/tests/test_mlp.py
+++ b/pylearn2/models/tests/test_mlp.py
@@ -329,6 +329,42 @@ def test_multiple_inputs():
     train.main_loop()
 
 
+def test_input_discard():
+    """
+    Create a VectorSpacesDataset with two inputs (features0 and features1)
+    and train an MLP which discards one input with a CompositeLayer.
+    """
+    mlp = MLP(
+        layers=[
+            FlattenerLayer(
+                CompositeLayer(
+                    'composite',
+                    [Linear(10, 'h0', 0.1)],
+                    {
+                        0: [0],
+                        1: []
+                    }
+                )
+            ),
+            Softmax(5, 'softmax', 0.1)
+        ],
+        input_space=CompositeSpace([VectorSpace(15), VectorSpace(20)]),
+        input_source=('features0', 'features1')
+    )
+    dataset = VectorSpacesDataset(
+        (np.random.rand(20, 20).astype(theano.config.floatX),
+         np.random.rand(20, 15).astype(theano.config.floatX),
+         np.random.rand(20, 5).astype(theano.config.floatX)),
+        (CompositeSpace([
+            VectorSpace(20),
+            VectorSpace(15),
+            VectorSpace(5)]),
+         ('features1', 'features0', 'targets')))
+    train = Train(dataset, mlp, SGD(0.1, batch_size=5))
+    train.algorithm.termination_criterion = EpochCounter(1)
+    train.main_loop()
+
+
 def test_input_and_target_source():
     """
     Create a MLP and test input_source and target_source