Debugging load failures, writing robust custom objects and loading untrusted models safely require knowing these internals.
A `.keras` file is a zip archive with three parts: config.json (the serialised architecture and compile config), model.weights.h5 (the variable values, keyed by layer structure) and metadata.json (Keras version, date). Loading reads the config, re-creates each object, builds it, and then loads weights in order.
Re-creation relies on serialisation: each object's get_config() must return the arguments its constructor needs, and the class must be found by name. Built-in classes are found automatically; your own need @keras.saving.register_keras_serializable or custom_objects=. Most "unknown layer" or "unexpected keyword argument" load errors come from a missing registration or an incomplete get_config.
`safe_mode=True` (the default) refuses to deserialise Python bytecode, such as Lambda layers' functions, which could run arbitrary code. Only pass safe_mode=False for files you trust; better, replace Lambda layers with small registered custom layers.
Tracking: when you assign a layer, variable, list or dict of layers to an attribute, Keras records it, which is how weights, saving and trainable propagation find everything. Every weight is a keras.Variable wrapping a backend array, with a path naming where it lives. Layers are built lazily in build(), and the functional API records an operation graph of KerasTensors that is replayed when you call the model.
Example
import zipfile
import keras
model = keras.Sequential([keras.Input((3,)), keras.layers.Dense(2, name="head")])
model.save("m.keras")
print(sorted(zipfile.ZipFile("m.keras").namelist()))
# ['config.json', 'metadata.json', 'model.weights.h5']
cfg = model.get_layer("head").get_config()
print(cfg["units"], cfg["activation"]) # 2 linear
rebuilt = keras.layers.Dense.from_config(cfg) # a fresh, unbuilt Dense(2)
kernel = model.get_layer("head").kernel
print(isinstance(kernel, keras.Variable), kernel.path) # True .../head/kernel
lam = keras.Sequential([keras.Input((3,)), keras.layers.Lambda(lambda t: t * 2)])
lam.save("lam.keras")
try:
keras.saving.load_model("lam.keras") # safe_mode=True by default
except ValueError:
print("blocked: Lambda bytecode needs safe_mode=False")Remember
.keras= config.json + model.weights.h5 + metadata.jsonget_config+ registration let Keras rebuild custom objects- safe_mode blocks deserialising Python code such as Lambda layers
Watch out for
- Loading untrusted .keras files with
safe_mode=False.