Prepare the Data
Before a neural network can learn from images, we need to prepare those images correctly. In this lesson, we will clean and transform the MNIST data into a format the model can work with efficiently.
Good data preparation makes training easier.
Our images already contain the information we need, but their pixel values are not yet in the most useful format for our neural network. We will normalize the pixels and make sure the input data has the correct shape.
Load the MNIST Dataset
First, we load the dataset using TensorFlow.
import tensorflow as tf
(x_train, y_train), (x_test, y_test) = \
tf.keras.datasets.mnist.load_data()
We now have four important variables.
x_train → training images
y_train → training labels
x_test → test images
y_test → test labels
Understand x_train
x_train contains the images used to train
the neural network.
print(x_train.shape)
The shape will look like this:
(60000, 28, 28)
This means there are 60,000 training images and every image has 28 rows and 28 columns of pixels.
60000
↓
Number of images
28
↓
Image height
28
↓
Image width
Understand y_train
y_train contains the correct answer for each
training image.
print(y_train.shape)
The result is:
(60000,)
There is one label for each image.
x_train[0] → Image
y_train[0] → Correct digit
x_train[1] → Image
y_train[1] → Correct digit
Understand Pixel Values
MNIST images use grayscale pixel values between 0 and 255.
0 → Black
255 → White
Example:
0 0 0
0 255 255
0 255 0
A neural network can work with these numbers, but using values between 0 and 1 is generally more convenient for training.
Normalize the Images
We divide every pixel value by 255.
x_train = x_train / 255.0
x_test = x_test / 255.0
This changes the range from:
0 → 255
to:
0 → 1
For example:
Original pixel:
255
After normalization:
255 / 255 = 1.0
Original pixel:
128
After normalization:
128 / 255 = 0.502
Why Normalize?
Neural networks generally train more smoothly when their input values are kept within a small numerical range.
Before:
0 ─────────────── 255
After:
0 ─────────────── 1
We are not changing what the image represents. We are simply changing the numerical scale used to represent the pixels.
Do We Normalize the Labels?
No. We do not divide the digit labels by 255.
Image:
x_train
↓
Normalize
↓
0 to 1
Label:
y_train
↓
Keep as
0 to 9
The label is already a class identifier. For example,
the label 7 simply means the image represents
the digit 7.
Check the Data After Normalization
print(x_train.min())
print(x_train.max())
We should now see values close to:
0.0
1.0
This confirms that the image pixels have been normalized.
Do We Need to Reshape MNIST?
That depends on the neural network architecture we build next.
If we use a simple Dense network, we need to flatten each 28 × 28 image into a single vector.
28 × 28 image
↓
784 values
28 × 28 = 784
However, if we build a CNN, we would normally preserve the spatial image structure instead of flattening it immediately.
Flattening the Image
Suppose we have:
28 × 28
Flattening converts it into:
784 values
[0.0, 0.0, 0.2, 0.8, ...]
In Keras, a Dense neural network can perform this using a Flatten layer.
model = tf.keras.Sequential([
tf.keras.layers.Flatten(
input_shape=(28, 28)
),
...
])
The Flatten layer does not learn anything. Its job is simply to rearrange the image data into a one-dimensional vector.
Keep Training and Test Data Separate
We normalize both datasets, but we must not mix their images together.
x_train → Model Training
y_train → Correct Training Answers
x_test → Final Evaluation
y_test → Correct Test Answers
The test set should remain unseen during model training. Otherwise, the final accuracy can give us a misleading picture of the model's performance.
Complete Data Preparation Code
import tensorflow as tf
# Load dataset
(x_train, y_train), (x_test, y_test) = \
tf.keras.datasets.mnist.load_data()
# Normalize pixel values
x_train = x_train / 255.0
x_test = x_test / 255.0
# Check shapes
print("Training images:", x_train.shape)
print("Training labels:", y_train.shape)
print("Test images:", x_test.shape)
print("Test labels:", y_test.shape)
# Check pixel range
print("Minimum pixel:", x_train.min())
print("Maximum pixel:", x_train.max())
What Happens to One Image?
Original Image
28 × 28 pixels
Pixel range: 0 - 255
↓
Normalize
28 × 28 pixels
Pixel range: 0 - 1
↓
Flatten if using Dense network
28 × 28
↓
784 values
↓
Neural Network
Notice that the actual digit has not changed. Only its numerical representation has been prepared for the model.
The Important Difference With CNNs
There is one important detail you should not overlook. Flattening is useful for a basic Dense network, but it throws away the explicit 2D structure of the image.
Dense Network
28 × 28
↓
Flatten
↓
784
↓
Dense Layers
CNN
28 × 28
↓
Convolution
↓
Feature Maps
↓
Pooling
↓
Classifier
This is one reason CNNs are better suited to image problems: they can learn spatial patterns such as edges, shapes, and textures before classification.
Prepare the data before asking the model to learn.
For our MNIST project, we load the images, inspect their shapes, normalize pixel values from 0–255 to 0–1, keep labels unchanged, and preserve the test data separately. If we use a Dense network, the 28 × 28 image can then be flattened into 784 values.
Quick Check
To normalize pixel values from the range 0–255 to the range 0–1.
No. The labels represent class numbers from 0 to 9 and should remain class identifiers.
Because we need genuinely unseen data to evaluate how well the trained model generalizes.