DEEP LEARNING LESSON 10 CONVOLUTIONAL NEURAL NETWORKS

Build a CNN With Python

Now let's build a real Convolutional Neural Network using Python, TensorFlow, and Keras. We will train the CNN to recognize handwritten digits from 0 to 9.

What Are We Building?

We will build a CNN that takes an image of a handwritten digit and predicts which number it represents.

Input Image
     ↓
CNN
     ↓
Feature Extraction
     ↓
Classification
     ↓
Prediction

Example:

Image of "7"
     ↓
CNN
     ↓
Prediction = 7

The model will learn from thousands of example images instead of us manually telling it what a "7" looks like.

The MNIST Dataset

MNIST is a dataset containing handwritten digits from 0 to 9.

Classes:

0
1
2
3
4
5
6
7
8
9

Each image is grayscale and has a size of 28 × 28 pixels.

Image Shape

28 × 28 × 1

28  → height
28  → width
1   → grayscale channel

We will use the training images to teach the CNN and the test images to check how well it learned.

Install TensorFlow

If TensorFlow is not installed, install it with:

pip install tensorflow

Then verify the installation:

python -c "import tensorflow as tf; print(tf.__version__)"

Step 1 — Import TensorFlow

import tensorflow as tf

TensorFlow provides the tools required to create and train the neural network.

Keras is included inside TensorFlow and provides a simpler interface for building neural networks.

Step 2 — Load the Dataset

import tensorflow as tf

(x_train, y_train), (x_test, y_test) = (
    tf.keras.datasets.mnist.load_data()
)

This loads the MNIST dataset.

x_train
→ Training images

y_train
→ Correct labels for training images

x_test
→ Test images

y_test
→ Correct labels for test images

For example:

x_train[0]
→ Image

y_train[0]
→ 5

This means the first training image represents the digit 5.

Step 3 — Normalize the Images

Pixel values in the MNIST images range from 0 to 255.

0   → Black
255 → White

Neural networks generally train more effectively when the input values are scaled to a smaller range.

x_train = x_train / 255.0
x_test = x_test / 255.0

Now the values are approximately between 0 and 1.

Before:

0 → 255


After:

0 → 1

Step 4 — Add the Channel Dimension

MNIST images initially have the shape:

28 × 28

A Conv2D layer expects an image shape that includes the channel dimension.

28 × 28 × 1

We can add that dimension using:

x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

Now:

x_train shape:

(number of images, 28, 28, 1)

Step 5 — Create the CNN

Now we build the actual CNN.

model = tf.keras.Sequential([

    tf.keras.layers.Conv2D(
        32,
        (3, 3),
        activation='relu',
        input_shape=(28, 28, 1)
    ),

    tf.keras.layers.MaxPooling2D(
        (2, 2)
    ),

    tf.keras.layers.Conv2D(
        64,
        (3, 3),
        activation='relu'
    ),

    tf.keras.layers.MaxPooling2D(
        (2, 2)
    ),

    tf.keras.layers.Flatten(),

    tf.keras.layers.Dense(
        128,
        activation='relu'
    ),

    tf.keras.layers.Dense(
        10,
        activation='softmax'
    )
])

This is the complete CNN architecture.

28 × 28 × 1
     ↓
Conv2D
     ↓
MaxPooling
     ↓
Conv2D
     ↓
MaxPooling
     ↓
Flatten
     ↓
Dense
     ↓
Dense
     ↓
10 Predictions

Understanding the First Convolution Layer

tf.keras.layers.Conv2D(
    32,
    (3, 3),
    activation='relu',
    input_shape=(28, 28, 1)
)

The important parts are:

32
→ Number of filters

(3, 3)
→ Kernel size

relu
→ Activation function

(28, 28, 1)
→ Input image shape

The 32 filters can learn different visual patterns.

Input Image
     ↓
┌─────────────────┐
│ Filter 1        │ → Feature Map
│ Filter 2        │ → Feature Map
│ Filter 3        │ → Feature Map
│ ...             │
│ Filter 32       │ → Feature Map
└─────────────────┘

During training, the network learns useful filter values automatically.

Understanding Max Pooling

tf.keras.layers.MaxPooling2D(
    (2, 2)
)

This uses a 2 × 2 window and keeps the largest value.

Feature Map

1   3
5   8

        ↓

Max Pooling

        ↓

8

This reduces the spatial dimensions of the feature maps.

Why Add a Second Convolution Layer?

The first convolution layer can learn simpler patterns. A deeper convolution layer receives those learned representations and can learn more complex patterns.

Image
 ↓
First Conv Layer
 ↓
Simple patterns
 ↓
Second Conv Layer
 ↓
More complex patterns
 ↓
Classification

The second layer uses 64 filters:

tf.keras.layers.Conv2D(
    64,
    (3, 3),
    activation='relu'
)

The number 64 means this layer has 64 learnable filters.

Step 6 — Flatten the Feature Maps

tf.keras.layers.Flatten()

At this point, the CNN has extracted useful image features. The Flatten layer converts the feature maps into one long vector.

Feature Maps
     ↓
2D / 3D representation
     ↓
Flatten
     ↓
One long vector

This vector can now be passed to the dense layers.

Step 7 — Dense Layer

tf.keras.layers.Dense(
    128,
    activation='relu'
)

This dense layer contains 128 neurons.

It combines the features extracted by the convolutional layers to help determine which digit is present.

Extracted Features
        ↓
Dense Layer
        ↓
Combine Information
        ↓
Prepare for Prediction

Step 8 — Output Layer

tf.keras.layers.Dense(
    10,
    activation='softmax'
)

Why 10 neurons?

Because MNIST has 10 possible classes:

0  1  2  3  4
5  6  7  8  9

Softmax converts the outputs into probabilities that sum to approximately 1.

Example:

0 → 0.01
1 → 0.02
2 → 0.03
3 → 0.01
4 → 0.01
5 → 0.02
6 → 0.01
7 → 0.86
8 → 0.02
9 → 0.01

Highest probability = 7

Prediction = 7

Step 9 — Compile the Model

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

Compiling tells Keras how the model should learn.

optimizer
→ How weights are updated

loss
→ How prediction error is measured

metrics
→ What we want to monitor

Here we use the Adam optimizer and sparse categorical cross-entropy loss because the labels are integer class values such as 0, 1, 2, and so on.

Step 10 — Train the CNN

history = model.fit(
    x_train,
    y_train,
    epochs=5,
    batch_size=64,
    validation_split=0.1
)

The model now learns from the training data.

x_train
→ Training images

y_train
→ Correct answers

epochs=5
→ Go through the training data 5 times

batch_size=64
→ Process 64 images at a time

validation_split=0.1
→ Keep 10% of training data for validation

What Happens During Training?

The training process follows the same basic cycle we learned earlier.

Input Image
     ↓
Forward Pass
     ↓
Prediction
     ↓
Calculate Loss
     ↓
Backpropagation
     ↓
Update Weights
     ↓
Next Batch

This process repeats many times.

Over time, the filters and other weights are adjusted so that the model's predictions become more accurate.

Step 11 — Evaluate the CNN

test_loss, test_accuracy = model.evaluate(
    x_test,
    y_test
)

print("Test Accuracy:", test_accuracy)

The model has not used the test data for training. Therefore, test data gives us a better idea of how well the trained model performs on unseen examples.

Training Data
     ↓
Learn

Test Data
     ↓
Evaluate

Step 12 — Make a Prediction

predictions = model.predict(
    x_test[:5]
)

predicted_classes = tf.argmax(
    predictions,
    axis=1
)

print(predicted_classes.numpy())

The model produces probabilities for each digit.

Model output:

[
    [0.01, 0.02, 0.03, ..., 0.86, ...]
]

        ↓

Highest probability

        ↓

Predicted class = 7

Complete CNN Python Code

Here is the complete example in one place:

import tensorflow as tf


# 1. Load MNIST dataset
(x_train, y_train), (x_test, y_test) = (
    tf.keras.datasets.mnist.load_data()
)


# 2. Normalize pixel values
x_train = x_train / 255.0
x_test = x_test / 255.0


# 3. Add channel dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]


# 4. Create CNN
model = tf.keras.Sequential([

    tf.keras.layers.Conv2D(
        32,
        (3, 3),
        activation='relu',
        input_shape=(28, 28, 1)
    ),

    tf.keras.layers.MaxPooling2D(
        (2, 2)
    ),

    tf.keras.layers.Conv2D(
        64,
        (3, 3),
        activation='relu'
    ),

    tf.keras.layers.MaxPooling2D(
        (2, 2)
    ),

    tf.keras.layers.Flatten(),

    tf.keras.layers.Dense(
        128,
        activation='relu'
    ),

    tf.keras.layers.Dense(
        10,
        activation='softmax'
    )
])


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


# 6. Train
history = model.fit(
    x_train,
    y_train,
    epochs=5,
    batch_size=64,
    validation_split=0.1
)


# 7. Evaluate
test_loss, test_accuracy = model.evaluate(
    x_test,
    y_test
)

print("Test Accuracy:", test_accuracy)


# 8. Make predictions
predictions = model.predict(x_test[:5])

predicted_classes = tf.argmax(
    predictions,
    axis=1
)

print("Predictions:")
print(predicted_classes.numpy())

print("Actual Labels:")
print(y_test[:5])

Understand the Complete Code Flow

1. Load Data
       ↓
2. Normalize Images
       ↓
3. Add Channel Dimension
       ↓
4. Create CNN
       ↓
5. Compile Model
       ↓
6. Train Model
       ↓
7. Evaluate Model
       ↓
8. Make Predictions

Each step has one clear responsibility.

Load Data
→ Get images and labels

Normalize
→ Convert pixel values to 0–1

Create CNN
→ Define the architecture

Compile
→ Define how learning happens

Train
→ Learn weights from training data

Evaluate
→ Test performance

Predict
→ Use the trained CNN on new images

Our CNN Architecture


                MNIST Image
                 28 × 28 × 1
                       │
                       ▼
                ┌──────────────┐
                │ Conv2D       │
                │ 32 filters   │
                │ 3 × 3        │
                └──────────────┘
                       │
                       ▼
                ┌──────────────┐
                │ Max Pooling  │
                │ 2 × 2        │
                └──────────────┘
                       │
                       ▼
                ┌──────────────┐
                │ Conv2D       │
                │ 64 filters   │
                │ 3 × 3        │
                └──────────────┘
                       │
                       ▼
                ┌──────────────┐
                │ Max Pooling  │
                │ 2 × 2        │
                └──────────────┘
                       │
                       ▼
                ┌──────────────┐
                │ Flatten      │
                └──────────────┘
                       │
                       ▼
                ┌──────────────┐
                │ Dense        │
                │ 128 neurons  │
                └──────────────┘
                       │
                       ▼
                ┌──────────────┐
                │ Dense        │
                │ 10 classes   │
                └──────────────┘
                       │
                       ▼
                Prediction 0–9

Simple Example — Predicting 7

Imagine we give the trained CNN an image containing a handwritten 7.

Handwritten "7"
       ↓
28 × 28 Image
       ↓
Convolution
       ↓
Detect edges
       ↓
Pooling
       ↓
Keep important features
       ↓
Second Convolution
       ↓
Detect more complex shapes
       ↓
Pooling
       ↓
Flatten
       ↓
Dense Layer
       ↓
Output probabilities

0 → 0.01
1 → 0.01
2 → 0.02
3 → 0.01
4 → 0.01
5 → 0.02
6 → 0.01
7 → 0.89
8 → 0.01
9 → 0.01

       ↓

Prediction = 7

What Does the CNN Actually Learn?

This is one of the most important concepts. We do not manually create the 32 or 64 filters.

We only define how many filters the layer should have. During training, the network learns the filter weights.

We define:

"Use 32 filters."

        ↓

Training

        ↓

CNN learns filter weights

        ↓

Filters become useful feature detectors

For example, some filters may become sensitive to edges or other useful patterns. We should not assume that every filter learns one specific human-interpretable feature; the learned representations depend on the data and training process.

Two Important Examples

Example 1 — Training:

Image
 ↓
CNN Prediction = 3
 ↓
Correct Label = 7
 ↓
Loss is calculated
 ↓
Backpropagation
 ↓
Weights updated
 ↓
CNN becomes slightly better

Example 2 — Prediction after training:

New Image
 ↓
Trained CNN
 ↓
Probability for each digit
 ↓
Highest probability = 7
 ↓
Prediction = 7

The Big Picture

              IMAGE
                ↓
        ┌───────────────┐
        │ Convolution   │
        └───────────────┘
                ↓
          Feature Maps
                ↓
        ┌───────────────┐
        │ Max Pooling   │
        └───────────────┘
                ↓
        Smaller Features
                ↓
        ┌───────────────┐
        │ Convolution   │
        └───────────────┘
                ↓
        More Complex
           Features
                ↓
        ┌───────────────┐
        │ Max Pooling   │
        └───────────────┘
                ↓
        ┌───────────────┐
        │ Flatten       │
        └───────────────┘
                ↓
        ┌───────────────┐
        │ Dense Layer   │
        └───────────────┘
                ↓
        ┌───────────────┐
        │ Output        │
        └───────────────┘
                ↓
            PREDICTION

Easy Way to Remember

LOAD
→ Get the images

NORMALIZE
→ Scale the pixels

CONVOLUTION
→ Find visual patterns

POOLING
→ Reduce the feature maps

FLATTEN
→ Convert features to a vector

DENSE
→ Combine the features

OUTPUT
→ Predict the class

TRAIN
→ Learn the weights

EVALUATE
→ Check performance

PREDICT
→ Use the trained model

The entire CNN workflow can be remembered as:

Image
 ↓
Feature Extraction
 ↓
Classification
 ↓
Prediction
QUICK CHECK

Check Your Understanding

What dataset did we use?
MNIST, which contains handwritten digits from 0 to 9.

Why do we divide the pixel values by 255?
To scale the pixel values from approximately 0–255 into the range 0–1.

Why does the output layer have 10 neurons?
Because there are 10 possible digit classes: 0 through 9.

What does Conv2D learn?
Its filters learn weights that can detect useful patterns in the training images.

What does MaxPooling2D do?
It reduces the spatial dimensions by keeping the strongest activation from each pooling region.

What does Flatten do?
It converts the multi-dimensional feature maps into one long vector.

What happens during model.fit()?
The CNN repeatedly makes predictions, calculates loss, computes gradients through backpropagation, and updates its weights.

What is the overall CNN workflow?
Load data → preprocess → build CNN → compile → train → evaluate → predict.