skip to content

Saving and Multi-Backend

Saving a Keras model is where custom code bites back: the .keras v3 archive re-instantiates your classes, so custom layers and losses must be registered as serializable. Keras 3 also lets you pick the numeric backend, which decides what a saved model can run on.

on this pageshow

questions

6

In Keras 3, what does model.save('model.keras') store and how do you load it?

level: juniorimportance: must knowfreq 72%

answer

  1. one file, not a directory
  2. zip: config JSON plus weights
  3. optimizer state travels with it
  4. extension is validated, .keras or .h5

basics

~10 s

model.save('model.keras') writes one zip archive holding the architecture config, the weights and the optimizer state. keras.saving.load_model('model.keras') rebuilds the whole model, ready to predict or resume training without re-running the build code.

solid answer

~30 s

The native Keras 3 format is the `.keras` v3 archive: a zip holding `metadata.json`, `config.json` (the serialized architecture) and `model.weights.h5` (the variable values). `model.save("model.keras")` writes it; `keras.saving.load_model("model.keras")` reads it back and returns a live model with the same layers, the same weights and, if the model was compiled, the optimizer state - so you can call `predict()` at once or resume `fit()`. The extension is checked: `model.save("model")` raises a ValueError asking for `.keras` (native) or `.h5` (legacy HDF5). Pass `compile=False` to `load_model` for inference-only loading.

code

python · 9 lines
python
import keras

model = keras.Sequential([keras.layers.Dense(8, activation="relu"), keras.layers.Dense(1)])
model.compile(optimizer="adam", loss="mse")
model.build((None, 4))

model.save("model.keras")
restored = keras.saving.load_model("model.keras")
restored.summary()

go deeper

for a junior

Know the two calls by heart: model.save('model.keras') and keras.saving.load_model('model.keras'), and be able to say the archive holds architecture plus weights, not just numbers.

for a middle

Explain what is inside the archive - metadata, a config JSON and a weights HDF5 - and why including optimizer state is what lets you resume training rather than restart it.

for a senior

Talk about artifact hygiene: pinning the Keras version next to the file, using compile=False in serving processes, and knowing that reloading depends on your custom classes still being importable.

for a principal

Frame it as an artifact contract. Decide what your teams ship - a resumable checkpoint for training continuity versus an inference-only export for serving - and make the two paths explicit instead of letting each project improvise.

## What the archive contains `model.save("model.keras")`, equivalently `keras.saving.save_model(model, "model.keras")`, produces the `.keras` v3 archive. Despite the extension it is an ordinary zip file with three entries: - `metadata.json` - the Keras version that wrote it, plus a timestamp. - `config.json` - the model configuration: a JSON tree of class names and the constructor arguments of every layer, wired into the same graph. - `model.weights.h5` - an HDF5 file of the variable values (kernels, biases, normalization moving statistics, and the optimizer variables of a compiled model). The split matters: the config is code-free JSON describing how to rebuild, and the weights file is plain numeric arrays. Nothing in the archive is executable, which is why a saved model is portable but also why Keras must be able to find your own classes again at load time. ## The round trip ``` model.save("model.keras") restored = keras.saving.load_model("model.keras") ``` `load_model` parses `config.json`, instantiates each class with its recorded arguments, then streams the weights into the freshly created variables. The result is a normal model object: `restored.predict(x)` works, `restored.summary()` prints the same table, and because the optimizer variables travelled too, `restored.fit(...)` continues with the optimizer's accumulated state intact rather than from a cold start. That last point is the practical reason to prefer whole-model saving over weights-only saving when you may need to resume a run. `load_model` takes `compile=False` if you only want to serve the model; the layers and weights are restored, the optimizer and loss are not, and the model comes back uncompiled. ## Extensions and the legacy path Keras 3 validates the filepath. No extension raises a ValueError telling you to add `.keras` or `.h5`. A `.h5` or `.hdf5` suffix still works but takes the legacy HDF5 whole-model path, and Keras warns that the native format is recommended. Legacy HDF5 predates the v3 archive, stores weights by layer name, and carries custom objects far less reliably - treat it as a format you read old files with, not one you write new ones in. There is a third verb: `model.export(...)` writes an inference-only artifact for serving stacks rather than a resumable Keras model. Do not reach for it when what you want is to reload the model in Python. ## What can still go wrong The archive stores class names, not class code. A model built only from built-in Keras layers therefore always reloads. A model containing your own `Layer` subclass reloads only if Keras can map the recorded name back to a Python class at load time - otherwise the load fails while trying to locate the class, and the fix is to register the class or pass it in `custom_objects`. The other common surprise is version skew: an archive written by a much newer Keras may contain config keys an older Keras does not understand. Pin the Keras version alongside the artifact the way you pin any other dependency.

  • What changes if you pass compile=False to load_model?
    You get the architecture and the weights but no training configuration: the optimizer, loss and metrics are not restored and the model comes back uncompiled. That is what you want in a serving process, which never calls fit() and should not pay to rebuild optimizer state. Call compile() yourself if you later decide to keep training.
  • Why can the same .keras file be loaded on a machine running a different Keras backend?
    Because nothing in the archive is backend-specific: config.json is class names plus arguments, and model.weights.h5 is plain numeric arrays. Keras re-instantiates the layers using whichever backend is active and fills them with those arrays. Portability only breaks when your own layer code calls a particular framework's ops directly.

saying these in an interview costs you the question

  • Thinking .keras is a directory of protobuf files
  • Believing the archive stores your layer's source code
  • Saying save() writes weights only
  • Assuming any filename works and the extension is ignored

context

open as a page

How do you choose the Keras 3 backend, and when must that choice happen?

level: middleimportance: must knowfreq 60%

basics

~20 s

Set the KERAS_BACKEND environment variable to tensorflow, jax or torch before the first import of keras, or set the backend field in the ~/.keras/keras.json config file. The choice is fixed for the process once keras is imported; keras.backend.backend() reports it.

open as a page

Why does Keras load_model fail to locate your custom layer, and how do you fix it?

level: middleimportance: must knowfreq 68%

basics

~20 s

The .keras archive stores a class name and its constructor arguments, never the code. On load, Keras must map that name to a real Python class; if it cannot, the load fails. Fix it with @keras.saving.register_keras_serializable or by passing custom_objects.

open as a page

When would you use Keras save_weights instead of saving the whole model?

level: middleimportance: should knowfreq 58%

basics

~20 s

Use save_weights when the architecture already lives in code you trust: it writes only variable values to a .weights.h5 file, so loading needs a model that is already built with the identical structure. Whole-model .keras saving is for artifacts that must reconstruct themselves.

open as a page

What must a Keras custom layer's get_config return to survive a .keras round trip?

level: seniorimportance: should knowfreq 46%

basics

~10 s

get_config must return a JSON-serializable dict carrying every constructor argument, merged over super().get_config(). Anything omitted comes back as a default after reload. Nested Keras objects need keras.saving.serialize_keras_object plus a from_config override that deserializes them.

open as a page

Why does calling framework ops inside a Keras 3 custom layer break portability?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A layer whose call() invokes one framework's tensor API only runs when that framework is the active backend, so the saved model silently stops being portable. keras.ops is the backend-agnostic numerics layer, and keras.random is its counterpart for random numbers.

open as a page