LeNet-5: EMNIST Balanced Character Classifier
This repository hosts a modified LeNet-5 Convolutional Neural Network (CNN) trained specifically on the EMNIST Balanced dataset. It is designed to recognize 47 distinct classes of handwritten characters, including digits (0-9), uppercase letters (A-Z), and a subset of lowercase letters.
Model Performance
While LeNet-5 is a compact architecture, it performs robustly on the expanded EMNIST Balanced dataset:
| Metric | Score |
|---|---|
| Validation Accuracy | ~88% |
| Weighted F1-Score | 0.8751 |
| Total Classes | 47 |
Dataset Context
The EMNIST Balanced dataset is significantly more challenging than standard MNIST. It contains 131,600 images across 47 classes, designed to account for the inherent overlap in character shapes (e.g., lowercase 'o' vs uppercase 'O').
Architecture & Training Details
The model follows the classic LeNet-5 design with modern enhancements like ReLU activation and Dropout to improve generalization for the 47-class task.
1. Model Summary (LeNet-5 EMNIST Modified)
| Layer | Output Shape | Param # |
|---|---|---|
| Conv2D (6 filters, 5ร5, ReLU) | (28, 28, 6) | 156 |
| AveragePooling2D (2ร2) | (14, 14, 6) | 0 |
| Conv2D (16 filters, 5ร5, ReLU) | (10, 10, 16) | 2,416 |
| AveragePooling2D (2ร2) | (5, 5, 16) | 0 |
| Flatten | 400 | 0 |
| Dropout (0.2) | 400 | 0 |
| Dense (120 units, ReLU) | 120 | 48,120 |
| Dropout (0.2) | 120 | 0 |
| Dense (84 units, ReLU) | 84 | 10,164 |
| Dense (47 units, Softmax) | 47 | 3,995 |
Total Trainable Parameters: 64,851
2. Training Strategy
- Optimizer: Adam
- Loss Function: Categorical Crossentropy
- Regularization: Dual Dropout layers (0.2) were added to the fully connected head to prevent overfitting on the complex 47-class distribution.
Usage
To load and use this model locally:
import tensorflow as tf
import numpy as np
import cv2
# Load the model
model = tf.keras.models.load_model('lenet5_emnist_balanced.keras')
def preprocess_char(img_path):
# EMNIST images are 28x28 grayscale, often rotated/flipped
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img, (28, 28))
img = img.astype('float32') / 255.0
img = np.expand_dims(img, axis=[0, -1]) # Shape: (1, 28, 28, 1)
return img
# Predict
input_data = preprocess_char('handwritten_sample.png')
prediction = model.predict(input_data)
class_id = np.argmax(prediction)
- Downloads last month
- 69