TensorFlow Image Classification

Image classification assigns a label to an entire image. A photo of a cat gets the label "cat." A photo of a dog gets "dog." This task is the most common starting point for CNN projects and the benchmark used to measure progress in computer vision research. This topic builds a complete image classifier from data loading through training, evaluation, and prediction — applying every CNN concept covered so far.

Project: Classifying Flowers Into 5 Categories

TensorFlow provides a Flowers dataset with 3,670 images across five categories: daisy, dandelion, roses, sunflowers, and tulips. This is a realistic small-dataset classification problem.

Step 1 — Load and Explore the Dataset

import tensorflow as tf
import pathlib

# Download the dataset
dataset_url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz'
data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url,
                                    untar=True)
data_dir = pathlib.Path(data_dir)

# Count images
image_count = len(list(data_dir.glob('*/*.jpg')))
print(f"Total images: {image_count}")   # 3,670

# Class names from folder names
class_names = sorted([item.name for item in data_dir.glob('*')
                       if item.is_dir()])
print(class_names)   # ['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']

Step 2 — Build the Data Pipeline

IMAGE_SIZE = (180, 180)
BATCH_SIZE = 32
AUTOTUNE = tf.data.AUTOTUNE

train_ds = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset='training',
    seed=42,
    image_size=IMAGE_SIZE,
    batch_size=BATCH_SIZE
)

val_ds = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset='validation',
    seed=42,
    image_size=IMAGE_SIZE,
    batch_size=BATCH_SIZE
)

# Optimize pipeline
train_ds = train_ds.cache().shuffle(1000).prefetch(AUTOTUNE)
val_ds   = val_ds.cache().prefetch(AUTOTUNE)

Step 3 — Build the CNN Model

import tensorflow as tf

NUM_CLASSES = 5

model = tf.keras.Sequential([
    # Augmentation (training only)
    tf.keras.layers.RandomFlip('horizontal', input_shape=(180, 180, 3)),
    tf.keras.layers.RandomRotation(0.1),
    tf.keras.layers.RandomZoom(0.1),

    # Normalization
    tf.keras.layers.Rescaling(1./255),

    # Block 1: 32 filters
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),

    # Block 2: 64 filters
    tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),

    # Block 3: 128 filters
    tf.keras.layers.Conv2D(128, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),

    # Block 4: 256 filters
    tf.keras.layers.Conv2D(256, 3, padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(),

    # Classifier head
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
])

model.summary()

Step 4 — Compile and Train

model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

history = model.fit(
    train_ds,
    epochs=30,
    validation_data=val_ds,
    callbacks=[
        tf.keras.callbacks.EarlyStopping(
            monitor='val_accuracy',
            patience=5,
            restore_best_weights=True
        )
    ]
)

print(f"Best validation accuracy: {max(history.history['val_accuracy']):.2%}")

Step 5 — Evaluate on the Full Validation Set

loss, accuracy = model.evaluate(val_ds)
print(f"Final validation loss:     {loss:.4f}")
print(f"Final validation accuracy: {accuracy:.2%}")

Step 6 — Predict on New Images

import numpy as np

def predict_image(model, image_path, class_names):
    # Load and preprocess a single image
    img = tf.keras.utils.load_img(image_path, target_size=(180, 180))
    img_array = tf.keras.utils.img_to_array(img)
    img_array = tf.expand_dims(img_array, 0)   # Add batch dimension

    predictions = model.predict(img_array, verbose=0)
    predicted_class = class_names[np.argmax(predictions[0])]
    confidence = np.max(predictions[0])

    print(f"Predicted: {predicted_class} ({confidence:.1%} confidence)")
    print(f"All scores: {dict(zip(class_names, predictions[0].round(3)))}")

predict_image(model, 'my_flower.jpg', class_names)

How the Model Sees the Image

Input Image: (180, 180, 3) — color photo of a flower

After Aug + Rescaling:  (180, 180, 3) — pixel values 0–1
After Conv2D(32)+Pool:  (90, 90, 32)  — 32 feature maps, half size
After Conv2D(64)+Pool:  (45, 45, 64)  — 64 feature maps, half size again
After Conv2D(128)+Pool: (22, 22, 128) — 128 maps, quarter size
After Conv2D(256)+Pool: (11, 11, 256) — 256 maps, eighth size
After Flatten:          (30976,)      — all features in a 1D vector
After Dense(128):       (128,)        — 128 abstract features
After Dense(5)+Softmax: (5,)          — one probability per class

[daisy:0.05, dandelion:0.03, roses:0.87, sunflowers:0.03, tulips:0.02]
Prediction → "roses"

Reading the Training History

history.history.keys()
# ['loss', 'accuracy', 'val_loss', 'val_accuracy']

# Identify overfitting
train_acc = history.history['accuracy']
val_acc   = history.history['val_accuracy']

for epoch, (t, v) in enumerate(zip(train_acc, val_acc), 1):
    gap = t - v
    status = 'OVERFITTING' if gap > 0.15 else 'OK'
    print(f"Epoch {epoch:02d}: train={t:.3f} val={v:.3f} gap={gap:.3f} {status}")

Common Classification Mistakes and Fixes

Symptom                               Fix
────────────────────────────────────────────────────────────────────
val_accuracy stuck at 1/N_CLASSES     Wrong loss function; use
(random guessing)                     sparse_categorical_crossentropy

Training accuracy high,               Add more Dropout, add augmentation,
val accuracy much lower               reduce model size, gather more data

Loss is NaN from epoch 1              Learning rate too high; try 1e-4 or 1e-5

Accuracy improves then                Use EarlyStopping + restore_best_weights
drops after many epochs

All predictions same class            Check class imbalance; add class_weight
────────────────────────────────────────────────────────────────────

Improving Accuracy Further

  • Use transfer learning with MobileNetV2 or EfficientNet (covered in Topic 34–37)
  • Add more augmentation: RandomBrightness, RandomContrast
  • Use label smoothing: loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1)
  • Try learning rate warmup and cosine decay schedule
  • Collect more data for the weakest-performing class

Building a complete image classifier from raw images to accurate predictions is the core skill of computer vision engineering. The architecture pattern here — augmentation, rescaling, stacked Conv+Pool blocks, Dropout, Dense head — appears in virtually every practical image classification project. The next section shifts to Recurrent Neural Networks, which handle sequential data like text, audio, and time series.

Leave a Comment

Your email address will not be published. Required fields are marked *