One namespace, every engine
keras.ops is the backbone of Keras 3's multi-backend architecture — and the part you actually touch when you write custom math. It implements the full NumPy API plus the neural-network primitives NumPy lacks (convolutions, activations, crossentropy), and dispatches each call to whatever backend is loaded. Deliberately mirroring NumPy's names and signatures is the design choice that makes it almost invisible: if you know NumPy, you already know most of this surface.
The four namespaces you'll reach for
| Namespace | Examples |
|---|---|
keras.ops (NumPy) | matmul, sum, mean, reshape, concatenate, stack, einsum, arange, clip |
keras.ops.nn | softmax, sigmoid, relu, conv, depthwise_conv, binary_crossentropy |
keras.ops.image | resize, crop_images, pad_images, rgb_to_grayscale |
keras.random | normal, uniform, dropout (with SeedGenerator) |
One gotcha worth internalizing now: randomness lives in keras.random and is meant to be driven by a SeedGenerator, not the backend's global RNG. That's what keeps results reproducible and JAX-pure across all three engines.
Porting TF code, op for op
If you're migrating an existing TensorFlow custom layer, most of the work is a near-mechanical rename — same operation, NumPy-flavored name:
| TensorFlow | Keras 3 |
|---|---|
tf.reduce_sum | keras.ops.sum |
tf.concat | keras.ops.concatenate |
tf.range | keras.ops.arange |
tf.reduce_mean | keras.ops.mean |
tf.gather | keras.ops.take |
tf.clip_by_value | keras.ops.clip |
Do the rename once and the layer stops being TF-only — it now runs unchanged on PyTorch and JAX too, which is the principle the next lesson leans on.