A simple image classifier is constructed using both the Keras Sequential and Functional APIs, solidifying understanding of how to assemble layers into a working model architecture. The Fashion MNIST dataset, a common benchmark dataset consisting of 70,000 grayscale images (28x28 pixels) of 10 different clothing categories, is employed. The primary objective is to construct model architectures; compilation, training, and evaluation are covered in the next chapter.Setup and Data PreparationFirst, we need to import TensorFlow and the necessary Keras components. We'll also load the Fashion MNIST dataset directly using the Keras datasets module.import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers import numpy as np import matplotlib.pyplot as plt print(f"Using TensorFlow version: {tf.__version__}") # Load the Fashion MNIST dataset (x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data() # Display basic information about the dataset print(f"x_train shape: {x_train.shape}") # (60000, 28, 28) print(f"y_train shape: {y_train.shape}") # (60000,) print(f"Number of training samples: {x_train.shape[0]}") print(f"Number of test samples: {x_test.shape[0]}") print(f"Number of classes: {len(np.unique(y_train))}") # 10 classesThe dataset consists of 60,000 training images and 10,000 test images. Each image is a 28x28 pixel grayscale image. The labels (y_train, y_test) are integers from 0 to 9, representing the 10 clothing categories.Before feeding the data into a neural network, it's standard practice to preprocess it. For image data, this typically involves:Normalization: Scaling pixel values from the range [0, 255] to [0, 1]. This helps the optimization algorithm converge more effectively.Reshaping (if necessary): Depending on the input layer, we might need to reshape the data. For a standard Dense layer, we need to flatten the 28x28 image into a 1D vector of 784 pixels.# Normalize pixel values to be between 0 and 1 x_train = x_train.astype("float32") / 255.0 x_test = x_test.astype("float32") / 255.0 # Define class names (optional, for visualization) class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'] # Let's visualize a few images plt.figure(figsize=(10,10)) for i in range(25): plt.subplot(5,5,i+1) plt.xticks([]) plt.yticks([]) plt.grid(False) plt.imshow(x_train[i], cmap=plt.cm.binary) plt.xlabel(class_names[y_train[i]]) plt.show()Sample images from the Fashion MNIST training set with their corresponding labels.Building the Classifier with the Sequential APIThe Sequential API is ideal for models where layers are arranged in a simple linear stack. Our first classifier will follow this pattern:Flatten Layer: Transforms the 2D image format (28x28 pixels) into a 1D array (784 pixels). This prepares the data for the densely connected layers.Dense Layer (Hidden): A fully connected layer with 128 units and ReLU activation. This layer learns intermediate representations from the flattened input.Dense Layer (Output): A fully connected layer with 10 units (one for each class) and Softmax activation. Softmax converts the raw output scores (logits) into probabilities for each class, ensuring they sum to 1.# Define the input shape (excluding batch size) input_shape = (28, 28) num_classes = 10 # Build the model using the Sequential API sequential_model = keras.Sequential( [ keras.Input(shape=input_shape, name="Input_Layer"), # Explicit Input layer recommended layers.Flatten(name="Flatten_Layer"), layers.Dense(128, activation="relu", name="Hidden_Layer_1"), layers.Dense(num_classes, activation="softmax", name="Output_Layer"), ], name="FashionMNIST_Sequential_Classifier" ) # Display the model's architecture print("Sequential Model Summary:") sequential_model.summary()The model.summary() output provides a concise overview of the layers, their output shapes, and the number of trainable parameters. Notice how the Flatten layer changes the shape from (None, 28, 28) to (None, 784), and the subsequent Dense layers process this 1D vector. The None in the shape represents the batch size, which can vary.Building the Classifier with the Functional APINow, let's build the exact same architecture using the Functional API. While overkill for this simple linear model, it demonstrates the alternative syntax, which is essential for more complex designs (e.g., models with multiple inputs/outputs or shared layers).The core idea is to define an input tensor and then call layers as functions, passing tensors between them.# Define the input tensor inputs = keras.Input(shape=input_shape, name="Input_Tensor") # Chain the layers x = layers.Flatten(name="Flatten_Layer")(inputs) x = layers.Dense(128, activation="relu", name="Hidden_Layer_1")(x) outputs = layers.Dense(num_classes, activation="softmax", name="Output_Layer")(x) # Create the Model instance functional_model = keras.Model(inputs=inputs, outputs=outputs, name="FashionMNIST_Functional_Classifier") # Display the model's architecture print("\nFunctional Model Summary:") functional_model.summary() # Optional: Visualize the model structure using Keras utils # (Requires pydot and graphviz to be installed: pip install pydot graphviz) try: keras.utils.plot_model(functional_model, "functional_model_diagram.png", show_shapes=True) print("\nModel diagram saved as functional_model_diagram.png") # Displaying the diagram as Graphviz DOT format for consistency # Note: This is illustrative; plot_model generates an image file. # We can represent the structure with Graphviz: print("\nModel Structure (Graphviz DOT):") dot_string = f""" digraph G {{ rankdir=TB; node [shape=box, style=filled, fillcolor="#a5d8ff"]; Input [label="Input\nshape=(None, 28, 28)"]; Flatten [label="Flatten\nshape=(None, 784)"]; Hidden1 [label="Dense (ReLU)\nunits=128\nshape=(None, 128)"]; Output [label="Dense (Softmax)\nunits={num_classes}\nshape=(None, {num_classes})"]; Input -> Flatten [label=" "]; Flatten -> Hidden1 [label=" "]; Hidden1 -> Output [label=" "]; }} """ # The actual graphviz rendering depends on the environment # For display purposes here, printing the DOT string represents it. # Example of embedding: # ```graphviz # digraph G {{ rankdir=TB; node [shape=box, style=filled, fillcolor="#a5d8ff"]; Input [label="Input\nshape=(None, 28, 28)"]; Flatten [label="Flatten\nshape=(None, 784)"]; Hidden1 [label="Dense (ReLU)\nunits=128\nshape=(None, 128)"]; Output [label="Dense (Softmax)\nunits=10\nshape=(None, 10)"]; Input -> Flatten; Flatten -> Hidden1; Hidden1 -> Output;}} # ``` # print(dot_string) # Printing the raw DOT string for context # A simple Graphviz DOT representation: print("```graphviz") print('digraph G { rankdir=TB; node [shape=box, style="filled", fillcolor="#a5d8ff"]; Input [label="Input\\nshape=(None, 28, 28)"]; Flatten [label="Flatten\\nshape=(None, 784)"]; Hidden1 [label="Dense (ReLU)\\nunits=128\\nshape=(None, 128)", fillcolor="#74c0fc"]; Output [label="Dense (Softmax)\\nunits=10\\nshape=(None, 10)", fillcolor="#4dabf7"]; Input -> Flatten; Flatten -> Hidden1; Hidden1 -> Output;}') print("```") except ImportError: print("\nInstall pydot and graphviz to visualize the model ('pip install pydot graphviz').") Graph of the Functional API model structure. Input flows from top to bottom through the layers.As you can see, the model.summary() output for the Functional API model is identical to the Sequential API model. Both methods resulted in the same architecture with the same number of parameters. The Functional API simply provides a more flexible way to define the connections between layers.We have now successfully defined the architecture for a simple image classifier using both primary Keras APIs. You've seen how to stack layers and understand the role of Flatten, Dense, and activation functions like ReLU and Softmax in this context. The next step, covered in Chapter 4, is to compile these models with a loss function and optimizer, train them on the Fashion MNIST data, and evaluate their performance.