pip install tensorflow★CPU + GPU on Linux in one wheel since 2.16.pip install tensorflow[and-cuda]Pulls the matching CUDA libraries with it.tf.__version__ · keras.__version__★Check both. TF 2.16+ ships Keras 3; a stray Keras 2 in the env is the root of most import errors.tf.config.list_physical_devices("GPU")★Empty list means you are on CPU, whatever the wheel name says.pip install tensorboard2.21No longer a dependency as of 2.21 — install it yourself.Windows native GPUsince 2.11Dropped after 2.10. Use WSL2, or accept CPU.Python 3.92.21Support removed in 2.21. Floor is 3.10.pip install tensorflow==2.21.*Pin the minor version. TF changes defaults between minors more than most libraries.tf.sysconfig.get_build_info()The CUDA and cuDNN versions this wheel was actually built against.
import tensorflow as tf★The one import you always need.import keras★Prefer this totf.keras. Same object on TF 2.16+, but it keeps you honest about which Keras you are on.from keras import layers, models, callbacksThe three you reach for constantly.tf.data · tf.image · tf.stringsInput pipeline, image ops, string ops.import tensorflow_hub as hubPart II. Still installable, frozen since Jan 2024.import keras_hubcurrentPart III. The successor, and where new presets land.tf.compat.v1The TF1 attic:Session,placeholder,get_variable. Read-only history.tf.estimator2.16Removed in 2.16. Any tutorial built on Estimators is dead code.keras.ops★The backend-agnostic op namespace. The single most important Keras 3 addition — see card 10.os.environ["KERAS_BACKEND"] = "jax"Must be set beforeimport keras. It cannot be changed afterwards.
tf.constant([[1, 2], [3, 4]])★Immutable. Every op returns a new tensor.tf.zeros((2, 3)) · ones · fill(shape, v)Shape goes in as a tuple or list.tf.random.normal((2, 3)) · uniformAddseed=for repeatability.tf.range(0, 10, 2) · tf.linspace(0, 1, 5)Sequences.tf.convert_to_tensor(arr)★NumPy, lists, scalars — all become tensors.tf.eye(3) · tf.one_hot(idx, depth)Identity; index vector to one-hot matrix.tf.zeros_like(x) · ones_like(x)Match an existing shape and dtype.tf.random.set_seed(0)Global seed. Also seed Python and NumPy separately.tf.random.Generator.from_seed(42)Object-based RNG — the reproducible choice insidetf.function.keras.random.normal(shape, seed=s)Stateless Keras RNG. Use this inside custom layers so they work on every backend.
x.shape · x.dtype · x.ndim★The three questions. Most errors are one of them disagreeing.tf.shape(x)★The dynamic shape, as a tensor. Use this inside@tf.functionwherex.shapemay holdNone.tf.cast(x, tf.float32)★TF will not promote for you. Mixing int32 and float32 raises rather than silently casting.tf.float32 · tf.float16 · tf.bfloat16bfloat16 keeps float32 range — the safer half precision.tf.int32 · tf.int64 · tf.bool · tf.stringYes, string is a real dtype and tensors of them work.x.numpy()Out to NumPy. Fails inside a tracedtf.function.tf.experimental.numpy · x[..., tf.newaxis]NumPy-style API and axis insertion.InvalidArgumentError: cannot compute ... as input #1(zero-based) was expected to be a floatThe dtype-mismatch message. It means: you forgot atf.cast.tf.is_tensor(x)EagerTensor, KerasTensor and ndarray behave differently. Check which one you are holding.tf.int4 · tf.uint4 · tf.int22.21Sub-byte integer dtypes, for quantized weights.
tf.reshape(x, (2, -1))★-1means "work it out". Only one per call.tf.transpose(x, perm=[2, 0, 1])Reorder axes.permlists the new order.tf.expand_dims(x, axis=0) · tf.squeeze(x)★Add or drop a length-1 axis — the batch dimension, usually.tf.concat([a, b], axis=0)★Joins along an existing axis. Rank stays the same.tf.stack([a, b], axis=0)Adds an axis. This is the pair people confuse.tf.split(x, 3, axis=1) · tf.unstack(x)Break apart into a list.tf.tile(x, [2, 1]) · tf.repeat(x, 3)Real copies, unlike broadcasting.keras.layers.Flatten() · Reshape(shape)The layer forms, for inside a model.tf.ensure_shape(x, (None, 128))Asserts and documents a shape in one line — fails here rather than three layers later.tf.pad(x, [[0,0], [1,1]])One[before, after]pair per axis.tf.roll(x, shift=1, axis=0) · tf.reverse(x, [0])Circular shift; flip along axes.
shapes align from the right★Trailing dims must match, or be 1, or be absent. That one rule explains every broadcast.(3,1) + (1,4) → (3,4)Length-1 axes stretch. Nothing is copied until it must be.tf.broadcast_to(x, shape)Make it explicit when you want the full tensor.tf.reduce_sum(x, axis=1, keepdims=True)Keeps rank so the result broadcasts back against the original.pred (N,1) vs label (N,)★famousThe expensive one. These broadcast to (N,N), so your loss quietly averages N² pairs. Nothing raises — the model just never learns.dtype does not broadcastShapes stretch; dtypes do not. int32 + float32 is an error, not a promotion.
x[0] · x[:, 1] · x[..., -1]Standard Python slicing works.tf.reduce_sum(x, axis=1) · reduce_mean · reduce_max★Note thereduce_prefix — TF spells these differently from NumPy.tf.argmax(x, axis=-1)★Turning logits into predicted classes.tf.gather(x, idx) · tf.gather_nd(x, idx)Pick rows by index tensor.tf.boolean_mask(x, mask) · tf.where(c, a, b)Filter by condition; elementwise choose.tf.top_k(x, k=5) · tf.sort · tf.uniqueReturns(values, indices).tf.reduce_any · reduce_all · math.count_nonzeroBoolean reductions.tf.tensor_scatter_nd_update(x, idx, v)Functional update — tensors are immutable, so you get a new one.tf.math.confusion_matrix(y_true, y_pred)Straight to a confusion matrix without leaving TF.tf.searchsorted · tf.math.bincountBinning and counting.
a + b · a * b · a ** 2Operators are elementwise and broadcast.a @ b · tf.matmul(a, b)★Matrix multiply. Batched automatically on leading dims.tf.einsum("bij,bjk->bik", a, b)When the index bookkeeping gets hard, write it down instead.tf.math.exp · log · sqrt · absMost elementwise maths lives undertf.math.tf.clip_by_value(x, lo, hi) · clip_by_normClamping, and gradient clipping.tf.nn.softmax(x, axis=-1) · relu · sigmoidRaw activations, outside of Keras layers.tf.linalg.inv · det · svd · solveThe linear algebra namespace.tf.math.is_nan(x) · tf.debugging.assert_all_finite(x, msg)★First thing to reach for when a loss goes NaN.tf.tensordot(a, b, axes=1) · tf.linalg.matvecContract over chosen axes; matrix × vector.tf.math.l2_normalize(x, axis=-1)Unit-length rows — what cosine similarity needs first.tf.math.unsorted_segment_mean(x, ids, n)Group-by reductions over an index tensor.
tf.ragged.constant([[1,2,3], [4]])★Rows of different lengths, as a first-class tensor. There is no PyTorch equivalent.rt.to_tensor(default_value=0)★Pad out to a rectangle.tf.RaggedTensor.from_tensor(t, lengths=)goes back.rt.row_lengths() · rt.bounding_shape()The true lengths — keep them, your loss mask needs them.keras.layers.Input(shape=(None,), ragged=True)Ragged input straight into a Keras model.tf.sparse.SparseTensor(indices, values, dense_shape)Mostly-zero data stored as coordinates.tf.sparse.to_dense(sp) · tf.sparse.sparse_dense_matmul(sp, d)★Sparse ops live in their own namespace — the normal operators do not work on them.tf.strings.split · lower · regex_replace · joinReal string ops on tensors, running inside the graph.tf.strings.unicode_split(t, "UTF-8") · to_hash_bucket_fastCharacter-level splitting; hashing into a fixed vocabulary.a ragged tensor in a dense-only opMany ops silently accept dense input only. Convert deliberately rather than hoping.
keras.ops.matmul(a, b) · sum · reshape · stack★The NumPy API reimplemented over every backend. Identical call on TensorFlow, JAX or PyTorch.keras.ops.softmax · conv · relu · binary_crossentropyThe neural-network ops NumPy never had.replace tf.* with keras.ops.*★The migration rule, in one line. Do that inside your custom layers and they run on all three backends unchanged.keras.ops.image · linalg · fft · einopsThe other namespaces underneathops.keras.ops.convert_to_tensor(x) · convert_to_numpy(x)The backend-neutral way in and out.on a symbolic tensor, ops return shape onlyCalled on aKerasTensorwhile building a model they infer the output spec and compute nothing.backends: tensorflow · jax · torch · numpy · openvinoWhat Keras 3 will sit on. NumPy and OpenVINO are inference only.a stray tf.* in a custom layer★It still works — on the TensorFlow backend only. That is exactly how portability is lost by accident.
v = tf.Variable(3.0)★Tensors are immutable; Variables are not. Every weight in your model is one.v.assign(5.0) · v.assign_add(1.0)★Notv = 5.0— that rebinds the Python name and throws the Variable away.v.read_value() · v.numpy()Snapshot the current value.tf.Variable(x, trainable=False)For counters and running statistics the optimizer must not touch.model.trainable_variables★What you hand totape.gradientand to the optimizer.model.variables · model.non_trainable_variablesEverything, and the frozen remainder (BatchNorm statistics live here).creating a Variable inside tf.functiontracingRaises on the second call — tracing would create it twice. Build them outside, or guard with a flag.
@tf.function★Traces the Python once per input signature into a graph, then reuses it. This is the whole performance story.it traces, then it runs★Python side effects —print, appending to a list, incrementing a counter — happen only during tracing.tf.print(x)★Prints every call, because it becomes a graph op.print(x)does not.retracing on a new signatureA new shape or dtype means a new trace. Passing Python ints instead of tensors retraces for every value.@tf.function(input_signature=[tf.TensorSpec([None, 32])])Pin the signature to stop runaway retracing.@tf.function(jit_compile=True)XLAHand the graph to XLA for fusion. Often a large win, sometimes a compile-time cost.f.get_concrete_function(spec)The single traced graph, if you want to inspect or export it.a Python loop over a tensorGets unrolled into the graph. Usetf.while_loop, or vectorise.tf.config.run_functions_eagerly(True)Turns tracing off globally so you can use a debugger. Slow — debugging only.
AutoGraph rewrites your if / while★Inside@tf.function, Python control flow on a tensor condition is converted into graph ops for you.tf.cond(pred, true_fn, false_fn)★The explicit form. Both branches are traced, so both must return the same structure and dtypes.tf.while_loop(cond, body, loop_vars)A real loop in the graph rather than an unrolled one.a Python if on a Python value★Baked into the trace as a constant. Change the value and you retrace — or silently keep the old branch.tf.TensorArray(dtype, size=n)How you accumulate results inside a graph loop. A Python list will not survive tracing.tf.map_fn(fn, elems) · tf.vectorized_map(fn, elems)Prefervectorized_map— it batches rather than loops.tf.autograph.to_code(f.python_function)Shows exactly what AutoGraph rewrote your function into.print(f"step {i}") inside the loopFires while tracing only.tf.printis the one that runs every iteration.
with tf.GradientTape() as tape:★Records every op on a watched tensor, then replays it backwards.grads = tape.gradient(loss, model.trainable_variables)★Variables are watched automatically. Constants are not.tape.watch(x)Needed for a plain tensor — for input gradients, saliency, adversarial examples.opt.apply_gradients(zip(grads, vars))Nozero_gradhere. A fresh tape each step means gradients never accumulate, unlike PyTorch.tf.GradientTape(persistent=True)The tape is consumed by the firstgradientcall unless you say this. Thendel tape.nested tapesSecond derivatives: an outer tape over an inner one.tape.stop_recording() · tf.stop_gradient(x)Exclude a region, or cut the gradient path at a tensor.grads come back as None★commonThe chain broke: a.numpy()call, an int dtype, or an op outside the tape.
tf.config.list_physical_devices("GPU")★TF grabs every visible GPU by default.with tf.device("/GPU:0"):Explicit placement. Rarely needed — TF places ops for you.tf.config.experimental.set_memory_growth(gpu, True)★Stops TF pre-allocating the entire GPU at startup. Set it before anything else runs.keras.mixed_precision.set_global_policy("mixed_float16")★One line, roughly 2× on modern GPUs.last layer: Dense(10, dtype="float32")With mixed precision, force the output layer back to float32 for numerical safety.tf.config.set_visible_devices([], "GPU")Hide the GPU to force a CPU run.CUDA_VISIBLE_DEVICES=0The environment-variable equivalent, applied before import.keras.mixed_precision.global_policy()Confirm the policy actually took effect.tf.config.set_logical_device_configuration(gpu, [...])Split one physical GPU into several logical ones — handy for testing a distribution strategy on a single card.mixed precision on an old GPUNo tensor cores means no speedup, and you keep all the numerical risk. Check the compute capability first.
tf.data.Dataset.from_tensor_slices((X, y))★Slices along the first axis — one element per row.tf.data.Dataset.from_generator(gen, output_signature=...)Python generator in. The signature is required.keras.utils.image_dataset_from_directory(path, image_size=(224,224))★Folder of class subfolders straight to a batched Dataset.keras.utils.text_dataset_from_directory(path)Same idea for text files.ds.map(fn, num_parallel_calls=tf.data.AUTOTUNE)Always pass AUTOTUNE — without it, mapping is single-threaded.ds.filter(pred) · ds.take(n) · ds.skip(n)Subset the stream.ds.shuffle(1024).batch(32)Order matters. Shuffle before batch, or you only shuffle whole batches.ds.batch(32, drop_remainder=True)Fixed batch size — needed on TPU and for some static shapes.for x, y in ds.take(1): print(x.shape)How to actually look at one batch.ds.element_specThe shapes and dtypes the pipeline promises.ds.zip((ds_x, ds_y)) · ds.concatenate(other)Pair two pipelines; append one to another.ds.unbatch() · ds.rebatch(n)Undo and redo batching, often before a distribution strategy.tf.data.Dataset.range(10).shuffle(...).take(3)The quickest way to sanity-check pipeline semantics.ds.map() runs in graph mode★Your Python function is traced. Arbitrary Python needstf.py_function, which serialises and kills throughput.
keras.utils.get_file(fname, origin=url, extract=True)★Download, cache and unpack in one call. Returns the local path.tf.io.read_file(path) → tf.io.decode_jpeg(bytes, channels=3)★The canonical image pipeline: list paths, map read, map decode, map resize.tf.io.decode_image · decode_png · encode_jpeg2.21decode_imagehandles JPEG XL as of 2.21.tf.data.Dataset.list_files("data/*/*.jpg", shuffle=True)Glob to a Dataset of paths. Shuffle here, before anything expensive.tf.data.experimental.make_csv_dataset(pattern, batch_size, label_name=)CSV straight to batched dictionaries of columns.tf.data.TextLineDataset(files)One line per element — JSONL and plain text.tf.io.TFRecordWriter + tf.train.ExampleWriting the sharded format. Verbose, and the fastest thing to read back.tf.io.parse_single_example(rec, feature_desc)Reading it back requires the same feature description you wrote with.decoding on the main thread★Withoutnum_parallel_callsyour GPU waits on JPEG decoding. This is the most common invisible bottleneck.
keras.utils.timeseries_dataset_from_array(data, targets, sequence_length=24)★The one-call answer. Sliding windows, batched, with targets aligned for you.sequence_stride= · sampling_rate= · shuffle=Step between windows, gap within a window, and whether to shuffle after windowing.targets=NoneReturns windows only — what you want for autoencoders and self-supervised setups.ds.window(size, shift=1, drop_remainder=True)The manual route. Yields datasets of datasets..flat_map(lambda w: w.batch(size))The step everyone misses — flatten those nested datasets back into tensors.shuffling before windowing★Destroys the time ordering the windows depend on. Window first, then shuffle.scaling on the whole series★Fit the scaler on the training split only, or the future leaks into the past.
ds.prefetch(tf.data.AUTOTUNE)★Last step, always. Overlaps CPU preprocessing with GPU compute.ds.cache()★Caches after the first epoch. Put it after expensive deterministic maps, before random augmentation.ds.cache("/tmp/cache")Cache to disk when the dataset will not fit in RAM.vectorise the map, then batchA map over batches beats a map over elements. Per-element Python is the usual bottleneck.ds.interleave(fn, num_parallel_calls=AUTOTUNE)Read many shards at once instead of one file at a time.tf.data.TFRecordDataset(files)The native sharded format. Worth it once data outgrows memory.tf.data.Dataset.save(ds, path) · load(path)Snapshot a fully preprocessed pipeline.ds.shuffle(buffer_size=100)★quietA small buffer barely shuffles. It samples from a 100-element window, not the dataset.ds.repeat()Rarely needed —fit(epochs=n)already loops. An unbounded repeat plus nosteps_per_epochhangs forever.opts = tf.data.Options(); ds = ds.with_options(opts)Where deterministic ordering and sharding policy are configured.opts.deterministic = FalseLets parallel maps return out of order. Faster, and fine for training.tf.data.experimental.AutoShardPolicy.DATAWhen a distribution strategy cannot shard by file, shard by element.tf.data.Dataset.counter() · ds.enumerate()Step indices alongside the data.
keras.layers.Normalization(); layer.adapt(data)★Learns statistics from data, then travels with the saved model. No train/serve skew.keras.layers.Rescaling(1./255)★The one-liner every image model starts with.keras.layers.Resizing · CenterCropDeterministic image shaping.keras.layers.RandomFlip · RandomRotation · RandomZoom★Augmentation layers. Active in training, bypassed at inference automatically.keras.layers.TextVectorization(max_tokens=20000); .adapt(texts)Builds the vocabulary, then maps strings to int sequences.keras.layers.StringLookup · IntegerLookup · DiscretizationCategorical encoding and binning.keras.layers.CategoryEncoding(output_mode="one_hot")One-hot / multi-hot / count.in the pipeline, or in the model?Inds.mapit runs on CPU in parallel; inside the model it ships with the export. Augmentation in the pipeline, normalization in the model.tf.image.random_flip_left_right · random_brightness · random_cropThe functional augmentation namespace, for use insideds.map.tf.image.resize(x, (224,224), method="bilinear")Resize with an explicit method.preserve_aspect_ratio=when it matters.keras.layers.RandomTranslation · RandomContrast · RandomBrightnessThe rest of the augmentation family.layer.adapt(ds.map(lambda x, y: x))Adapt on features only. Passing the whole (x, y) dataset is a common and confusing failure.
keras.Sequential([layers...])★A straight stack. Simplest, and enough surprisingly often.inp = keras.Input(shape=(32,)); out = Dense(1)(inp)★The Functional API. Any DAG: multiple inputs, skips, shared layers.model = keras.Model(inp, out)Close the Functional graph by naming its ends.class Net(keras.Model): call(self, x, training=None)Subclassing. Full Python control, but no static graph to inspect.model.summary()Shapes and parameter counts. Empty until the model is built.keras.utils.plot_model(model, show_shapes=True)Draws the graph. Functional models only.model.build(input_shape) · model(x)Subclassed and Sequential models are built lazily on first call.pass training through in a subclass★Forget it and Dropout stays on at inference. Functional models handle this for you.keras.Model(inputs=[a, b], outputs=[y1, y2])Multiple inputs and outputs — the reason the Functional API exists.loss={"price": "mse", "cls": "bce"}, loss_weights={...}Per-output losses, keyed by output layer name.model.get_layer("block3").outputTap an intermediate tensor and build a feature-extractor model from it.keras.Model(model.inputs, model.get_layer(n).output)Embeddings out of a trained network, in one line.
keras.layers.Dense(128, activation="relu")★Units first. No input size — it is inferred.Conv2D(32, 3, padding="same", activation="relu")★Channels-last(N, H, W, C)— the opposite of PyTorch.MaxPooling2D(2) · GlobalAveragePooling2D()Downsample; collapse spatial dims before the head.BatchNormalization() · LayerNormalization()BatchNorm keeps running statistics — they are non-trainable variables.Dropout(0.5)Active in training only. Keras switches it for you.Embedding(vocab, dim) · LSTM · GRUSequence basics.MultiHeadAttention(num_heads=8, key_dim=64)Transformer attention, built in.Concatenate() · Add() · Lambda(fn)Merge branches; wrap an arbitrary op.out = (in + 2*pad − k) // stride + 1★The conv size formula.padding="same"keeps the size when stride is 1.SeparableConv2D · DepthwiseConv2D · Conv1DCheaper convolutions; 1-D for sequences and signals.Bidirectional(LSTM(64, return_sequences=True))return_sequencesdecides whether you get every step or only the last.Attention() · GroupNormalization · UnitNormalizationThe rest of the modern block kit.Masking(mask_value=0.) · mask_zero=True on EmbeddingPropagates a mask so padded steps stop influencing the loss.
class MyDense(keras.layers.Layer):★Everything in Keras is a Layer — including Model. Learn this once and the rest opens up.build(self, input_shape)★Where weights are created, once, when the true input shape is finally known.self.w = self.add_weight(shape=..., initializer=..., trainable=True)Registers the variable so Keras tracks, saves and trains it.call(self, inputs, training=None)The forward pass. Accept and honourtrainingif the layer behaves differently while fitting.use keras.ops inside callKeeps the layer working on all three backends.get_config(self) → {...}Without this, the layer cannot be reloaded. Return every constructor argument.@keras.saving.register_keras_serializable()Letsload_modelfind your class without acustom_objectsdict.self.add_loss(value) · self.add_metric(value, name=)Attach a regularization term or a diagnostic from inside the layer.compute_output_shape(self, input_shape)Needed when Keras cannot infer it symbolically.creating weights in __init__★Works only if you already know the input shape.buildexists precisely to avoid that.
Dense(64, kernel_regularizer=keras.regularizers.l2(1e-4))★Weight decay, the layer way.l1,l2andl1_l2are available.bias_regularizer= · activity_regularizer=Penalise the bias, or the layer output itself.Dropout(0.3) · SpatialDropout2D · GaussianNoiseDrop units, whole feature maps, or add noise.keras.losses.CategoricalCrossentropy(label_smoothing=0.1)★Softens hard targets. Cheap, and reliably helps calibration.keras.optimizers.AdamW(clipnorm=1.0) · clipvalue=★Gradient clipping lives on the optimizer, not in the loop. Essential for RNNs and transformers.kernel_initializer="he_normal" · "glorot_uniform"He for ReLU, Glorot for tanh and sigmoid. Glorot is the default.keras.initializers.Constant(v) · bias_initializer=Initialising the output bias to the base rate speeds up imbalanced training.kernel_constraint=keras.constraints.MaxNorm(3)A hard cap applied after every update.regularizer on every layer at onceUsually over-regularises. Add it where the parameters are, then tune one number.
keras.losses.SparseCategoricalCrossentropy(from_logits=True)★Integer labels. The most common classification loss in TF.keras.losses.CategoricalCrossentropy()★One-hot labels. Choosing the wrong one of these two is a rite of passage.from_logits=True ↔ no final activation★pair upMatch them. A softmax layer plusfrom_logits=Truetrains on nonsense — and never errors.keras.losses.BinaryCrossentropy(from_logits=True) · MeanSquaredErrorBinary and regression.keras.optimizers.AdamW(learning_rate=1e-3, weight_decay=0.01)Decoupled decay — the modern default.keras.optimizers.schedules.CosineDecay(lr, steps)Pass a schedule object aslearning_rate.keras.metrics.SparseCategoricalAccuracy · AUC · PrecisionStateful objects: they accumulate across the epoch.loss_fn(y_true, y_pred)True first. TF orders the arguments the opposite way round from most libraries.keras.losses.Huber() · CosineSimilarity() · KLDivergence()Robust regression; embedding similarity; distribution matching.def my_loss(y_true, y_pred): return keras.ops.mean(...)A plain function is a valid loss. Usekeras.opsand it stays portable.class M(keras.metrics.Metric): update_state / result / reset_stateThe three methods a stateful custom metric must implement.weighted_metrics=[...] · sample_weight=Per-sample weighting, honoured by both metrics and loss.keras.optimizers.Lion · Adafactor · SGD(momentum=0.9, nesterov=True)Memory-lean alternatives, and the classic that still wins on vision.opt.exclude_from_weight_decay(var_names=["bias", "scale"])Never decay biases or norm scales. Standard practice for transformers.
model.compile(optimizer=, loss=, metrics=[])★Wires the three together. Strings work:"adam","mse".model.fit(ds, epochs=10, validation_data=val_ds)★Handles batching, metrics, callbacks and the progress bar.model.fit(X, y, batch_size=32, validation_split=0.2)★The NumPy path.validation_splitworks only for arrays, not Datasets.history = model.fit(...); history.history["val_loss"]Everything for your learning curves is in that dict.model.evaluate(test_ds) · model.predict(x)predictreturns NumPy; callingmodel(x)returns a tensor and is faster for one batch.class_weight={0: 1.0, 1: 5.0}Imbalance, without touching the data.model.compile(..., jit_compile=True)fastXLA for the whole training step.steps_per_epoch=nRequired if your Dataset repeats forever.model.fit(..., verbose=2)One line per epoch — what you want in a log file rather than a terminal.initial_epoch=nResume a run and keep the epoch numbering honest.validation_freq=5 · validation_steps=nValidate less often, or on a subset, when it dominates the epoch.model.predict(ds, batch_size=) vs model(x, training=False)predictloops a whole dataset; calling the model is faster for a single batch.
keras.callbacks.ModelCheckpoint("best.keras", save_best_only=True)★Keep the best epoch, not the last one.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True)★Setrestore_best_weights— without it you keep the worse, later weights.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=3)Cut the learning rate when validation stalls.keras.callbacks.TensorBoard(log_dir="logs")Thentensorboard --logdir logs. Remember to pip install it on 2.21+.keras.callbacks.CSVLogger · LearningRateScheduler · BackupAndRestoreLog to file; per-epoch LR; resume after a crash.class Cb(keras.callbacks.Callback): on_epoch_end(self, epoch, logs)Your own. Alsoon_train_batch_endand friends.monitor="val_loss", mode="min"★Silently does nothing if the monitored name is not inlogs. Check your spelling.self.model.stop_training = TrueHow a custom callback ends the run early.keras.callbacks.TerminateOnNaN() · ProgbarLogger()Bail out on a NaN loss instead of burning an hour.keras.callbacks.LambdaCallback(on_epoch_end=fn)A one-off hook without writing a class.
base = keras.applications.EfficientNetV2B0(include_top=False, weights="imagenet")★Start from a trained backbone; drop its classifier head.base.trainable = False★Step one: freeze everything. Train only the new head first, or large random-head gradients wreck the pretrained weights.x = base(inputs, training=False)subtlePasstraining=Falseas well. Freezing does not stop BatchNorm updating its running statistics; this does.x = GlobalAveragePooling2D()(x); out = Dense(n)(x)The new head. Train it to convergence at a normal learning rate.base.trainable = True; model.compile(optimizer=AdamW(1e-5))Step two: unfreeze and recompile. A very small learning rate — typically 10–100× lower.forgetting to recompile after unfreezing★silentThe change simply does not take effect.trainableis read at compile time.for layer in base.layers[:-20]: layer.trainable = FalsePartial unfreezing — the last few blocks only, when data is scarce.match the preprocessing to the backboneEach family expects its own input range. The wrongpreprocess_inputcosts accuracy silently.
@tf.function def train_step(x, y):★Decorate the step, not the epoch loop. That single line is most of the speed.with tf.GradientTape() as tape: loss = loss_fn(y, model(x, training=True))★training=Truematters: it switches Dropout and BatchNorm on.grads = tape.gradient(loss, model.trainable_variables)★One call gives you every gradient.opt.apply_gradients(zip(grads, model.trainable_variables))And the step is done. No zeroing required.metric.update_state(y, pred) → metric.result() → metric.reset_state()Keras metrics are stateful. Reset them each epoch or they average across all of training.model.train_step(self, data)The middle path: override just the step and keepfit()with all its callbacks.tf.function(reduce_retracing=True)For a last batch of a different size.keras.Model.compute_loss(self, x, y, y_pred, sample_weight)Override this instead oftrain_stepand it stays backend-agnostic.tape.gradient(loss, vars, unconnected_gradients="zero")Turns thoseNonegradients into zeros when the disconnection is expected.@tf.custom_gradientDefine the backward pass yourself — straight-through estimators, gradient reversal.accum += grads; apply every k stepsGradient accumulation: a larger effective batch than the GPU can hold.model.test_step(self, data) · predict_stepThe evaluation and inference counterparts oftrain_step.
model.save("m.keras")★changedThe extension is mandatory in Keras 3. A bare directory path raisesValueError: Invalid filepath extension.keras.models.load_model("m.keras")★Architecture, weights, optimizer state — ready to resume.model.export("saved_model/")Keras 3This is how you get a SavedModel now. For TF Serving, LiteRT, anything outside Python.model.save_weights("m.weights.h5")Weights only. That exact suffix is required.keras.layers.TFSMLayer(path, call_endpoint="serving_default")The bridge back. Wraps any TF SavedModel as a Keras 3 layer — inference only.tf.train.Checkpoint(model=m, optimizer=o).save(path)The low-level, object-graph checkpoint. What long training runs use.custom_objects={...} · @keras.saving.register_keras_serializable()Custom layers and losses need registering or reloading fails.keras.models.load_model("saved_model/")★Keras 3Cannot load a SavedModel. Keras 3 reads only.kerasand.h5. UseTFSMLayer.model.export(path, format="tf_saved_model")Explicit export format.ExportArchivegives finer control over signatures.keras.saving.save_model · load_model(..., compile=False)Skip restoring the optimizer when you only need to predict.tf.train.latest_checkpoint(dir) · ckpt.restore(path).expect_partial()Silences the warnings when you deliberately restore weights only.model.get_weights() · model.set_weights(w)Plain NumPy arrays — useful for weight averaging and manual surgery.
strategy = tf.distribute.MirroredStrategy()★Multi-GPU on one machine. The easiest real speedup there is.with strategy.scope(): model = build(); model.compile(...)★Build and compile inside the scope. Outside it, the variables are not mirrored.tf.distribute.MultiWorkerMirroredStrategy() · TPUStrategy()Many machines; TPU pods.global batch = per-replica × replicasKeras splits the batch you pass across replicas. Scale the learning rate accordingly.docker run -p 8501:8501 tensorflow/servingPoint it at an exported SavedModel directory and you have a REST endpoint.pip install ai-edge-litert★tf.litetf.liteis deprecated and is being removed from the TF package. On-device inference moved to the standalone LiteRT project.tf.saved_model.load(path) · obj.signatures["serving_default"]The pure-TF loader, no Keras involved.saved_model_cli show --dir path --allInspect signatures before you try to serve them.model.quantize("int8")Keras 3Post-training quantization in one call, right on the Keras model.keras.distribution.DataParallel() · ModelParallel()The Keras 3, backend-agnostic distribution API — the portable alternative totf.distribute.options = tf.saved_model.SaveOptions(experimental_io_device=...)Needed when saving from a TPU worker.warm up before you benchmarkThe first call traces and compiles. Timing it measures the compiler, not the model.
print(x.shape, x.dtype, tf.reduce_mean(x))★The three questions. Nearly every TF error is one of them disagreeing.overfit a single batch firstIf the model cannot drive one batch to near-zero loss, the bug is in the model or the loss — not the data.tf.config.run_functions_eagerly(True)Disables tracing so you can set a breakpoint inside atf.function.model.compile(..., run_eagerly=True)Same idea, scoped to one model.tf.random.set_seed(s); random.seed(s); np.random.seed(s)Three separate RNGs. Seeding one is the usual mistake.tf.config.experimental.enable_op_determinism()Makes GPU kernels deterministic. Slower, and worth it while chasing a bug.TF_CPP_MIN_LOG_LEVEL=2Silence the informational C++ chatter at import.loss is NaN from step one★Learning rate too high, an unscaled input, or alog(0). Check the inputs before the architecture.val accuracy stuck at chance★Almost always thefrom_logits/ final-activation mismatch, or labels that do not line up with predictions.tf.debugging.enable_check_numerics()Raises at the exact op that first produced a NaN or infinity, with a stack trace.tf.debugging.assert_shapes([(x, ("N", "C")), ...])Named-dimension assertions that read like documentation.ValueError: as_list() is not defined on an unknown TensorShapeA dynamic shape reached code expecting a static one. Usetf.shape(x).loss decreases, val loss does not moveCheck the validation pipeline separately — a mismatched preprocessing path is more common than overfitting.
pip install tensorboard★2.21Not installed with TF as of 2.21. The callback fails with an import error if you skip this.tensorboard --logdir logs --bind_allThen open port 6006.--bind_allexposes it beyond localhost.keras.callbacks.TensorBoard(log_dir, histogram_freq=1)Adds weight and gradient histograms, at some cost per epoch.profile_batch=(10, 20)★Profiles those steps and shows exactly where time goes — usually the input pipeline.the Trace Viewer★If the GPU timeline has gaps, the problem istf.data, not the model.tf.summary.scalar(name, v, step=i)Manual logging from a custom loop, inside acreate_file_writercontext.keras.callbacks.TensorBoard(embeddings_freq=1)The embedding projector, for inspecting learned representations.