Neural Network
MNIST
Handwriting Recognition
Machine Learning
Troubleshooting

Neural Network not learning - MNIST data - Handwriting recognition

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When a neural network fails to learn MNIST, the problem is usually not that MNIST is hard. It is usually a bug in preprocessing, output configuration, loss selection, or training setup. MNIST is simple enough that a correctly wired model should improve quickly, so failure to learn is a strong signal that something basic is wrong.

Start With a Known-Good Baseline

Before debugging fancy architecture choices, build a minimal model that is known to work.

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(
17    optimizer="adam",
18    loss="sparse_categorical_crossentropy",
19    metrics=["accuracy"],
20)
21
22model.fit(x_train, y_train, epochs=5, batch_size=128, validation_split=0.1)
23model.evaluate(x_test, y_test)

If a setup close to this does not learn, the issue is likely in the data pipeline or training configuration rather than in the general idea of using a network for MNIST.

Check the Labels and Output Layer Match

A classic failure mode is a mismatch between labels, output activation, and loss function.

Correct combinations include:

  • integer labels plus Dense(10, softmax) plus sparse_categorical_crossentropy
  • one-hot labels plus Dense(10, softmax) plus categorical_crossentropy

A wrong pairing can make training appear stuck even though the code runs.

For example, one-hot labels should not be trained with sparse_categorical_crossentropy unless you intentionally change the label format.

Normalize the Inputs

MNIST pixel values arrive in the range 0 through 255. Feeding raw bytes into the model is not guaranteed to fail, but training is usually easier and more stable when inputs are scaled to 0.0 through 1.0.

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

This is one of the simplest fixes for sluggish or unstable learning.

Do Not Make the First Model More Complex Than Necessary

MNIST is small and clean. You do not need a deep experimental architecture to verify that the training loop works.

Once a simple dense model or small CNN learns properly, then it makes sense to compare architectures. If the very first model already contains aggressive regularization, unusual activation functions, or hand-written custom layers, debugging becomes much harder.

A Small CNN Baseline

A compact convolutional model often performs better than a plain dense network while still remaining easy to debug.

python
1model = keras.Sequential([
2    keras.layers.Input(shape=(28, 28, 1)),
3    keras.layers.Conv2D(32, 3, activation="relu"),
4    keras.layers.MaxPooling2D(),
5    keras.layers.Conv2D(64, 3, activation="relu"),
6    keras.layers.MaxPooling2D(),
7    keras.layers.Flatten(),
8    keras.layers.Dense(64, activation="relu"),
9    keras.layers.Dense(10, activation="softmax"),
10])

If you use this shape, remember to reshape the input images to include the channel dimension.

python
x_train = x_train[..., None]
x_test = x_test[..., None]

Watch the Metrics During the First Epoch

If training accuracy stays near random guessing, useful questions are:

  • are labels aligned with images
  • is the loss function correct for the label format
  • are gradients exploding or vanishing
  • is the learning rate wildly wrong

MNIST usually improves fast, so a flat training curve is a clue, not bad luck.

Learning Rate and Initialization Still Matter

Even on MNIST, a very large learning rate can make the loss bounce around and prevent convergence. A very small rate can make the model look frozen.

That is why adam is a good default baseline. It removes one more variable from the debugging problem. Once learning works, you can experiment with SGD, momentum, schedulers, and other optimizer choices more confidently.

Common Pitfalls

The most common mistake is pairing the wrong output layer and loss function with the label format.

Another common issue is forgetting to normalize the image data. Developers also often debug architecture complexity before checking whether a tiny known-good model can learn at all, which delays the real fix.

Summary

  • MNIST should be easy enough that a correct baseline model learns quickly.
  • Start with a minimal working model before experimenting.
  • Make sure labels, output activation, and loss function match.
  • Normalize the input images to stabilize training.
  • If learning stays flat, treat it as a wiring bug until proven otherwise.

Course illustration
Course illustration

All Rights Reserved.