Repository: keras-team/keras

Issue:
Bidirectional LSTM freeze(trainable=False) need to be checked.
I need to freeze lstm layer(using Bidirectional wrapper).
So I freeze layer using 
`singleModel.layers[x].trainable = False`
But it seems to not be frozen.

After checking layer(by seeing # of Trainable params), I find that forward_layer,backward_layer flag need to be set. like below
```python
from keras.layers import Dense, Activation,LSTM,Input,Bidirectional,Dropout,CuDNNLSTM
for x in range(len(classificationWithString.layers)-2):
    if classificationWithString.layers[x].__class__==Bidirectional:
        classificationWithString.layers[x].forward_layer.trainable = False        
        classificationWithString.layers[x].backward_layer.trainable = False            
    classificationWithString.layers[x].trainable = False
```
I wonder is it intended? 
If not, I think that if layers.trainable=False, it needs to be freeze forward_layer,backward_layer.

Thanks




Discussion:
I have get the same problem, when I use a pre-train model, `    for layer in pretrain_model.layers:
        layer.trainable = False`, the bidirectional wrapper layer also can be training.
same issue. Even I set 

`model.get_layer("blstm").trainable = False
model.get_layer("blstm").layer.trainable = False
model.get_layer("blstm").forward_layer.trainable = False
model.get_layer("blstm").backward_layer.trainable = False`. 

The Bidirectional layer still trainable.
Looking into it, thanks for the report.

Resolving patch:
diff --git a/keras/layers/wrappers.py b/keras/layers/wrappers.py
--- a/keras/layers/wrappers.py
+++ b/keras/layers/wrappers.py
@@ -42,6 +42,14 @@ def activity_regularizer(self):
         else:
             return None
 
+    @property
+    def trainable(self):
+        return self.layer.trainable
+
+    @trainable.setter
+    def trainable(self, value):
+        self.layer.trainable = value
+
     @property
     def trainable_weights(self):
         return self.layer.trainable_weights
@@ -246,7 +254,6 @@ class Bidirectional(Wrapper):
     """
 
     def __init__(self, layer, merge_mode='concat', weights=None, **kwargs):
-        super(Bidirectional, self).__init__(layer, **kwargs)
         if merge_mode not in ['sum', 'mul', 'ave', 'concat', None]:
             raise ValueError('Invalid merge mode. '
                              'Merge mode should be one of '
@@ -266,6 +273,18 @@ def __init__(self, layer, merge_mode='concat', weights=None, **kwargs):
         self.return_sequences = layer.return_sequences
         self.return_state = layer.return_state
         self.supports_masking = True
+        self._trainable = True
+        super(Bidirectional, self).__init__(layer, **kwargs)
+
+    @property
+    def trainable(self):
+        return self._trainable
+
+    @trainable.setter
+    def trainable(self, value):
+        self._trainable = value
+        self.forward_layer.trainable = value
+        self.backward_layer.trainable = value
 
     def get_weights(self):
         return self.forward_layer.get_weights() + self.backward_layer.get_weights()


Test patch:
diff --git a/tests/keras/layers/wrappers_test.py b/tests/keras/layers/wrappers_test.py
--- a/tests/keras/layers/wrappers_test.py
+++ b/tests/keras/layers/wrappers_test.py
@@ -137,6 +137,22 @@ def test_TimeDistributed_learning_phase():
     assert_allclose(np.mean(y), 0., atol=1e-1, rtol=1e-1)
 
 
+@keras_test
+def test_TimeDistributed_trainable():
+    # test layers that need learning_phase to be set
+    x = Input(shape=(3, 2))
+    layer = wrappers.TimeDistributed(layers.BatchNormalization())
+    _ = layer(x)
+    assert len(layer.updates) == 2
+    assert len(layer.trainable_weights) == 2
+    layer.trainable = False
+    assert len(layer.updates) == 0
+    assert len(layer.trainable_weights) == 0
+    layer.trainable = True
+    assert len(layer.updates) == 2
+    assert len(layer.trainable_weights) == 2
+
+
 @keras_test
 def test_regularizers():
     model = Sequential()
@@ -325,5 +341,18 @@ def test_Bidirectional_state_reuse():
     outputs = model.predict(inputs)
 
 
+@keras_test
+def test_Bidirectional_trainable():
+    # test layers that need learning_phase to be set
+    x = Input(shape=(3, 2))
+    layer = wrappers.Bidirectional(layers.SimpleRNN(3))
+    _ = layer(x)
+    assert len(layer.trainable_weights) == 6
+    layer.trainable = False
+    assert len(layer.trainable_weights) == 0
+    layer.trainable = True
+    assert len(layer.trainable_weights) == 6
+
+
 if __name__ == '__main__':
     pytest.main([__file__])