Handwritten Digit Recognition Using Convolutional Neural Networks on Small Datasets

Dataset Preparation

The scikit-learn library provides a built-in dataset of handwritten digits that serves as an ideal starting point for image classification tasks.

from sklearn.datasets import load_digits
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split

# Load the digit recognition dataset
dataset = load_digits()
pixel_data = dataset.data.astype(np.float32)
labels = dataset.target.astype(np.float32).reshape(-1, 1)

Data Preprocessing Pipeline

Before feeding data into neural networks, several preprocessing steps are essential including normalization, label encoding, and dataset partitioning.

# Normalize pixel values to range [0,1]
normalizer = MinMaxScaler()
normalized_pixels = normalizer.fit_transform(pixel_data)
print('Normalized data shape:', normalized_pixels.shape)

# Apply one-hot encoding to labels
encoder = OneHotEncoder()
encoded_labels = encoder.fit_transform(labels).toarray()
print('Encoded labels shape:', encoded_labels.shape)

# Reshape data into image format: (samples, height, width, channels)
image_data = normalized_pixels.reshape(-1, 8, 8, 1)

# Split dataset into training and testing subsets
X_train, X_test, Y_train, Y_test = train_test_split(
    image_data, encoded_labels, test_size=0.2, random_state=42, stratify=encoded_labels
)

print('Training set shapes:', X_train.shape, Y_train.shape)
print('Testing set shapes:', X_test.shape, Y_test.shape)

CNN Architecture Design

The network architecture is carefully designed considering the small input size and limited feature complexity:

  • Limited depth: Only three convolutional layers due to small image dimensions
  • Compact kernels: 3x3 filters suitable for 8x8 pixel images
  • Feature maps progression: 16 → 32 → 64 → 128 channels across layers
  • Regularization: Batch normalization instead of dropout to preserve features
  • Activation functions: ReLU to mitigate gradient vanishing issues
  • Output layer: Softmax for multi-class probability distribution
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D

# Initialize sequential model
network = Sequential()

# First convolutional block
network.add(Conv2D(16, kernel_size=(3, 3), activation='relu', input_shape=(8, 8, 1)))
network.add(MaxPooling2D(pool_size=(2, 2)))
network.add(Dropout(0.25))

# Second convolutional block
network.add(Conv2D(32, kernel_size=(3, 3), activation='relu'))
network.add(MaxPooling2D(pool_size=(2, 2)))
network.add(Dropout(0.25))

# Third convolutional block
network.add(Conv2D(64, kernel_size=(3, 3), activation='relu'))
network.add(Conv2D(128, kernel_size=(3, 3), activation='relu'))
network.add(MaxPooling2D(pool_size=(2, 2)))
network.add(Dropout(0.25))

# Fully connected layers
network.add(Flatten())
network.add(Dense(128, activation='relu'))
network.add(Dropout(0.25))
network.add(Dense(10, activation='softmax'))

print(network.summary())

Model Training Process

The training configuration uses Adam optimizer with categorical crossentropy loss for multi-class classification.

# Compile model with appropriate loss function and optimizer
network.compile(
    loss='categorical_crossentropy',
    optimizer='adam',
    metrics=['accuracy']
)

# Execute training process
training_history = network.fit(
    x=X_train,
    y=Y_train,
    validation_split=0.2,
    batch_size=300,
    epochs=10,
    verbose=2
)

# Evaluate model performance on test set
test_score = network.evaluate(X_test, Y_test)
predicted_classes = network.predict_classes(X_test)
print('Test accuracy:', test_score[1])
print('Sample predictions:', predicted_classes[:10])

Training Visualization

Visualizing training progress helps monitor model convergence and detect overfitting.

import matplotlib.pyplot as plt

def plot_training_progress(history, metric, validation_metric):
    plt.figure(figsize=(12, 4))
    
    # Plot accuracy
    plt.subplot(1, 2, 1)
    plt.plot(history.history[metric])
    plt.plot(history.history[validation_metric])
    plt.title('Model Accuracy')
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Training', 'Validation'], loc='lower right')
    
    # Plot loss
    plt.subplot(1, 2, 2)
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('Model Loss')
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Training', 'Validation'], loc='upper right')
    
    plt.tight_layout()
    plt.show()

plot_training_progress(training_history, 'accuracy', 'val_accuracy')

Performance Evaluation

Evaluation involves both quantitative metrics and visual confusion matrix analysis.

import pandas as pd
import seaborn as sns

# Quantitative evaluation
evaluation_score = network.evaluate(X_test, Y_test)
print(f'Model Performance - Loss: {evaluation_score[0]:.4f}, Accuracy: {evaluation_score[1]:.4f}')

# Generate predictions
test_predictions = network.predict_classes(X_test)
actual_labels = np.argmax(Y_test, axis=1)

# Create confusion matrix
confusion_matrix = pd.crosstab(
    actual_labels, 
    test_predictions, 
    rownames=['Actual'], 
    colnames=['Predicted']
)

# Visualize results
plt.figure(figsize=(10, 8))
sns.heatmap(confusion_matrix, annot=True, fmt='d', cmap='Blues', linewidths=0.5)
plt.title('Confusion Matrix for Handwritten Digit Classification')
plt.show()

Tags: machine-learning convolutional-neural-networks TensorFlow scikit-learn image-classification

Posted on Wed, 02 Sep 2026 16:04:50 +0000 by camoconnell.com