Start hereQuick start · 6 you’ll reach for daily
Target versions · paceVersions
tensorflow ≥ 2.17
keras ≥ 3
python ≥ 3.10
TF 2.16+ ships Keras 3 as tf.keras — multi-backend, fully rewritten,
breaks a few subclassed-model patterns from Keras 2. Model.save("...") writes the new
.keras archive; model.export(...) writes a SavedModel for serving.
tf.estimator is deprecated; the only path is Keras.
macOS GPU goes through tensorflow-metal. This sheet pins to TF 2.17+ / Keras 3.
Install · deviceSetup
# Install — one wheel covers CPU + GPU on Linux / WSL2 (TF 2.16+)
pip install "tensorflow>=2.17" # Linux/WSL2 GPU is detected automatically
pip install "tensorflow-cpu>=2.17" # CPU-only build (smaller)
# macOS — Apple Silicon needs the Metal plugin
pip install tensorflow tensorflow-metal # MPS-backed accelerator
# Keras 3 as a standalone, multi-backend library (TF / JAX / PyTorch)
pip install "keras>=3"
export KERAS_BACKEND="tensorflow" # or "jax" / "torch"
# Confirm the device the runtime sees
python -c "
import tensorflow as tf
print('tf', tf.__version__)
print('GPUs:', tf.config.list_physical_devices('GPU'))
print('Keras', tf.keras.__version__)
"
# Memory growth — almost always what you want on a shared GPU
# import tensorflow as tf
# for g in tf.config.list_physical_devices('GPU'):
# tf.config.experimental.set_memory_growth(g, True)
Where things liveCommon imports
| import tensorflow as tf | Core — tensors, ops, datasets, distribution. |
| import keras / from keras import layers, models, optimizers, losses, metrics | Keras 3 modules. Preferred over the old tf.keras.* deep imports. |
| from keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard, ReduceLROnPlateau | Lifecycle hooks for fit. |
| from keras.preprocessing import image_dataset_from_directory | One-call image dataset builder. |
| from keras import mixed_precision | Mixed-precision policies (FP16 / BF16). |
| import tensorflow_datasets as tfds | Curated public datasets, ready-batched. |
| from tensorflow import data as tf_data | Alias for tf.data — the input pipeline API. |
Create · reshape · opsTensors
| tf.constant([1., 2.], dtype=tf.float32) | Immutable tensor. |
| tf.Variable(initial_value, trainable=True) | Mutable. Used for model weights. |
| tf.zeros((2,3)) / tf.ones(...) / tf.fill(shape, value) | Constant-filled. |
| tf.range(0, 10) / tf.linspace(0., 1., 50) | Range / even spacing. |
| tf.random.normal((B, D), seed=42) | Standard Gaussian. Set seed for reproducibility. |
| tf.cast(x, tf.float32) | Type cast. |
| tf.convert_to_tensor(np_arr) | NumPy → tensor. Implicit in most ops. |
| x.shape / x.dtype / x.device | Inspect. |
| tf.reshape(x, (B, -1)) / tf.transpose(x, [1, 0]) | Reshape / permute. |
| tf.concat([a, b], axis=0) / tf.stack([a, b], axis=0) | Join along existing / new axis. |
| a @ b / tf.matmul(a, b) / tf.einsum("bnd,bdm->bnm", a, b) | Matrix multiply / general contraction. |
| tf.reduce_sum(x, axis=0) / reduce_mean / reduce_max | Reductions. keepdims=True preserves rank. |
Eager autodiffGradientTape
| with tf.GradientTape() as tape: loss = ... | Record ops on watched tensors. |
| grads = tape.gradient(loss, model.trainable_variables) | Compute grads on the recorded graph. |
| optimizer.apply_gradients(zip(grads, model.trainable_variables)) | One optimizer step. |
| with tf.GradientTape(persistent=True): ... | Reuse the tape for multiple gradient() calls. Don’t forget del tape. |
| tape.watch(x) | Force tracking on a constant (Variables are auto-watched). |
| tape.jacobian / batch_jacobian | Higher-order derivatives. |
| @tf.function | Compile a Python fn into a static graph — speed + serializability. |
| tf.function(jit_compile=True) | XLA compilation — bigger speedup on GPU / TPU. |
| tf.stop_gradient(x) | Cut the graph — useful for targets in distillation / RL. |
Sequential · Functional · subclassKeras models
| keras.Sequential([layers.Dense(...), ...]) | Quickest path — linear stack. |
| inputs = keras.Input((D,)); x = layers.Dense(...)(inputs); ... | Functional API — branches + multi-input / output. |
| keras.Model(inputs, outputs, name="...") | Wrap functional graph as a Model. |
| class M(keras.Model): def call(self, x, training=False): ... | Subclassed — max flexibility. |
| model.summary() / keras.utils.plot_model(model, show_shapes=True) | Inspect structure. |
| model.build(input_shape=(None, D)) | Force-build a subclassed model so summary works. |
| model.trainable_variables / non_trainable_variables | Iterate params for custom training loops. |
| model.get_layer("name") / layer.get_weights() / set_weights() | Reach into specific layers. |
| layer.trainable = False | Freeze layers — transfer learning. |
import tensorflow as tf
import keras
from keras import layers
# 1. Sequential — quickest, for plain feedforward stacks
seq = keras.Sequential([
layers.Input((28, 28)),
layers.Flatten(),
layers.Dense(128, activation="gelu"),
layers.Dropout(0.2),
layers.Dense(10), # raw logits — let the loss apply softmax
])
# 2. Functional API — branches, residuals, multi-input
inputs = keras.Input(shape=(28, 28))
x = layers.Flatten()(inputs)
hidden = layers.Dense(256, activation="gelu")(x)
skip = layers.Dense(256, activation="gelu")(hidden) + hidden
outputs = layers.Dense(10)(skip)
func = keras.Model(inputs, outputs, name="mlp_skip")
# 3. Subclassed Model — full Python control (research code)
class MLP(keras.Model):
def __init__(self, hidden=128, n_classes=10):
super().__init__()
self.flat = layers.Flatten()
self.fc1 = layers.Dense(hidden, activation="gelu")
self.drop = layers.Dropout(0.2)
self.fc2 = layers.Dense(n_classes)
def call(self, x, training=False):
x = self.flat(x); x = self.fc1(x)
x = self.drop(x, training=training)
return self.fc2(x)
sub = MLP()
# Inspect — works on all three styles after build
sub.build((None, 28, 28))
sub.summary()
Dense · Conv · normLayers & activations
| layers.Dense(units, activation="gelu", kernel_initializer="he_normal") | Fully connected. |
| layers.Conv2D(filters, kernel_size=3, padding="same", strides=1) | 2-D convolution. |
| layers.Conv2DTranspose(...) / MaxPooling2D / GlobalAveragePooling2D | Upsample / pool. |
| layers.BatchNormalization() / LayerNormalization() | Normalization. |
| layers.Dropout(0.2) / SpatialDropout2D(0.2) | Element / channel dropout. |
| layers.Embedding(input_dim=vocab, output_dim=D, mask_zero=True) | Token embeddings. mask_zero=True propagates padding masks. |
| layers.MultiHeadAttention(num_heads, key_dim)(q, k, v) | Self / cross attention. |
| layers.LSTM / GRU(units, return_sequences=True) | Recurrent. |
| layers.RandomFlip / RandomRotation / RandomCrop / Rescaling | Built-in image augmentation. Apply in the model so they run on GPU. |
| layers.Lambda(fn) | Wrap an arbitrary function into a layer. |
| keras.activations.relu / gelu / silu / softmax(axis=-1) | Functional activations. |
Input pipelinestf.data pipeline
| tf.data.Dataset.from_tensor_slices((X, y)) | Build from in-memory tensors. |
| tf.data.Dataset.list_files("data/*.tfrec") | Glob files. shuffle=True randomizes shard order. |
| tf.data.TFRecordDataset(files, num_parallel_reads=AUTOTUNE) | Read sharded TFRecords in parallel. |
| .map(fn, num_parallel_calls=AUTOTUNE) | Parse / transform in parallel. |
| .batch(N, drop_remainder=True) | Drop partial last batch — required for static shapes. |
| .shuffle(buffer_size, reshuffle_each_iteration=True) | Buffer size should comfortably exceed one epoch’s patterns. |
| .cache() / .cache("/tmp/cache") | Cache after expensive ops. Path version spills to disk. |
| .prefetch(tf.data.AUTOTUNE) | Overlap CPU pipeline with GPU train. Always last step. |
| .repeat() / .take(N) / .skip(N) | Lifecycle helpers. |
| .interleave(map_fn, cycle_length=4) | Read multiple files in round-robin — faster than chained map. |
| tf.data.Options() / dataset.with_options(opts) | Tweak threading / determinism. |
import tensorflow as tf
# From in-memory tensors
ds = tf.data.Dataset.from_tensor_slices((X, y))
# From files — variable-length shards, parallel reads
files = tf.data.Dataset.list_files("data/shard-*.tfrec")
ds = tf.data.TFRecordDataset(files, num_parallel_reads=tf.data.AUTOTUNE)
# Parse one TFRecord example
feature_spec = {
"image": tf.io.FixedLenFeature([], tf.string),
"label": tf.io.FixedLenFeature([], tf.int64),
}
def parse(record):
ex = tf.io.parse_single_example(record, feature_spec)
img = tf.io.decode_jpeg(ex["image"], channels=3)
img = tf.image.resize(img, (224, 224)) / 255.0
return img, ex["label"]
ds = (ds
.shuffle(10_000, reshuffle_each_iteration=True)
.map(parse, num_parallel_calls=tf.data.AUTOTUNE)
.batch(64, drop_remainder=True)
.prefetch(tf.data.AUTOTUNE) # overlap CPU prep with GPU train
)
# Caching — for datasets that fit in memory or on disk
ds_cached = ds.cache().repeat()
# ds_cached = ds.cache("/tmp/cache").repeat() # spill to disk
# Distributed-aware sharding
strategy = tf.distribute.MirroredStrategy()
ds_dist = strategy.experimental_distribute_dataset(ds)
compile · fit · callbacksTraining
| model.compile(optimizer, loss, metrics=[...]) | Wire optimizer + loss + metrics. |
| keras.optimizers.AdamW(learning_rate=3e-4, weight_decay=0.01) | Modern default. Preferred over Adam + manual decay. |
| keras.optimizers.SGD(lr, momentum=0.9, nesterov=True) | Classic. |
| keras.losses.SparseCategoricalCrossentropy(from_logits=True) | Preferred — pass raw logits, no softmax in the last layer. |
| keras.losses.BinaryCrossentropy(from_logits=True) | Binary / multi-label. |
| model.fit(train_ds, validation_data=val_ds, epochs=N, callbacks=[...]) | High-level training loop. |
| model.evaluate(test_ds) / model.predict(x) | Score / inference. |
| EarlyStopping(monitor="val_loss", patience=3, restore_best_weights=True) | Stop when val plateaus. |
| ModelCheckpoint("best.keras", save_best_only=True) | Save best checkpoint by metric. |
| ReduceLROnPlateau(factor=0.5, patience=2) | Drop LR when stuck. |
| TensorBoard(log_dir="./runs") | Live metrics + graph in the TB UI. |
| LearningRateScheduler(fn) / CosineDecay(...) | Custom / cosine schedules. |
train_step · AMPCustom training & mixed precision
| class M(keras.Model): def train_step(self, data): ... | Override the per-batch step but keep fit() + callbacks. |
| @tf.function for d in ds: train_step(d) | Pure custom loop. Compile with @tf.function for speed. |
| tape.gradient(loss, vars) → optimizer.apply_gradients(zip(...)) | Manual optimizer step. |
| keras.mixed_precision.set_global_policy("mixed_float16") | Enable AMP. Compute in FP16, vars in FP32. |
| last layer: dtype="float32" | Keep the head FP32 for numerically stable softmax / loss. |
| opt = mixed_precision.LossScaleOptimizer(opt) | Auto loss-scaling. fit() wires this for you. |
| jit_compile=True in compile() | XLA on the whole graph — bigger gains on TPU + modern GPUs. |
| tf.config.experimental.set_memory_growth(gpu, True) | Don’t pre-allocate the whole GPU. |
Multi-GPU / TPUDistribution strategies
| strategy = tf.distribute.MirroredStrategy() | Single-host multi-GPU. |
| strategy = tf.distribute.MultiWorkerMirroredStrategy() | Multi-host. Set TF_CONFIG env per worker. |
| strategy = tf.distribute.TPUStrategy(resolver) | TPU pods (Cloud / Colab). |
| with strategy.scope(): model = ... ; model.compile(...) | Required — variables created in this scope are mirrored. |
| strategy.experimental_distribute_dataset(ds) | Shard a dataset across replicas. |
| tf.distribute.ParameterServerStrategy(...) | Async PS-worker setups — legacy. |
| tf.distribute.get_strategy() | Current scope — returns default outside with blocks. |
| strategy.run(step_fn, args=(batch,)) | Per-replica step in custom loops. |
.keras · SavedModel · TFLiteSave, load, serve
| model.save("model.keras") | Preferred single-file archive (Keras 3). |
| keras.models.load_model("model.keras") | Reload — weights + architecture + optimizer. |
| model.save_weights("w.weights.h5") / model.load_weights(...) | Weights only. |
| model.export("savedmodel") | TensorFlow SavedModel — for TF Serving / TFLite / TF.js. |
| tf.saved_model.load("savedmodel") | Load a SavedModel as a callable. |
| converter = tf.lite.TFLiteConverter.from_saved_model(...) | Quantize + export for mobile. |
| converter.optimizations = [tf.lite.Optimize.DEFAULT] | Default = post-training int8 quant. |
| tfjs.converters.save_keras_model(model, "tfjs_out") | Browser deployment via @tensorflow/tfjs. |
| tensorflow_model_server --model_base_path=... | REST / gRPC serving for SavedModels. |
MNIST · Keras 3 · SavedModelEnd-to-end · MNIST classifier
tf.data pipeline, mixed-precision Keras 3 model, early stopping + checkpoint callbacks, SavedModel export.
# End-to-end MNIST — tf.data, Keras model, compile/fit, callbacks, SavedModel.
import tensorflow as tf
import keras
from keras import layers
# Mixed-precision: free speedup on Ampere+ / Apple Silicon
keras.mixed_precision.set_global_policy("mixed_float16")
(x_tr, y_tr), (x_te, y_te) = keras.datasets.mnist.load_data()
x_tr = (x_tr / 255.0).astype("float32"); x_te = (x_te / 255.0).astype("float32")
BATCH = 128
train_ds = (tf.data.Dataset.from_tensor_slices((x_tr, y_tr))
.shuffle(60_000).batch(BATCH).prefetch(tf.data.AUTOTUNE))
val_ds = (tf.data.Dataset.from_tensor_slices((x_te, y_te))
.batch(BATCH).prefetch(tf.data.AUTOTUNE))
model = keras.Sequential([
layers.Input((28, 28)),
layers.Flatten(),
layers.Dense(256, activation="gelu"),
layers.Dropout(0.2),
layers.Dense(10, dtype="float32"), # last layer in fp32 — stable softmax
])
model.compile(
optimizer=keras.optimizers.AdamW(learning_rate=3e-4, weight_decay=0.01),
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=["sparse_categorical_accuracy"],
)
cbs = [
keras.callbacks.EarlyStopping(monitor="val_loss", patience=3, restore_best_weights=True),
keras.callbacks.ModelCheckpoint("mnist.keras", save_best_only=True, monitor="val_loss"),
keras.callbacks.TensorBoard(log_dir="./runs"),
]
model.fit(train_ds, validation_data=val_ds, epochs=10, callbacks=cbs)
# Save + reload
model.export("mnist_savedmodel") # for TF Serving / TFLite
reloaded = keras.models.load_model("mnist.keras")
Best practiceGood to know
tf.data pipeline with .prefetch(tf.data.AUTOTUNE).
It overlaps CPU input prep with GPU compute — usually frees 10–30% of wall time for one extra line of code.
from_logits=True, leave the final activation off.
Softmax / sigmoid + cross-entropy as separate ops is numerically unstable. Logits + from_logits=True in the loss is faster and avoids underflow.
set_global_policy("mixed_float16") is a one-line speedup. Keep the output layer FP32 for stable softmax / loss.
Common trapsWatch out for
@tf.function kills performance.
Python-side branching on tensor values or passing different shapes / dtypes triggers a re-trace. Stabilize input signatures with input_signature=[tf.TensorSpec(...)].
build().
Layers materialize on first call, so load_weights before that call fails or aligns wrong. Call model.build(input_shape) or run a dummy forward pass first.
tf.config.experimental.set_memory_growth(gpu, True) — especially on shared dev boxes.
Go deeperSee also
TF* classes.