tensorflow 2.0
data augmentation
tfds.load()
machine learning
deep learning

How to apply data augmentation in TensorFlow 2.0 after tfds.load

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

After tfds.load, the right place for data augmentation is the tf.data pipeline or a dedicated augmentation block used only for training. The main goal is to keep preprocessing reproducible and fast while making sure random transformations affect training data only, not validation or test data.

Load the Dataset With Clear Splits

Start by separating training and evaluation data explicitly. That prevents accidental leakage when the pipeline grows later.

python
1import tensorflow as tf
2import tensorflow_datasets as tfds
3
4(train_ds, val_ds), info = tfds.load(
5    "cats_vs_dogs",
6    split=["train[:80%]", "train[80%:]"],
7    as_supervised=True,
8    with_info=True,
9)
10
11print(info.features["label"].num_classes)

Using named or percentage-based splits is clearer than loading one dataset and then trying to remember later which branch should receive augmentation.

Build a Reusable Augmentation Block

In current TensorFlow, Keras preprocessing layers are a clean way to express stochastic augmentation.

python
1augment = tf.keras.Sequential([
2    tf.keras.layers.RandomFlip("horizontal"),
3    tf.keras.layers.RandomRotation(0.08),
4    tf.keras.layers.RandomZoom(0.10),
5])

Now define separate preprocessing functions for train and evaluation:

python
1IMAGE_SIZE = (224, 224)
2
3def preprocess_train(image, label):
4    image = tf.image.resize(image, IMAGE_SIZE)
5    image = tf.cast(image, tf.float32) / 255.0
6    image = augment(image, training=True)
7    return image, label
8
9def preprocess_eval(image, label):
10    image = tf.image.resize(image, IMAGE_SIZE)
11    image = tf.cast(image, tf.float32) / 255.0
12    return image, label

The explicit training=True call matters because it makes the stochastic behavior intentional instead of relying on an ambient training context.

Compose the tf.data Pipeline Deliberately

The pipeline order affects both correctness and performance. A strong default looks like this:

python
1BATCH_SIZE = 32
2AUTOTUNE = tf.data.AUTOTUNE
3
4train_pipeline = (
5    train_ds
6    .shuffle(2000, reshuffle_each_iteration=True)
7    .map(preprocess_train, num_parallel_calls=AUTOTUNE)
8    .batch(BATCH_SIZE)
9    .prefetch(AUTOTUNE)
10)
11
12val_pipeline = (
13    val_ds
14    .map(preprocess_eval, num_parallel_calls=AUTOTUNE)
15    .batch(BATCH_SIZE)
16    .prefetch(AUTOTUNE)
17)

That keeps augmentation in the training path only and overlaps preprocessing with model execution through prefetching.

One subtle but important detail is cache placement. If you cache after random augmentation, you freeze one augmented version of each example. If you cache before augmentation, you keep randomness across epochs while still saving upstream decode or read costs.

Train With the Augmented Pipeline

Once the datasets produce ready-to-train tensors, the model code stays simple.

python
1model = tf.keras.Sequential([
2    tf.keras.layers.Input(shape=(224, 224, 3)),
3    tf.keras.layers.Conv2D(32, 3, activation="relu"),
4    tf.keras.layers.MaxPooling2D(),
5    tf.keras.layers.Conv2D(64, 3, activation="relu"),
6    tf.keras.layers.GlobalAveragePooling2D(),
7    tf.keras.layers.Dense(1, activation="sigmoid"),
8])
9
10model.compile(
11    optimizer="adam",
12    loss="binary_crossentropy",
13    metrics=["accuracy"],
14)
15
16model.fit(train_pipeline, validation_data=val_pipeline, epochs=3)

This separation is valuable because augmentation becomes a data concern rather than a hidden side effect in the training loop.

Reproducibility and Sanity Checks

Random augmentation helps generalization, but it also makes debugging harder. Set a seed when reproducibility matters:

python
tf.keras.utils.set_random_seed(42)

Then run a visual sanity check before long training jobs. Pull one batch and inspect whether the transformed images still preserve the class semantics.

If the model suddenly stops converging after an augmentation change, the issue is often not the optimizer. It is usually that the new random transforms are too aggressive or were accidentally applied to validation data.

Common Pitfalls

The most common mistake is applying random augmentation to validation or test datasets. That makes evaluation noisy and hard to compare.

Another common issue is putting augmentation in the wrong place in the pipeline, especially caching after random transforms and then wondering why every epoch sees the same augmented sample. Developers also often forget to inspect transformed images visually. An augmentation policy can look reasonable in code while destroying the signal in practice.

Summary

  • Apply augmentation after tfds.load inside a dedicated training preprocessing pipeline.
  • Keep validation and test preprocessing deterministic.
  • Use Keras preprocessing layers or tf.image transforms for stochastic train-only augmentation.
  • Compose shuffle, map, batch, and prefetch intentionally.
  • Check cache placement and visualize a few augmented samples before committing to long training runs.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.