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
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.