Let's put the concepts we've discussed into practice. In this section, we will build a simple image classifier using both the Keras Sequential and Functional APIs. This exercise will solidify your understanding of how to assemble layers into a working model architecture. We'll use the Fashion MNIST dataset, a common benchmark dataset consisting of 70,000 grayscale images (28x28 pixels) of 10 different clothing categories. Our goal here is purely to construct the model architectures; we will cover compilation, training, and evaluation in the next chapter.
First, 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 classes
The 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:
# 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.
The Sequential API is ideal for models where layers are arranged in a simple linear stack. Our first classifier will follow this pattern:
# 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.
Now, 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 conceptually with Graphviz:
print("\nConceptual Model 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 conceptually 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.
© 2025 ApX Machine Learning