Repository: keras-team/keras
Issue category: Feature Request

Issue:
Add a warning for mismatched inputs structure in `Functional`
There are several issues related to mismatched inputs structure:
- #20166
- #20136
- #20086
and more...

As a result, it would be beneficial to add a warning for users.
Ultimately, we might want to raise an error when a mismatch occurs. Otherwise, it could lead to subtle issues if the inputs have the same shape and dtype, as the computation could be incorrect even though the code runs.


Hints:
## [Codecov](https://app.codecov.io/gh/keras-team/keras/pull/20170?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 79.34%. Comparing base [(`3cc4d44`)](https://app.codecov.io/gh/keras-team/keras/commit/3cc4d44df0d9d2cc0766c9a36ddc9cc0f20e9f3d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) to head [(`dddfbfe`)](https://app.codecov.io/gh/keras-team/keras/commit/dddfbfecfcba4a117e0f1c9563173aedcc053f37?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master   #20170   +/-   ##
=======================================
  Coverage   79.34%   79.34%           
=======================================
  Files         501      501           
  Lines       47319    47325    +6     
  Branches     8692     8694    +2     
=======================================
+ Hits        37544    37550    +6     
  Misses       8017     8017           
  Partials     1758     1758           
```

| [Flag](https://app.codecov.io/gh/keras-team/keras/pull/20170/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | Coverage Δ | |
|---|---|---|
| [keras](https://app.codecov.io/gh/keras-team/keras/pull/20170/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | `79.19% <100.00%> (+<0.01%)` | :arrow_up: |
| [keras-jax](https://app.codecov.io/gh/keras-team/keras/pull/20170/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | `62.47% <100.00%> (+<0.01%)` | :arrow_up: |
| [keras-numpy](https://app.codecov.io/gh/keras-team/keras/pull/20170/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | `57.65% <100.00%> (+0.07%)` | :arrow_up: |
| [keras-tensorflow](https://app.codecov.io/gh/keras-team/keras/pull/20170/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | `63.85% <100.00%> (+<0.01%)` | :arrow_up: |
| [keras-torch](https://app.codecov.io/gh/keras-team/keras/pull/20170/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | `62.50% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/keras-team/keras/pull/20170?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team).

After upgrading Keras to latest version I am getting the warning, but can't figure out, what is wrong with the input

This is my code
![image](https://github.com/user-attachments/assets/f98f73b2-a05d-4950-952d-0e8191ab89b5)

and this is the output:
```
[<KerasTensor shape=(None, 42), dtype=float16, sparse=False, name=X_input>, <KerasTensor shape=(None, 15), dtype=float16, sparse=False, name=B_input>]                                                                                                                                            
x dtype: float16 shape: (1, 42)                                                                                                                                                                                                                                                                   
b dtype: float16 shape: (1, 15)                                                                                                                                                                                                                                                                   
UserWarning: The structure of `inputs` doesn't match the expected structure: ['X_input', 'B_input']. Received: the structure of inputs=('*', '*')                                             
```
Hard to see, what is wrong

I have tried with: result = model.predict({'X_input': x, 'B_input': b},verbose=0)
but got the same warning

Code is working fine
@ThorvaldAagaard Same here. Did you figure it out?
I did not find a fix, except downgrading Tensorflow to 2.17
I assume it is a bug in the implemenation, but the fix is merged, so perhaps noone else but us are reading this.

There should be a way to disable these warnings.

The change is this

```
    def _maybe_warn_inputs_struct_mismatch(self, inputs):
        try:
            tree.assert_same_structure(
                inputs, self._inputs_struct, check_types=False
            )
        except:
            model_inputs_struct = tree.map_structure(
                lambda x: x.name, self._inputs_struct
            )
            inputs_struct = tree.map_structure(lambda x: "*", inputs)
            warnings.warn(
                "The structure of `inputs` doesn't match the expected "
                f"structure: {model_inputs_struct}. "
                f"Received: the structure of inputs={inputs_struct}"
            )
```

so probably 
assert_same_structure 
is the problem

I found this, that can suppress all the warnings from Python
```
import warnings
warnings.filterwarnings("ignore")
```
It is working

Simple script to reproduce the error:

```
keras==3.6.0
tensorflow==2.17.0
```

```python
import numpy as np
from tensorflow.keras import layers, models

# Generate model
input_tensor_1 = layers.Input(shape=(10,), name='input_1')
input_tensor_2 = layers.Input(shape=(5,), name='input_2')

combined = layers.concatenate([input_tensor_1, input_tensor_2])

output = layers.Dense(1, activation='sigmoid')(combined)

model = models.Model(inputs=[input_tensor_1, input_tensor_2], outputs=output)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# Train
num_samples = 1000

X1 = np.random.rand(num_samples, 10)
X2 = np.random.rand(num_samples, 5)
y = np.random.randint(2, size=num_samples)

model.fit([X1, X2], y, epochs=5, batch_size=32, verbose=0)

# Prediction
X1_new = np.random.rand(5, 10)
X2_new = np.random.rand(5, 5)

model.predict([X1_new, X2_new], verbose=0)
```

which gives the mentioned error:
```
UserWarning: The structure of `inputs` doesn't match the expected structure: ['input_1', 'input_2']. Received: the structure of inputs=('*', '*')
  warnings.warn(
```

By debugging, it seems a simple list/tuple mismatch in my case, given by:
```python
            tree.assert_same_structure(
                inputs, self._inputs_struct, check_types=False
            )
```

Fixed by using tuple as input for model:

```python
model = models.Model(inputs=(input_tensor_1, input_tensor_2), outputs=output)
```

Patch:
diff --git a/keras/src/models/functional.py b/keras/src/models/functional.py
index 0acb84ed028..9c71308a651 100644
--- a/keras/src/models/functional.py
+++ b/keras/src/models/functional.py
@@ -214,8 +214,12 @@ def _assert_input_compatibility(self, *args):
 
     def _maybe_warn_inputs_struct_mismatch(self, inputs):
         try:
+            # We first normalize to tuples before performing the check to
+            # suppress warnings when encountering mismatched tuples and lists.
             tree.assert_same_structure(
-                inputs, self._inputs_struct, check_types=False
+                tree.lists_to_tuples(inputs),
+                tree.lists_to_tuples(self._inputs_struct),
+                check_types=False,
             )
         except:
             model_inputs_struct = tree.map_structure(


Test patch:
diff --git a/keras/src/models/functional_test.py b/keras/src/models/functional_test.py
index ef792fd7f11..44858d33811 100644
--- a/keras/src/models/functional_test.py
+++ b/keras/src/models/functional_test.py
@@ -1,4 +1,5 @@
 import os
+import warnings
 
 import numpy as np
 import pytest
@@ -503,13 +504,19 @@ def test_warning_for_mismatched_inputs_structure(self):
         model = Model({"i1": i1, "i2": i2}, outputs)
 
         with pytest.warns() as record:
-            model([np.ones((2, 2)), np.zeros((2, 2))])
+            model.predict([np.ones((2, 2)), np.zeros((2, 2))], verbose=0)
         self.assertLen(record, 1)
         self.assertStartsWith(
             str(record[0].message),
             r"The structure of `inputs` doesn't match the expected structure:",
         )
 
+        # No warning for mismatched tuples and lists.
+        model = Model([i1, i2], outputs)
+        with warnings.catch_warnings(record=True) as warning_logs:
+            model.predict((np.ones((2, 2)), np.zeros((2, 2))), verbose=0)
+            self.assertLen(warning_logs, 0)
+
     def test_for_functional_in_sequential(self):
         # Test for a v3.4.1 regression.
         if backend.image_data_format() == "channels_first":