Custom Keras layers, losses, metrics, and activation functions are Python objects that a saved .keras archive can describe but cannot recreate without the matching Python definition. Models that include those objects need a serialization path for the object configuration and a loading path that makes the object available again.
The native .keras format stores the model architecture, weights, and compile state, but it does not embed custom Python source code. A custom class that accepts constructor arguments should implement get_config() so those values are saved with the model configuration.
The preferred loading path registers the object with @keras.saving.register_keras_serializable and imports the module containing that decorator before calling load_model(). Keeping the class in a small importable module avoids the common failure where the archive exists, but Keras cannot locate the custom class during deserialization.
Related: How to save and load a Keras model
Related: How to create a custom layer in Keras
Steps to save and load a Keras model with custom objects:
- Save the custom layer in
custom_layers.py
.
- custom_layers.py
import keras from keras import ops @keras.saving.register_keras_serializable(package="Guide") class ScaleLayer(keras.layers.Layer): def __init__(self, factor=1.0, **kwargs): super().__init__(**kwargs) self.factor = factor def call(self, inputs): return ops.multiply(inputs, self.factor) def get_config(self): config = super().get_config() config.update({"factor": self.factor}) return config
get_config() stores the constructor values needed to rebuild the layer. Because factor is a plain float, no from_config() method is needed.
- Save a model that uses the registered layer.
- save_custom_model.py
import numpy as np import keras from custom_layers import ScaleLayer inputs = keras.Input(shape=(3,), name="features") x = ScaleLayer(0.5, name="scale_features")(inputs) outputs = keras.layers.Dense( 1, kernel_initializer="ones", bias_initializer="zeros", name="score", )(x) model = keras.Model(inputs, outputs) sample = np.ones((2, 3), dtype="float32") model(sample) model.save("custom_scale_model.keras") print("saved: custom_scale_model.keras") print(f"custom layer: {model.get_layer('scale_features').__class__.__name__}") print(f"prediction shape: {np.asarray(model(sample)).shape}")
- Run the save script.
$ python3 save_custom_model.py saved: custom_scale_model.keras custom layer: ScaleLayer prediction shape: (2, 1)
The saved archive contains the model config and weights. The custom layer code remains in custom_layers.py and must be importable wherever the model is loaded.
- Save a failing loader that omits the custom object import.
- load_without_custom_object.py
import keras keras.saving.load_model("custom_scale_model.keras")
- Run the failing loader and confirm that Keras cannot locate the class.
$ python3 load_without_custom_object.py Traceback (most recent call last): ##### snipped ##### TypeError: Could not locate class 'ScaleLayer'. Make sure custom classes are decorated with `@keras.saving.register_keras_serializable()`.
Do not set safe_mode=False just to bypass a missing custom class. Safe mode controls unsafe lambda deserialization in the Keras v3 format; it does not replace importing or registering the object.
- Save the corrected loader that imports the registration module before loading.
- load_custom_model.py
import numpy as np import keras from custom_layers import ScaleLayer # Registers Guide>ScaleLayer for load_model(). sample = np.ones((2, 3), dtype="float32") model = keras.saving.load_model("custom_scale_model.keras") prediction = np.asarray(model(sample)) print(f"custom layer: {model.get_layer('scale_features').__class__.__name__}") print(f"prediction shape: {prediction.shape}") print(f"first prediction: {prediction[0].tolist()}")
If a legacy object cannot use a decorator, import the class or function and pass it as custom_objects={“ScaleLayer”: ScaleLayer} to keras.saving.load_model().
- Run the corrected loader and verify that the custom layer reloads.
$ python3 load_custom_model.py custom layer: ScaleLayer prediction shape: (2, 1) first prediction: [1.5]
- Remove the temporary failing-loader probe after the corrected load works.
$ rm load_without_custom_object.py
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.