Quick Reference · deep learning in Python · Keras 3.15 (Jul 2026)

keras + keras_preprocessing · tensorflow · torch

Keras is an API, not a runtime. You describe layers and models; a backend — JAX, TensorFlow, PyTorch, or OpenVINO for inference — actually executes them. You choose it with one environment variable before the import, and a model built only from built-in layers runs unchanged on any of them. Everything below follows from that one separation.
setup & backend keras.ops & tensors layers & models training data & preprocessing save, export & tune gotcha / deprecated most common
Verified 2026-08-25 against Keras 3.15.1 (Python 3.11+; backends: JAX / TensorFlow / PyTorch / OpenVINO-inference). Cross-checked against: keras.io (API docs, developer guides, Keras 3 announcement) · github.com/keras-team (keras releases) · tensorflow.org · pytorch.org · pypi.org (keras 3.15.1, Jul 2026)
One API, four runtimes
A · THE SEPARATION THAT DEFINES KERAS 3 your code — keras.layers · keras.Model · compile() · fit() keras.ops — one NumPy-shaped surface over every backend dispatch jax fastest, TPU-native tensorflow tf.data · SavedModel torch DataLoader · nn.Module openvino inference only the switch os.environ["KERAS_BACKEND"] = "torch" import keras must be set BEFORE the import or edit ~/.keras/keras.json B · THE SAME LOOP WHATEVER IS UNDERNEATH any data source NumPy · tf.data.Dataset torch DataLoader · PyDataset keras.Model Sequential · Functional Subclass .compile().fit() or your own train_step or a native backend loop three different outputs model.save("m.keras") ← resume training model.save_weights("m.weights.h5") ← weights only model.export(...) ← SavedModel / LiteRT C · WHERE keras-preprocessing WENT keras-preprocessing final release 1.1.2 · May 2020 repo archived Sep 2024 Python 2.7–3.6 only keras._legacy.preprocessing keras.utils.image_dataset_from_directory keras.layers.Random* · Rescaling · Resizing keras.layers.TextVectorization why it matters Augmentation now runs as layers — on the GPU, inside the model, and travelling with it when exported. The old generators were CPU-bound NumPy loops that had to be reimplemented at serving time. One genuine gap: shear has no built-in replacement layer.
import os
os.environ["KERAS_BACKEND"] = "jax"   # or "tensorflow" | "torch" — BEFORE the import
import keras

model = keras.Sequential([
    keras.layers.Rescaling(1./255),        # preprocessing IS a layer now
    keras.layers.Dense(128, activation="relu"),
    keras.layers.Dense(10),                  # logits, no softmax
])
model.compile(optimizer="adam",
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=["accuracy"])
model.fit(train_data, epochs=5, validation_data=val_data)
model.save("m.keras")                       # extension is mandatory

# train_data may be NumPy, a tf.data.Dataset, a torch DataLoader,
# or a keras.utils.PyDataset — on ANY backend.
Change one string on line 2 and the same model trains on a different framework.
PART I

keras  ·  the core

Keras 3 is a full rewrite: the same API you know, reimplemented over four backends. Anything built only from built-in layers is portable today — existing tf.keras models included. Custom code is portable only if you write it with keras.ops instead of tf.* or torch.*.
01Install & the backend switchthe first decision
02The API surfacewhat lives where
03keras.opswrite once, run anywhere
04Tensors, variables & RNGstate, portably
05Three ways to buildpick the simplest that fits
06Layersthe catalogue
07Write your own layerthe extension point
08Losses, metrics, optimizersthe training contract
09Learning-rate schedulesthe highest-leverage knob
10compile() & fit()the loop, for free
11Callbackshooks into fit()
12Custom trainingfrom train_step to raw loops
13Debug & inspectbefore blaming the model
14Save, load & serializethree different files
15Export & deployout of Python
16Quantization & LoRAmaking big models fit
17KerasTunersearching the space
18Feeding the modeldata, portably
19Preprocessing layersthe modern way
20Pretrained modelsapplications & KerasHub
21Transfer learningthe recipe, in order
22Migrating Keras 2 → 3what actually changes
23The JAX backendbriefly, for completeness
PART II

keras-preprocessing  ·  the archived module

Read this before copying any tutorial. The standalone package's final release was 1.1.2 in May 2020 — declaring support for Python 2.7–3.6 — and the repository was archived in September 2024. Its symbols moved into core Keras, were deprecated there, and in Keras 3 survive only under the private keras._legacy.preprocessing namespace. This part covers what each symbol became.
K1Where it standsthe honest status
K2The image APIImageDataGenerator → layers
K3The text APITokenizer → TextVectorization
K4The sequence APIgenerators → functions
K5Rebuilding an old pipelinebefore and after
K6Migration mapold call → new call
PART III

keras + tensorflow  ·  the default pairing

Since TF 2.16, pip install tensorflow brings Keras 3 and tf.keras is Keras 3. This is the backend with the most mature serving story — tf.data, SavedModel, TF Serving, tf.distribute — and the one where reaching for tf.* inside a custom layer costs you portability without any warning.
T1Setup on TensorFlowgains and trade-offs
T2tf.data with Kerasthe input pipeline
T3TF-native trainingwhen you drop down
T4Graph mode & tf.functionwhat Keras hides
T5Export & servingthe TF advantage
T6The Keras 2 escape hatchwhen you must go back
PART IV

keras + pytorch  ·  two-way interop

The most surprising part of Keras 3: on the torch backend a Keras layer is a torch.nn.Module. Not wrapped, not adapted — it subclasses it. So Keras layers drop into PyTorch models and register their parameters, PyTorch modules drop into Keras models as layers, and fit() accepts a DataLoader. You can adopt as much or as little of either as you like.
P1Setup on PyTorchgains and trade-offs
P2Data with DataLoadertorch-native input
P3Layers both waysthe interop
P4A native torch loopfull manual control
P5The torch ecosystemwhat else carries over
P6Multi-GPU with DDPscaling out

Four pictures worth the whole reference

What each backend gives you, where preprocessing went, how the PyTorch interop works, and which file to save.

1 · choosing a backend

The same model runs on all four. What differs is the ecosystem you inherit — and how much of your custom code stays portable.

jax fastest, TPU-native stateless / functional sharding built in tensorflow tf.data pipelines SavedModel, Serving most existing code torch DataLoader works layers ARE nn.Modules DDP for multi-GPU openvino CPU inference no training load and predict built-in layers only → portable across all four, unchanged custom layers using keras.ops → still portable custom code using tf.* or torch.* → pinned to that one backend

2 · where preprocessing went

Every symbol from the archived module has a home — usually a layer, so the transformation lives inside the model and is exported with it.

keras-preprocessing (2020) Keras 3 ImageDataGenerator image_dataset_from_directory rotation/zoom/flip_range layers.Random* rescale=1./255 layers.Rescaling Tokenizer layers.TextVectorization TimeseriesGenerator utils.timeseries_dataset_from_array Sequence utils.PyDataset pad_sequences utils.pad_sequences — unchanged shear_range nothing built in — write a layer

3 · the PyTorch interop

On the torch backend the inheritance is real: keras.layers.Layer subclasses torch.nn.Module. That single fact makes the interop work in both directions with no adapter.

torch.nn.Module keras.layers.Dense(64) appears in net.parameters() keras.Sequential MyTorchModule() used as an ordinary layer class Layer(torch.nn.Module): — on the torch backend, literally So a torch optimizer trains Keras layers, DDP wraps a Keras model, and state_dict() / .to(device) all behave exactly as you expect. None of this holds on the JAX or TensorFlow backend — it is torch-specific.

4 · which file do you want

Three calls, three artifacts, three purposes. Choosing the wrong one is the most common Keras 3 stumble, because save() stopped producing a SavedModel.

model.save("m.keras") architecture + weights + optimizer state → keras.saving.load_model() resume training model.save_weights("m.weights.h5") weights only → load_weights() into an identical architecture transfer / partial model.export("dir/") SavedModel / LiteRT / ONNX → serving runtimes, not load_model() deploy A bare path in save() raises; load_model() cannot read an exported SavedModel.

Worth memorizing

backend before importKERAS_BACKEND must be set before import keras; it cannot change after
keras.ops, not tf.* or torch.*the one swap that keeps a custom layer portable across backends
built-ins are portable alreadya model of only built-in layers runs on all four backends unchanged
weights go in build()and without get_config() the layer cannot be reloaded
keras.random, not Python randomstateless RNG is what makes custom layers work under JAX tracing
.keras or .h5, never baresave("path") raises; export() is what makes a SavedModel
load_model can't read SavedModeluse keras.layers.TFSMLayer for that
from_logits must matcha softmax layer plus from_logits=True trains on nonsense, silently
loss_fn(y_true, y_pred)true label first
clipnorm is on the optimizernot in the training loop
metrics are statefulreset_state() each epoch or you average over all of training
adapt() on features onlypass ds.map(lambda x, y: x), not the whole dataset
augmentation layers self-disableactive in training, bypassed at inference — no flag needed
keras-preprocessing is archivedfinal release 1.1.2 (2020); in Keras 3 it survives only under _legacy
Tokenizer → TextVectorizationand it belongs inside the model, so the vocabulary can't drift
Sequence → PyDatasetsame two methods, new name, better multiprocessing
on torch, a layer IS an nn.Moduleso torch optimizers, DDP and state_dict() just work
torch backend needs zero_grad()gradients accumulate; forgetting it is the classic bug there
training=True, not model.train()the Keras kwarg is what switches Dropout and BatchNorm
a raw loop is never portableoverride compute_loss or train_step if you want to keep backends open
freeze, train head, then unfreezeand recompile after unfreezing or the change is silently ignored
base(inputs, training=False)freezing alone does not stop BatchNorm updating its running stats
match the backbone's preprocess_inputeach family expects its own input range; the wrong one quietly costs accuracy
decay_steps counts stepsnot epochs — steps_per_epoch * epochs
sample learning rates log-uniformlylinear sampling wastes most tuner trials at the top of the range
rotation_range=20 ≠ RandomRotation(20)degrees became a fraction of a full turn; 20 → about 0.056
install TensorFlow anywaytf.data and several preprocessing layers need it on every backend
on JAX, no side effects in call()a print or an append runs once at trace time and never again
overfit one batch firstif it can't, the bug is the model or the loss — not the data
quantize, then enable_lorathat order is QLoRA; adapters stay full precision
don't load untrusted .h5Keras 3.12.3 was a security release hardening exactly that path