Skip to content

Fix loading nested-Sequential .h5 exports under Keras 3 - #14

Merged
MeqdadDev merged 2 commits into
mainfrom
fix/nested-sequential-h5-load
Sep 12, 2026
Merged

MeqdadDev merged 2 commits into
mainfrom
fix/nested-sequential-h5-load

Conversation

@MeqdadDev

Copy link
Copy Markdown
Owner

Some Teachable Machine .h5 exports save the model as a Sequential wrapping nested Sequential/Functional submodels:

Sequential
├─ InputLayer
├─ Sequential
│  ├─ InputLayer
│  ├─ Functional (MobileNet backbone)
│  └─ GlobalAveragePooling2D
└─ Sequential
   ├─ InputLayer
   ├─ Dense
   └─ Dense

Keras 3's legacy H5 loader mis-rebuilds this shape: when the Functional backbone is called as a plain layer inside Sequential.build(), its single output tensor comes back wrapped in a length-1 Python list instead of the bare tensor. The next layer then treats that list as two separate inputs, failing with:
ValueError: ... expects 1 input(s), but it received 2 input tensors.
_load_model previously caught this as a generic exception and re-raised it as a misleading FileNotFoundError: Model file not found, hiding the real cause.

Fix: _load_model now falls back to a new _load_nested_sequential_h5 when the normal load_model() call fails. It:

Flattens the nested Sequential wrappers out of the model_config JSON (keeping only the first InputLayer, leaving any Functional submodels intact).
Rebuilds the graph via the Functional API, unwrapping the length-1 list after calling a Functional submodel.
Restores weights by matching each leaf layer's name against the H5 file's saved weight paths directly, since Keras's built-in weight loader expects the now-discarded nested-Sequential grouping those paths were originally saved under.
Models that already load fine via load_model() are unaffected — this is a pure fallback path.

Tested against a real Teachable Machine MobileNetV2 export (2 classes) that previously failed with the error above; it now loads and classifies correctly.

@deepsource-io

deepsource-io Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

DeepSource Code Review

We reviewed changes in ccd3065...cc1d2db on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python Sep 12, 2026 6:19p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@MeqdadDev
MeqdadDev added this pull request to stack #15 September 12, 2026 17:50
Base automatically changed from fix/tf-keras3-compat to main September 12, 2026 18:12
Some Teachable Machine .h5 exports save the model as a Sequential
wrapping nested Sequential/Functional submodels: Sequential > [InputLayer,
Sequential > [InputLayer, Functional MobileNet backbone,
GlobalAveragePooling2D], Sequential > [InputLayer, Dense, Dense]].

Keras 3's legacy H5 loader mis-rebuilds that shape: when the Functional
backbone is called as a plain layer inside Sequential.build(), its single
output tensor comes back wrapped in a length-1 Python list instead of the
bare tensor. The next layer then treats that list as two separate inputs,
failing with e.g. "expects 1 input(s), but it received 2 input tensors" --
which _load_model previously mislabeled as a generic
"Model file not found" FileNotFoundError.

_load_model now falls back to _load_nested_sequential_h5 when the normal
load_model() call fails. It flattens the nested Sequential wrappers out of
the model_config JSON (keeping only the first InputLayer, leaving any
Functional submodels intact), rebuilds the graph via the Functional API
(unwrapping the length-1 list after calling a Functional submodel), and
restores weights by matching each leaf layer's name against the H5 file's
saved weight paths directly, since Keras's built-in weight loader expects
the now-discarded nested-Sequential grouping those paths were saved under.

Models that already load fine via load_model() are unaffected.
@MeqdadDev
MeqdadDev force-pushed the fix/nested-sequential-h5-load branch from c607500 to 2f28433 Compare September 12, 2026 18:12
Adds a docstring to the nested weight-path walk() helper (the one genuine
new-code finding DeepSource flagged on PR #14) and drops the redundant
`(object)` base class, a no-op cleanup that's free to fix.

The remaining findings the check reports (missing docstrings on
_load_labels/_get_image_classification/_preprocess_image, long docstring
lines, etc.) predate this branch entirely -- confirmed via git blame,
they're from 2023-2024 commits -- so they're left alone as pre-existing
repo debt rather than scope-creeping this bugfix PR.
@MeqdadDev
MeqdadDev merged commit 28046b0 into main Sep 12, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant