Data Normalization
Data Scaling
MNIST Dataset
Machine Learning Preprocessing
Data Preparation

Correct way of normalizing and scaling the MNIST dataset

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

MNIST images are small grayscale digits with pixel values from 0 to 255. Preprocessing them is simple, but there is still confusion about whether you should only scale, fully standardize, or apply both. The correct answer depends on the model, but for most modern workflows the safe baseline is to convert to floating point and scale to 0.0 through 1.0.

Start with Simple Scaling

Each MNIST image is 28 x 28, stored as unsigned bytes. Neural networks train more smoothly when the inputs are on a smaller numeric range, so the usual first step is:

python
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0

That alone is enough for many Keras and PyTorch examples. It keeps the relative brightness structure intact and avoids large input magnitudes.

Scaling Versus Standardization

These terms are often mixed together.

Scaling means mapping pixel values into a smaller fixed range, usually 0 through 1.

Standardization means subtracting a mean and dividing by a standard deviation so the resulting distribution is centered near zero with unit variance.

For MNIST, simple scaling is often sufficient, especially for convolutional models and small multilayer perceptrons. Standardization can still help, but it is not mandatory in the same way it often is for tabular features with very different units.

A Good Keras Pipeline

Here is a minimal TensorFlow example that uses the common baseline preprocessing.

python
1import tensorflow as tf
2from tensorflow import keras
3
4(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
5
6x_train = x_train.astype("float32") / 255.0
7x_test = x_test.astype("float32") / 255.0
8
9model = keras.Sequential([
10    keras.layers.Input(shape=(28, 28)),
11    keras.layers.Flatten(),
12    keras.layers.Dense(128, activation="relu"),
13    keras.layers.Dense(10, activation="softmax")
14])
15
16model.compile(optimizer="adam",
17              loss="sparse_categorical_crossentropy",
18              metrics=["accuracy"])
19
20model.fit(x_train, y_train, epochs=3, validation_split=0.1)

This is a valid and standard way to preprocess MNIST.

When Mean and Standard Deviation Normalization Helps

Some workflows standardize MNIST using dataset statistics. In PyTorch, a common pair is mean 0.1307 and standard deviation 0.3081.

python
1from torchvision import datasets, transforms
2
3transform = transforms.Compose([
4    transforms.ToTensor(),
5    transforms.Normalize((0.1307,), (0.3081,))
6])
7
8train_data = datasets.MNIST(root="data", train=True, download=True, transform=transform)

ToTensor() already scales from 0 through 255 into 0.0 through 1.0. Normalize then standardizes using the supplied statistics.

This is useful if you want inputs centered around zero or you are following a reference training setup that assumes those values.

What You Usually Should Not Do

Do not apply random scaling formulas without understanding them. For example, dividing by 256 instead of 255 is slightly wrong and has no advantage.

Do not compute separate normalization statistics for each image unless you explicitly want per-image normalization. That changes the task because each digit image gets rescaled independently, which can distort intensity information.

Also avoid fitting normalization statistics on the test set separately if your goal is a realistic evaluation pipeline. In general, compute any learned preprocessing on the training set and reuse it for validation and test data.

CNN Input Shape and Data Type

Many bugs come from shape handling rather than normalization itself. A dense model may accept 28 x 28 and flatten internally, but a convolutional model usually expects a channel dimension.

python
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)

The data should also be floating point before division. If a framework uses integer division rules or the dtype remains integer too long, you can silently get incorrect results.

Common Pitfalls

A common mistake is saying you are normalizing when you are only scaling. The code may still be fine, but the terminology becomes misleading.

Another mistake is stacking redundant preprocessing steps. If a library transform already scales to 0 through 1, do not divide by 255 again.

Finally, keep training and inference consistent. If you train on scaled inputs and later feed raw byte images into the model, performance will collapse even though the model itself is unchanged.

Summary

  • For most MNIST models, converting to float and dividing by 255.0 is the correct baseline.
  • Standardization with dataset mean and standard deviation is optional, not mandatory.
  • Use framework helpers carefully so you do not scale the data twice.
  • Keep preprocessing consistent between training, validation, and inference.
  • Watch tensor shape and dtype, because many MNIST issues are shape bugs disguised as preprocessing problems.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design