Repository: keras-team/keras (python)

Issue:
"ValueError: Undefined shapes are not supported." when calling model.call()
hello everybody.

I'm having trouble creating a Siamese network class, which extends keras.Model , from a function that returns the same model. My knowledge about [keras.Model](https://keras.io/api/models/model/) isn't good, so I don't know if it is a bug or my mistake.

This is the function:

```
def siamese_loss_network():
    inputs = keras.layers.Input((128, 128, 3))
    x = keras.applications.efficientnet.preprocess_input(inputs)
    base = keras.applications.EfficientNetB0(include_top=False, input_tensor=inputs, pooling = 'max')
    head = base.output
    x = keras.layers.Dense(256, activation="relu")(head)
    x = keras.layers.Dense(32)(x)
    embedding_network = keras.Model(inputs, x)
    input_1 = keras.layers.Input((128, 128, 3),name="input_layer_base_r")
    input_2 = keras.layers.Input((128, 128, 3),name="input_layer_base_l")
    tower_1 = embedding_network(input_1)
    tower_2 = embedding_network(input_2)
    merge_layer = keras.layers.Lambda(euclidean_distance, output_shape=(1,))(
        [tower_1, tower_2]
    )
    output_layer = keras.layers.Dense(1, activation="sigmoid")(merge_layer)
    siamese = keras.Model(inputs=[input_1, input_2], outputs=output_layer)
    return siamese

def euclidean_distance(vects):
    x, y = vects
    sum_square = ops.sum(ops.square(x - y), axis=1, keepdims=True)
    return ops.sqrt(ops.maximum(sum_square, keras.backend.epsilon()))
```

When running:

``` 
model = siamese_loss_network()
model.compile(optimizer=Adam(), loss=loss())
model.summary()
```


I get the following output:

```
Model: "functional_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                  ┃ Output Shape              ┃         Param # ┃ Connected to               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ input_layer_base_r            │ (None, 128, 128, 3)       │               0 │ -                          │
│ (InputLayer)                  │                           │                 │                            │
├───────────────────────────────┼───────────────────────────┼─────────────────┼────────────────────────────┤
│ input_layer_base_l            │ (None, 128, 128, 3)       │               0 │ -                          │
│ (InputLayer)                  │                           │                 │                            │
├───────────────────────────────┼───────────────────────────┼─────────────────┼────────────────────────────┤
│ functional (Functional)       │ (None, 32)                │       4,385,731 │ input_layer_base_r[0][0],  │
│                               │                           │                 │ input_layer_base_l[0][0]   │
├───────────────────────────────┼───────────────────────────┼─────────────────┼────────────────────────────┤
│ lambda (Lambda)               │ (None, 1)                 │               0 │ functional[0][0],          │
│                               │                           │                 │ functional[1][0]           │
├───────────────────────────────┼───────────────────────────┼─────────────────┼────────────────────────────┤
│ dense_2 (Dense)               │ (None, 1)                 │               2 │ lambda[0][0]               │
└───────────────────────────────┴───────────────────────────┴─────────────────┴────────────────────────────┘
 Total params: 4,385,733 (16.73 MB)
 Trainable params: 4,343,710 (16.57 MB)
 Non-trainable params: 42,023 (164.16 KB)
```


So here is my adaptation for a class that inherits keras.Model:

```
class SiameseModel(keras.Model):
    def __init__(self): 
        super().__init__()
        self.inputs = keras.layers.Input((128, 128, 3))
        self.input_1 = keras.layers.Input((128, 128, 3),name="input_layer_base_r")
        self.input_2 = keras.layers.Input((128, 128, 3),name="input_layer_base_l")
        self.base = keras.applications.EfficientNetB0(include_top=False, input_tensor=self.inputs, pooling = 'max')
        self.dense_1 = keras.layers.Dense(256, activation="relu")
        self.dense_2 = keras.layers.Dense(32)
        self.merge_layer = keras.layers.Lambda(euclidean_distance, output_shape=(1,))
        self.output_layer = keras.layers.Dense(1, activation="sigmoid")

    def call(self, inputs):
        head = self.base.output
        x = self.dense_1(head)
        x = self.dense_2(x)
        embedding_network = keras.Model(inputs, x)
        tower_1 = embedding_network(self.input_1)
        tower_2 = embedding_network(self.input_2)
        merge = self.merge_layer([tower_1, tower_2])
        output = self.output_layer(merge)
        return keras.Model(inputs=[self.input_1, self.input_2], outputs=output)
``` 

When running:

``` 
model = SiameseModel()
model.compile(optimizer=Adam(), loss=loss())
model.summary()
```

i got the error: 

```
Traceback (most recent call last):
  File "D:[project path]\main.py", line 20, in <module>
    model.summary()
  File "C:[user path]\.conda\envs\[env path]\lib\site-packages\keras\src\utils\traceback_utils.py", line 122, in error_handler
    raise e.with_traceback(filtered_tb) from None
  File "C:\[user path]\.conda\envs\[env path]\lib\site-packages\optree\ops.py", line 594, in tree_map
    return treespec.unflatten(map(func, *flat_args))
ValueError: Undefined shapes are not supported.
``` 


I read [this other issue](https://github.com/keras-team/keras/issues/19482), but i honestly didn't understand the reason for the error, nor how to resolve it. Could anyone enlighten me about this?

Python version: Python 3.10.13
pip version: 24.0
Tensorflow version: 2.16.1
Keras version: Version: 3.4.1

Grateful for the attention and hard work!



Pull request description:
Extend support to `Sequential._maybe_build` for functional/sequential models
Fix #19982

This PR also fixes `.summary()` when there is a built `Functional`/`Sequential` model as the first layer of a `Sequential` model

Patch:
diff --git a/keras/src/models/sequential.py b/keras/src/models/sequential.py
index 821b12d20..194d59ce6 100644
--- a/keras/src/models/sequential.py
+++ b/keras/src/models/sequential.py
@@ -137,6 +137,12 @@ class Sequential(Model):
         if isinstance(self._layers[0], InputLayer) and len(self._layers) > 1:
             input_shape = self._layers[0].batch_shape
             self.build(input_shape)
+        elif hasattr(self._layers[0], "input_shape") and len(self._layers) > 1:
+            # We can build the Sequential model if the first layer has the
+            # `input_shape` property. This is most commonly found in Functional
+            # model.
+            input_shape = self._layers[0].input_shape
+            self.build(input_shape)
 
     def _lock_state(self):
         # Unlike other layers, Sequential is mutable after build.
diff --git a/keras/src/utils/summary_utils.py b/keras/src/utils/summary_utils.py
index 7fe10f776..e6118fab8 100644
--- a/keras/src/utils/summary_utils.py
+++ b/keras/src/utils/summary_utils.py
@@ -96,12 +96,15 @@ def format_layer_shape(layer):
             )
     else:
         try:
-            outputs = layer.compute_output_shape(**layer._build_shapes_dict)
+            if hasattr(layer, "output_shape"):
+                output_shapes = layer.output_shape
+            else:
+                outputs = layer.compute_output_shape(**layer._build_shapes_dict)
+                output_shapes = tree.map_shape_structure(
+                    lambda x: format_shape(x), outputs
+                )
         except NotImplementedError:
             return "?"
-        output_shapes = tree.map_shape_structure(
-            lambda x: format_shape(x), outputs
-        )
     if len(output_shapes) == 1:
         return output_shapes[0]
     out = str(output_shapes)


Test patch:
diff --git a/integration_tests/dataset_tests/cifar100_test.py b/integration_tests/dataset_tests/cifar100_test.py
index 3a497dd20..7f2062e22 100644
--- a/integration_tests/dataset_tests/cifar100_test.py
+++ b/integration_tests/dataset_tests/cifar100_test.py
@@ -26,9 +26,9 @@ class Cifar100LoadDataTest(testing.TestCase):
     def test_dtypes(self):
         (x_train, y_train), (x_test, y_test) = cifar100.load_data()
         self.assertEqual(x_train.dtype, np.uint8)
-        self.assertEqual(y_train.dtype, np.uint8)
+        self.assertEqual(y_train.dtype, np.int64)
         self.assertEqual(x_test.dtype, np.uint8)
-        self.assertEqual(y_test.dtype, np.uint8)
+        self.assertEqual(y_test.dtype, np.int64)
 
     def test_invalid_label_mode(self):
         with self.assertRaises(ValueError):
diff --git a/keras/src/models/sequential_test.py b/keras/src/models/sequential_test.py
index ce51d7238..99f673b86 100644
--- a/keras/src/models/sequential_test.py
+++ b/keras/src/models/sequential_test.py
@@ -150,6 +150,58 @@ class SequentialTest(testing.TestCase):
         y = model(x)
         self.assertEqual(y.shape, (2, 3, 4))
 
+    def test_basic_flow_with_functional_model_as_first_layer(self):
+        # Build functional model
+        inputs = Input((16, 16, 3))
+        outputs = layers.Conv2D(4, 3, padding="same")(inputs)
+        functional_model = Model(inputs=inputs, outputs=outputs)
+
+        model = Sequential(
+            [functional_model, layers.Flatten(), layers.Dense(1)]
+        )
+        model.summary()
+        self.assertEqual(len(model.layers), 3)
+        self.assertTrue(model.built)
+        for layer in model.layers:
+            self.assertTrue(layer.built)
+
+        # Test eager call
+        x = np.random.random((1, 16, 16, 3))
+        y = model(x)
+        self.assertEqual(type(model._functional), Functional)
+        self.assertEqual(tuple(y.shape), (1, 1))
+
+        # Test symbolic call
+        x = backend.KerasTensor((1, 16, 16, 3))
+        y = model(x)
+        self.assertEqual(y.shape, (1, 1))
+
+    def test_basic_flow_with_sequential_model_as_first_layer(self):
+        # Build sequential model
+        sequential_model = Sequential(
+            [Input((16, 16, 3)), layers.Conv2D(4, 3, padding="same")]
+        )
+
+        model = Sequential(
+            [sequential_model, layers.Flatten(), layers.Dense(1)]
+        )
+        model.summary()
+        self.assertEqual(len(model.layers), 3)
+        self.assertTrue(model.built)
+        for layer in model.layers:
+            self.assertTrue(layer.built)
+
+        # Test eager call
+        x = np.random.random((1, 16, 16, 3))
+        y = model(x)
+        self.assertEqual(type(model._functional), Functional)
+        self.assertEqual(tuple(y.shape), (1, 1))
+
+        # Test symbolic call
+        x = backend.KerasTensor((1, 16, 16, 3))
+        y = model(x)
+        self.assertEqual(y.shape, (1, 1))
+
     def test_dict_inputs(self):
         class DictLayer(layers.Layer):
             def call(self, inputs):