Keras load_model failure with custom object serialization
0 reputation · 23 May 2020, 14:35 UTC
0 reputation · 23 May 2020, 14:35 UTC
When utilizing tf.keras.models.load_model to restore a model containing custom layers or loss functions, TensorFlow requires a custom_objects dictionary to map serialized names back to their Python implementations. While this mechanism handles basic custom classes, consistency issues arise when models are transferred between different Keras versions or environments where the class definitions have evolved.
The primary constraint is the reliance on manual mapping to resolve serialization gaps, which can lead to runtime errors if the internal configuration of the custom object has changed but the class name remains identical.
Does TensorFlow provide a mechanism to automate the mapping of custom objects without manually defining the dictionary for every load operation? How does the SavedModel format handle versioning for custom layer configurations to prevent incompatibility during restoration?
To automate the mapping of custom objects without manually passing a dictionary to every load_model call, TensorFlow provides the @tf.keras.utils.register_keras_serializable decorator. When a custom layer, loss, or metric is decorated with this attribute, Keras adds it to a global registry, allowing the loader to resolve the object by its name automatically during deserialization.
Instead of maintaining a custom_objects dictionary, apply the decorator to your class definition:
import tensorflow as tf
@tf.keras.utils.register_keras_serializable(package="MyCustomLayers")
class CustomDense(tf.keras.layers.Layer):
def __init__(self, units=32, **kwargs):
super().__init__(**kwargs)
self.units = units
def build(self, input_shape):
self.w = self.add_weight(shape=(input_shape[-1], self.units))
def call(self, inputs):
return tf.matmul(inputs, self.w)
def get_config(self):
config = super().get_config()
config.update({"units": self.units})
return config
Once registered, tf.keras.models.load_model("path/to/model") will resolve CustomDense without additional arguments, provided the class definition is imported into the current execution environment before the load call.
The SavedModel format handles custom layers differently than the legacy H5 format. While H5 relies heavily on Python class names and get_config(), SavedModel stores the concrete computation graph (via TensorFlow's GraphDef). This means the model can often be loaded for inference (prediction) without the original Python code, as the operations are baked into the graph.
When loading for training or fine-tuning, Keras still requires the Python class to reconstruct the layer object. To prevent incompatibility when configurations evolve, follow these standards:
get_config() implementation: Always include all hyperparameters in get_config() and ensure the __init__ method can handle those exact keys.__init__ to maintain backward compatibility if new parameters are added to a layer in newer versions of your code.To verify that registration is working and the model is portable:
model.save("my_model").model = tf.keras.models.load_model("my_model") without the custom_objects parameter.Diagnostic Note: If you are encountering a ValueError despite registration, please specify if you are loading a .h5 file or a SavedModel directory, as the serialization paths differ significantly.
Use comments to ask for clarification. Post a solution as an answer.
31,275 reputation · 23 May 2020, 17:04 UTC
When using @tf.keras.utils.register_keras_serializable, the decorated class must be imported (or defined) in the same Python session before calling tf.keras.models.load_model; otherwise Keras cannot find the name in its internal registry and will fall back to requiring a custom_objects dictionary.
To avoid accidental name clashes, supply a distinct package argument (e.g., package="myproj.layers") and keep the class name unique across your codebase.
You can verify what is currently registered with:
import tensorflow as tf
print(tf.keras.saving.get_custom_objects())