Repository: keras-team/keras

Issue:
model.get_layer fails to work if called a second time after adding a new layer
model.get_layer() does not correctly return the correct layer if a new layer is added after it is first used.


To replicate (using keras 2.0.6)

from keras.models import Sequential
from keras.layers import Dense, Dropout, Conv2D, Flatten, Reshape

model = Sequential()
model.add(Dense(128, input_shape=(784,)))
model.add(Dense(64))
model.get_layer(index=-1).output_shape
model.add(Dense(32))
model.get_layer(index=-1).output_shape

----- outputs: (None, 64), rather than the expected (None, 32)




Discussion:

Resolving patch:
diff --git a/keras/models.py b/keras/models.py
--- a/keras/models.py
+++ b/keras/models.py
@@ -516,12 +516,12 @@ def get_layer(self, name=None, index=None):
         # Returns
             A layer instance.
         """
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.get_layer(name, index)
 
     def call(self, inputs, mask=None):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.call(inputs, mask)
 
@@ -560,7 +560,7 @@ def build(self, input_shape=None):
 
     @property
     def uses_learning_phase(self):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.uses_learning_phase
 
@@ -625,41 +625,41 @@ def non_trainable_weights(self):
 
     @property
     def updates(self):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.updates
 
     @property
     def state_updates(self):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.state_updates
 
     def get_updates_for(self, inputs):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.get_updates_for(inputs)
 
     @property
     def losses(self):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.losses
 
     def get_losses_for(self, inputs):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.get_losses_for(inputs)
 
     @property
     def regularizers(self):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.regularizers
 
     @property
     def constraints(self):
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.constraints
 
@@ -678,7 +678,7 @@ def get_weights(self):
                 weights.append(layer.get_weights())
             return weights
 
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.get_weights()
 
@@ -698,7 +698,7 @@ def set_weights(self, weights):
                 layer.set_weights(weights[:nb_param])
                 weights = weights[nb_param:]
 
-        if self.model is None:
+        if not self.built:
             self.build()
         self.model.set_weights(weights)
 
@@ -847,7 +847,7 @@ def fit(self, x, y, batch_size=32, epochs=10, verbose=1, callbacks=None,
         if kwargs:
             raise TypeError('Unrecognized keyword arguments: ' + str(kwargs))
 
-        if self.model is None:
+        if not self.built:
             raise RuntimeError('The model needs to be compiled '
                                'before being used.')
         return self.model.fit(x, y,
@@ -883,7 +883,7 @@ def evaluate(self, x, y, batch_size=32, verbose=1,
         # Raises
             RuntimeError: if the model was never compiled.
         """
-        if self.model is None:
+        if not self.built:
             raise RuntimeError('The model needs to be compiled '
                                'before being used.')
         return self.model.evaluate(x, y,
@@ -904,7 +904,7 @@ def predict(self, x, batch_size=32, verbose=0):
         # Returns
             A Numpy array of predictions.
         """
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.predict(x, batch_size=batch_size, verbose=verbose)
 
@@ -918,7 +918,7 @@ def predict_on_batch(self, x):
         # Returns
             A Numpy array of predictions.
         """
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.predict_on_batch(x)
 
@@ -943,7 +943,7 @@ def train_on_batch(self, x, y, class_weight=None,
         # Raises
             RuntimeError: if the model was never compiled.
         """
-        if self.model is None:
+        if not self.built:
             raise RuntimeError('The model needs to be compiled '
                                'before being used.')
         return self.model.train_on_batch(x, y,
@@ -969,7 +969,7 @@ def test_on_batch(self, x, y,
         # Raises
             RuntimeError: if the model was never compiled.
         """
-        if self.model is None:
+        if not self.built:
             raise RuntimeError('The model needs to be compiled '
                                'before being used.')
         return self.model.test_on_batch(x, y,
@@ -1100,7 +1100,7 @@ def generate_arrays_from_file(path):
                                 steps_per_epoch=1000, epochs=10)
         ```
         """
-        if self.model is None:
+        if not self.built:
             raise RuntimeError('The model needs to be compiled '
                                'before being used.')
         return self.model.fit_generator(generator,
@@ -1147,7 +1147,7 @@ def evaluate_generator(self, generator, steps,
         # Raises
             RuntimeError: if the model was never compiled.
         """
-        if self.model is None:
+        if not self.built:
             raise RuntimeError('The model needs to be compiled '
                                'before being used.')
         return self.model.evaluate_generator(generator,
@@ -1181,7 +1181,7 @@ def predict_generator(self, generator, steps,
         # Returns
             A Numpy array of predictions.
         """
-        if self.model is None:
+        if not self.built:
             self.build()
         return self.model.predict_generator(generator, steps,
                                             max_queue_size=max_queue_size,


Test patch:
diff --git a/tests/keras/test_sequential_model.py b/tests/keras/test_sequential_model.py
--- a/tests/keras/test_sequential_model.py
+++ b/tests/keras/test_sequential_model.py
@@ -256,5 +256,16 @@ def test_sequential_count_params():
     assert(n == model.count_params())
 
 
+@keras_test
+def test_rebuild_model():
+    model = Sequential()
+    model.add(Dense(128, input_shape=(784,)))
+    model.add(Dense(64))
+    assert(model.get_layer(index=-1).output_shape == (None, 64))
+
+    model.add(Dense(32))
+    assert(model.get_layer(index=-1).output_shape == (None, 32))
+
+
 if __name__ == '__main__':
     pytest.main([__file__])