Freedom Sale
Independence Day Special — Unlock the AI Path 70% off our most popular AI course · Limited time offer
--Days
--Hrs
--Min
--Sec
Claim Your Discount
✦ Intermediate ⏱ 40 min

✍️ Build a Handwritten Digit Recognizer with Keras

🎯 What You'll Build

A neural network trained on 60,000 handwritten digits that recognizes 0–9 with 99% accuracy — and a script to test it on your own handwritten images.

📋 What You'll Need

1

Load and explore the MNIST dataset

MNIST is built into Keras — 70,000 images of handwritten digits, already split into train and test sets.

import tensorflow as tf
import matplotlib.pyplot as plt

# Load MNIST — downloads automatically on first run (~11MB)
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()

print(f'Training images: {X_train.shape}')   # (60000, 28, 28)
print(f'Test images:     {X_test.shape}')    # (10000, 28, 28)
print(f'Pixel range:     {X_train.min()} to {X_train.max()}')

# Show a few samples
fig, axes = plt.subplots(1, 5, figsize=(10, 2))
for i, ax in enumerate(axes):
    ax.imshow(X_train[i], cmap='gray')
    ax.set_title(f'Label: {y_train[i]}')
    ax.axis('off')
plt.tight_layout()
plt.savefig('sample_digits.png')
Training images: (60000, 28, 28)
Test images:     (10000, 28, 28)
Pixel range:     0 to 255
2

Preprocess the data

Normalize pixel values to 0–1 and flatten each 28×28 image into a 784-element vector.

# Normalize: scale pixels from 0-255 to 0-1
X_train = X_train / 255.0
X_test  = X_test  / 255.0

# Flatten: reshape (60000, 28, 28) → (60000, 784)
X_train_flat = X_train.reshape(-1, 784)
X_test_flat  = X_test.reshape(-1, 784)

print(f'Training shape after flatten: {X_train_flat.shape}')
Training shape after flatten: (60000, 784)
💡 Tip: Normalization is crucial — neural networks train much faster and more stably when inputs are in the range 0–1 rather than 0–255.
3

Build and train the neural network

A simple 3-layer network (784 → 128 → 64 → 10) is enough to hit 98%+ accuracy on MNIST.

from tensorflow import keras

model = keras.Sequential([
    keras.layers.Dense(128, activation='relu', input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation='softmax')  # 10 outputs = digits 0-9
])

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

model.summary()

# Train — takes about 1-2 minutes on CPU
history = model.fit(
    X_train_flat, y_train,
    epochs=10,
    batch_size=128,
    validation_split=0.1,
    verbose=1
)
Epoch 1/10 - loss: 0.2584 - accuracy: 0.9242
Epoch 5/10 - loss: 0.0789 - accuracy: 0.9763
Epoch 10/10 - loss: 0.0512 - accuracy: 0.9846
4

Evaluate on the test set

Check accuracy on data the model has never seen before.

test_loss, test_accuracy = model.evaluate(X_test_flat, y_test, verbose=0)
print(f'Test accuracy: {test_accuracy:.4f}')

# Show some predictions
import numpy as np
predictions = model.predict(X_test_flat[:5])
predicted_digits = np.argmax(predictions, axis=1)

for i in range(5):
    print(f'Image {i}: True={y_test[i]}, Predicted={predicted_digits[i]}, Confidence={predictions[i][predicted_digits[i]]:.1%}')
Test accuracy: 0.9821

Image 0: True=7, Predicted=7, Confidence=99.9%
Image 1: True=2, Predicted=2, Confidence=99.7%
Image 2: True=1, Predicted=1, Confidence=99.8%
Image 3: True=0, Predicted=0, Confidence=99.9%
Image 4: True=4, Predicted=4, Confidence=98.4%
5

Save the model and predict your own digit

Save the trained model and write a script to predict digits from your own drawn images.

# Save the model
model.save('digit_recognizer.keras')
print('Model saved!')

# To predict your own image:
# 1. Draw a digit on white paper and photograph it
# 2. Or draw in MS Paint on a white background and save as PNG
# 3. Run this script:

from PIL import Image
import numpy as np
import tensorflow as tf

model = tf.keras.models.load_model('digit_recognizer.keras')

img = Image.open('my_digit.png').convert('L')   # convert to grayscale
img = img.resize((28, 28))                       # resize to 28x28
img_array = np.array(img) / 255.0               # normalize
img_flat  = img_array.reshape(1, 784)            # flatten

# MNIST uses white digits on black — invert if your image is black on white
img_flat = 1 - img_flat

prediction = model.predict(img_flat)
digit = np.argmax(prediction)
confidence = prediction[0][digit]

print(f'Predicted digit: {digit} ({confidence:.1%} confidence)')
Model saved!
Predicted digit: 5 (96.3% confidence)
💡 Tip: For even higher accuracy (99.5%+), use a Convolutional Neural Network (CNN) — swap the Dense layers for Conv2D layers. CNNs are specifically designed for image data and detect local patterns like edges and curves.

🎉 You Did It!

You trained a neural network from scratch to 98% accuracy on 10,000 test images. MNIST is the "Hello World" of deep learning — the same architecture principles (layers, activation functions, dropout, softmax output) apply to any image classification task.

Found something wrong?

Spotted a bug, broken code, or something that doesn't look right? Tell us what's off and we'll fix it.