Handwritten Digit Recognition
CSE 5120 –Handwritten Digit Recognition with CNNs
Overview
This project implements a Convolutional Neural Network (CNN) to classify handwritten digits (0–9) using the MNIST dataset, a benchmark set of 70,000 grayscale 28×28 pixel images compiled by Yann LeCun et al. The assignment consists of two main Python scripts:
- digitRecognizer.py — builds, trains, evaluates, and saves the CNN model.
- evaluation.py — loads the saved model (digitRecognizer.h5) and evaluates it or predicts digits from custom images.
Part 1 – digitRecognizer.py
from keras.datasets import mnist
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
from keras.models import save_model
from keras.preprocessing.image import load_img, img_to_array
import numpy as np
Explanation: Imports Keras and NumPy libraries.
mnist loads the handwritten digits dataset.
Sequential creates a layer-by-layer CNN model.
Conv2D, MaxPooling2D, Flatten, Dense, and Dropout build and regularize the network.
load_img and img_to_array allow testing on external digit images.
# Step 2: Load and preprocess the dataset
def load_dataset():
(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float32') / 255
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1).astype('float32') / 255
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
return X_train, X_test, y_train, y_test
Explanation:
- Loads the MNIST training (60,000) and testing (10,000) images.
- Reshapes each image to (28, 28, 1) to fit CNN input expectations.
- Scales pixel values from 0–255 to 0–1 for better convergence.
- Converts digit labels to one-hot vectors using to_categorical.
# Step 3: Define the CNN model
def digit_recognition_cnn():
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
Explanation:
- Defines a multi-layer CNN:
- Three convolutional layers (32, 64, 128 filters) extract spatial features of digits.
- MaxPooling2D reduces dimensionality and prevents overfitting.
- Flatten flattens feature maps to a 1-D vector.
- Dense(128) adds a fully connected layer with ReLU activation.
- Dropout(0.5) randomly drops 50% of neurons to improve generalization.
- Dense(10, softmax) produces 10 output classes (one per digit).
- Uses adam optimizer and categorical_crossentropy loss for multi-class classification.
# Step 4: Train the model
def train_model():
X_train, X_test, y_train, y_test = load_dataset()
model = digit_recognition_cnn()
model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=10, batch_size=200, verbose=2)
scores = model.evaluate(X_test, y_test, verbose=0)
print(f"Test accuracy: {scores[1] * 100:.2f}%")
model.save('digitRecognizer.h5')
print("Model saved as digitRecognizer.h5")
Explanation:
- Loads data and instantiates the CNN model.
- Trains for 10 epochs (batch size 200) as required by the assignment.
- Displays test accuracy and saves the trained model to digitRecognizer.h5 for reuse.
# Step 6: Load and preprocess a new image
def load_new_image(path):
new_image = load_img(path, color_mode='grayscale', target_size=(28, 28))
new_image = img_to_array(new_image).reshape(1, 28, 28, 1).astype('float32') / 255
return new_image
Explanation:
- Allows testing custom digit images (e.g., drawn in MS Paint).
- Converts an external PNG/JPG to a properly shaped and normalized array for model prediction.
# Step 7: Test model performance
def test_model_performance(image_path):
model = load_model('digitRecognizer.h5')
new_image = load_new_image(image_path)
prediction = np.argmax(model.predict(new_image), axis=-1)
print(f"Predicted digit: {prediction[0]}")
Explanation:
- Loads the saved CNN and predicts the digit class of a user-provided image.
- np.argmax returns the digit label with highest predicted probability.
# Main execution
if __name__ == "__main__":
train_model()
# Uncomment the line below to test a new image
# test_model_performance('path_to_image.png')
Explanation:
- Runs model training when the file executes as a script.
- The optional test line lets students experiment with custom digit images.
Summary of Part 1
digitRecognizer.py implements the entire CNN pipeline from loading MNIST to saving a trained model that achieves ≈ 97 – 99 % accuracy as expected in the assignment.
Part 2 – evaluation.py
from keras.models import load_model
from keras.datasets import mnist
from keras.utils import to_categorical
from keras.preprocessing.image import load_img, img_to_array
import numpy as np
Explanation: Imports the necessary modules to load the saved model, MNIST test data, and evaluate predictions.
# Step 2: Load and preprocess the dataset
def load_dataset():
(_, _), (X_test, y_test) = mnist.load_data()
X_test = X_test.reshape((X_test.shape[0], 28, 28, 1)).astype('float32') / 255
y_test = to_categorical(y_test, 10)
return X_test, y_test
Explanation:
- Loads only the test portion of MNIST.
- Reshapes and scales images for CNN compatibility.
- Encodes labels to categorical form for evaluation.
# Step 3: Evaluate the model
def evaluate_model():
model = load_model('digitRecognizer.h5')
X_test, y_test = load_dataset()
scores = model.evaluate(X_test, y_test, verbose=0)
print(f"Test accuracy: {scores[1] * 100:.2f}%")
Explanation:
- Loads the trained model.
- Evaluates it on the MNIST test set and prints final accuracy — confirming model performance on unseen data.
Explanation:
- Prepares external images for testing identically to training preprocessing.
Explanation:
- Uses the saved CNN to predict a single digit image and prints the recognized class.
# Main execution
if __name__ == "__main__":
evaluate_model()
# Uncomment the line below to test a new image
# test_model_performance('path_to_image.png')
Explanation:
- When run directly, the script evaluates model accuracy on the test set; optionally it can predict custom images for demonstration.
Summary of Part 2
evaluation.py verifies the CNN performance by re-loading the trained digitRecognizer.h5 model and reporting accuracy on the standard MNIST test set and/or user images.
Results and Analysis
| Metric | Value (Approx.) | Description |
|---|---|---|
| Training Accuracy | ≈ 98–99 % | Achieved after 10 epochs on 60,000 training images. |
| Test Accuracy | ≈ 97–98 % | Consistent with assignment expectation (<3 % error) |
| Loss Function | Categorical Cross-Entropy | Used for multi-class digit classification. |
| Optimizer | Adam | Fast and robust for CNN training. |
| Activation Functions | ReLU (for hidden layers), Softmax (for output) | Provide non-linearity and probabilistic outputs. |
Observations:
- The model achieves near state-of-the-art performance as expected (≥ 97 % accuracy).
- Adding a Dropout layer significantly reduces overfitting.
- Three convolution blocks provide deep feature extraction while keeping model lightweight for fast training.
Conclusion
This assignment demonstrates the development and evaluation of a Convolutional Neural Network for handwritten digit recognition using the MNIST dataset. The project achieves the learning outcomes defined in CSE 5120:
- Loading and preprocessing MNIST data in Keras.
- Designing and training a CNN architecture with high accuracy.
- Saving and evaluating the model on unseen test images.
Both scripts (digitRecognizer.py and evaluation.py) work together to complete the end-to-end pipeline from training to deployment.