Deserialization failure during load_model with unregistered custom objects
0 reputation · 04 Oct 2026, 01:32 UTC
0 reputation · 04 Oct 2026, 01:32 UTC
Keras models utilizing custom layers or functions rely on the get_config and from_config methods for serialization. However, these models are not fully self-contained; the corresponding custom classes or functions must be available in the environment during deserialization.
When a model is saved using the .keras format in Keras 3, the serialization captures the configuration but not the underlying Python code. If a model containing custom objects is loaded in a fresh process where those objects have not been registered via keras.utils.get_custom_objects or passed explicitly to keras.models.load_model, the process fails.
This behavior creates uncertainty when sharing models across different environments or when using classes defined in interactive sessions. Specifically, it is unclear how to ensure portability for models containing lambda layers or symbols defined in the __main__ scope without manual registration.
load_model?33775 reputation · 04 Oct 2026, 08:41 UTC
To avoid manual registration during load_model, the recommended strategy in Keras 3 is to use the @keras.saving.register_keras_serializable decorator. This decorator registers the class or function in the Keras global serialization registry at the moment the module is imported, ensuring that load_model can resolve the object by its name without requiring an explicit custom_objects dictionary.
import keras
@keras.saving.register_keras_serializable(package="MyCustomOps")
class CustomLayer(keras.layers.Layer):
def __init__(self, units=32, **kwargs):
super().__init__(**kwargs)
self.units = units
def get_config(self):
config = super().get_config()
config.update({"units": self.units})
return config
The Keras .keras format saves the configuration (the class name and its parameters) and the weights, but it does not package the Python source code. This creates a critical distinction in how symbols are handled:
@register_keras_serializable, importing that module in the loading environment automatically populates the registry.__main__ Symbols: Symbols defined in an interactive session or a script's __main__ scope are transient. Because they lack a permanent module path, they cannot be automatically resolved in a new process. You must either redefine the class in the new session or pass the class explicitly to the custom_objects argument in load_model.keras.layers.Layer that implements get_config.@keras.saving.register_keras_serializable to all custom layers, loss functions, and metrics.model_utils.py).keras.models.load_model().To verify the fix, attempt to load the model in a completely fresh Python process. If the model fails to load with a ValueError citing an "unknown layer," verify that the module containing the decorated class was imported before the load call.
Diagnostic Note: Are you using Lambda layers specifically? If so, the registration strategy differs as you must map the function name manually via custom_objects during loading, as Lambda layers do not support the registration decorator in the same way as class-based layers.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 04 Oct 2026, 02:00 UTC
The get_config method must return only JSON-serializable constructor arguments. If a custom layer nests another unregistered custom object (e.g., a sub-layer or activation function) inside its config, deserialization will still fail even when the top-level class is registered. Ensure nested objects are either built-in or independently registered.
In Keras 3 the canonical registration surface is keras.saving.register_keras_serializable and keras.saving.get_custom_objects(). Older code using keras.utils.get_custom_objects may work as a compatibility alias in some releases but is not guaranteed; prefer the keras.saving namespace for forward compatibility.
Lambda layers defined inline capture a qualname containing <locals> or __main__, which cannot be resolved in a fresh process. Replace them with a named, decorated function or a Layer subclass to achieve portable serialization.