The Solution
To resolve the ValueError: Unknown layer: <function <lambda>..., you must provide the function used in the Lambda layer to the custom_objects argument during model loading. Because lambda functions are anonymous and not serializable by Python's pickle module, Keras cannot reconstruct the function from the saved model file alone.
Implementation Steps
- Define a named function: Replace the inline
lambda with a standard Python function. This ensures the function has a consistent name that can be referenced across different sessions.
- Update the Layer: Pass the named function into the
Lambda layer.
- Load with Custom Objects: Pass a dictionary mapping the function name to the actual function object when calling
load_model.
import tensorflow as tf
from tensorflow.keras.layers import Lambda
from tensorflow.keras.models import Sequential, load_model
# 1. Define a named function instead of a lambda
def multiply_by_two(x):
return x * 2
# 2. Use the named function in the model
model = Sequential([
Lambda(multiply_by_two, name="my_lambda_layer")
])
model.save("my_model.h5")
# 3. Provide the function in custom_objects during loading
reloaded_model = load_model("my_model.h5",
custom_objects={"multiply_by_two": multiply_by_two})
Technical Explanation
Why this happens
Keras saves the configuration of a layer, not the executable bytecode of the function. For a Lambda layer, it saves the name of the function. Since lambda functions are anonymous, they are assigned a generic name (like <lambda>) and a memory address that changes every time the Python interpreter starts. When load_model is called in a new process, the memory address is gone, and the name <lambda> is ambiguous, leading to the ValueError.
Trade-offs of Automatic Wrapping
While Keras could theoretically attempt to serialize the bytecode of a lambda function, this introduces significant risks:
- Security: Deserializing arbitrary bytecode (similar to
pickle) can lead to remote code execution (RCE) vulnerabilities if a model file is tampered with.
- Compatibility: Bytecode is often version-specific. A lambda serialized in Python 3.10 might not execute in Python 3.12.
- Performance: There is negligible performance gain in using a lambda over a named function; the overhead is entirely in the serialization logic.
Strengthening documentation is the preferred path over API changes, as requiring named functions enforces a disciplined approach to model versioning and deployment.
Verification
To verify the fix, attempt to load the model in a completely fresh Python shell or a separate script where the model was not originally defined. If the model loads without the custom_objects dictionary and fails, but succeeds with it, the resolution is confirmed.
Diagnostic Detail: Are you using the .h5 format or the newer SavedModel format? The SavedModel format (default in TF 2.x) attempts to save the computation graph, which can sometimes bypass this issue for simple operations, whereas .h5 relies more heavily on Python-side reconstruction.