Full Pipeline: Training and Evaluating the CNN

Introduction

Welcome back to the fourth lesson of JAX in Action: Building an Image Classifier! We've constructed a solid foundation over our previous lessons: efficient data loading with preprocessing pipelines, a sophisticated CNN architecture using Flax, and the essential training and evaluation utilities that form the computational core of our learning system. Now comes the exciting culmination, where we orchestrate all these components into a complete, functioning training pipeline.

In this lesson, we'll build the main orchestration layer that brings everything together in a seamless training and evaluation workflow. We'll implement the full pipeline in our main module, complete with hyperparameter configuration, systematic epoch-based training loops, and comprehensive evaluation cycles that track our model's learning progress. This represents the final piece of our machine learning puzzle, transforming our modular components into a cohesive system capable of training a CNN from random initialization to high accuracy on MNIST digit classification.

Setting Up Hyperparameters

Let's begin our implementation by establishing the fundamental configuration that will support our entire training process:

Python
import jax
import jax.numpy as jnp
import optax
import time
from data_loader import load_mnist_dataset
from model import CNN
from train_utils import train_step, eval_step

def main():
    # Hyperparameters
    NUM_EPOCHS = 5
    BATCH_SIZE = 64
    LEARNING_RATE = 1e-3
    PRNG_SEED = 0

    print(f"Hyperparameters: Epochs={NUM_EPOCHS}, Batch Size={BATCH_SIZE}, LR={LEARNING_RATE}")

This configuration section establishes the hyperparameter foundation for our training process. We set a modest 5 epochs since we're limiting our dataset size to keep execution time reasonable for demonstration purposes. The batch size of 64 provides a good balance between computational efficiency and gradient noise, while the learning rate of 1e-3 represents a proven starting point for Adam optimization. The PRNG seed ensures reproducible results across different runs, which is crucial for debugging and comparison purposes.

Setting up Data Loading

Let's now focus on the data infrastructure:

Python
    # Load datasets
    max_train_samples = 1000
    max_test_samples = 200
    train_data_iter, test_data_iter, dataset_info = load_mnist_dataset(
        batch_size=BATCH_SIZE, 
        max_train_samples=max_train_samples, 
        max_test_samples=max_test_samples
    )
    num_train_batches = max_train_samples // BATCH_SIZE
    num_test_batches = max_test_samples // BATCH_SIZE
    print(f"Loaded MNIST: {num_train_batches} training batches, {num_test_batches} test batches")

The data loading configuration deliberately limits our dataset size to 1,000 training samples and 200 test samples. This limitation serves a practical purpose: keeping our training demonstration fast and focused while still providing meaningful learning dynamics. We calculate the exact number of batches upfront, which will be essential for our training loop logic and ensures we know precisely how many iterations to perform in each epoch.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal