Repository: keras-team/keras

Issue:
Error "Can only concatenate list (not "tuple") to list" when passing Embedding layer to Bidirectional LSTM layer in Keras 3
**TensorFlow version**: 2.16.1
**Keras version**: 3.1.0

I was playing with encoder-decoder architectures and encountered the "_can only concatenate list (not "tuple") to list_" error when passing the output of the embedding layer to the bidirectional LSTM layer. This error did not occur with a previous TensorFlow/Keras version 2.15.0. 

Here is a toy code that causes this error:
```python
import keras
import tensorflow as tf

vocab_size = 10
embed_size = 5
max_length = 50

sentences = ["Hello!", "How do you do?"]

text_vec_layer = keras.layers.TextVectorization(vocab_size, output_sequence_length=max_length)
text_vec_layer.adapt(sentences)

encoder_inputs = keras.layers.Input(shape=[], dtype=tf.string)
encoder_input_ids = text_vec_layer(encoder_inputs)

encoder_embedding_layer = keras.layers.Embedding(vocab_size, embed_size, mask_zero=True)
encoder_embeddings = encoder_embedding_layer(encoder_input_ids)

encoder = keras.layers.Bidirectional(keras.layers.LSTM(256, return_state=True))

encoder_outputs, *encoder_state = encoder(encoder_embeddings) # << this line causes the error
```

Error output:
```
Arguments received by Bidirectional.call():
  • args=('<KerasTensor shape=(None, 50, 5), dtype=float32, sparse=False, name=keras_tensor_6>',)
  • kwargs={'mask': '<KerasTensor shape=(None, 50), dtype=bool, sparse=False, name=keras_tensor_7>'}",
	"stack": "---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 21
     17 encoder_embeddings = encoder_embedding_layer(encoder_input_ids)
     19 encoder = keras.layers.Bidirectional(keras.layers.LSTM(256, return_state=True))
---> 21 encoder_outputs, *encoder_state = encoder(encoder_embeddings)

File c:\\Utilities\\Miniconda3\\envs\\tf\\Lib\\site-packages\\keras\\src\\utils\\traceback_utils.py:122, in filter_traceback.<locals>.error_handler(*args, **kwargs)
    119     filtered_tb = _process_traceback_frames(e.__traceback__)
    120     # To get the full stack trace, call:
    121     # `keras.config.disable_traceback_filtering()`
--> 122     raise e.with_traceback(filtered_tb) from None
    123 finally:
    124     del filtered_tb

File c:\\Utilities\\Miniconda3\\envs\\tf\\Lib\\site-packages\\keras\\src\\layers\\rnn\\bidirectional.py:190, in Bidirectional.compute_output_shape(self, sequences_shape, initial_state_shape)
    188     if self.merge_mode is None:
    189         return output_shape + state_shape + copy.copy(state_shape)
--> 190     return [output_shape] + state_shape + copy.copy(state_shape)
    191 return output_shape

TypeError: Exception encountered when calling Bidirectional.call().

can only concatenate list (not \"tuple\") to list

Arguments received by Bidirectional.call():
  • args=('<KerasTensor shape=(None, 50, 5), dtype=float32, sparse=False, name=keras_tensor_6>',)
  • kwargs={'mask': '<KerasTensor shape=(None, 50), dtype=bool, sparse=False, name=keras_tensor_7>'}"
```


Discussion:
Hi @csttsn ,

Thanks for reporting. Replicated the reported error and attached [gist](https://colab.sandbox.google.com/gist/SuryanarayanaY/08914fe3d1b6b3f49bca3dfaacfbd4c5/19328.ipynb). Seems like a bug. Will gig more and comeback to you.

Resolving patch:
diff --git a/keras/layers/rnn/bidirectional.py b/keras/layers/rnn/bidirectional.py
--- a/keras/layers/rnn/bidirectional.py
+++ b/keras/layers/rnn/bidirectional.py
@@ -182,13 +182,13 @@ def compute_output_shape(self, sequences_shape, initial_state_shape=None):
             output_shape[-1] *= 2
             output_shape = tuple(output_shape)
         elif self.merge_mode is None:
-            output_shape = [output_shape, copy.copy(output_shape)]
+            output_shape = [output_shape, output_shape]
 
         if self.return_state:
             if self.merge_mode is None:
-                return output_shape + state_shape + copy.copy(state_shape)
-            return [output_shape] + state_shape + copy.copy(state_shape)
-        return output_shape
+                return tuple(output_shape) + state_shape + state_shape
+            return tuple([output_shape]) + (state_shape) + (state_shape)
+        return tuple(output_shape)
 
     def call(
         self,


Test patch:
diff --git a/keras/layers/rnn/bidirectional_test.py b/keras/layers/rnn/bidirectional_test.py
--- a/keras/layers/rnn/bidirectional_test.py
+++ b/keras/layers/rnn/bidirectional_test.py
@@ -234,3 +234,29 @@ def test_return_state(self):
             np.array([[0.2501858, 0.2501858], [0.941473, 0.941473]]),
             c2,
         )
+
+    @pytest.mark.requires_trainable_backend
+    def test_output_shape(self):
+        x = np.array([[[101, 202], [303, 404]]])
+        for merge_mode in ["ave", "concat", "mul", "sum", None]:
+            sub_layer = layers.LSTM(2, return_state=True)
+            layer = layers.Bidirectional(sub_layer, merge_mode=merge_mode)
+            output = layer(x)
+            output_shape = layer.compute_output_shape(x.shape)
+            for out, shape in zip(output, output_shape):
+                self.assertEqual(out.shape, shape)
+
+        for merge_mode in ["concat", "ave", "mul", "sum"]:
+            sub_layer = layers.LSTM(2, return_state=False)
+            layer = layers.Bidirectional(sub_layer, merge_mode=merge_mode)
+            output = layer(x)
+            output_shape = layer.compute_output_shape(x.shape)
+            self.assertEqual(output.shape, output_shape)
+
+        # return_state=False & merge_mode=None
+        sub_layer = layers.LSTM(2, return_state=False)
+        layer = layers.Bidirectional(sub_layer, merge_mode=None)
+        output = layer(x)
+        output_shape = layer.compute_output_shape(x.shape)
+        for out, shape in zip(output, output_shape):
+            self.assertEqual(out.shape, shape)