A CNN trained from scratch on the EMNIST Balanced dataset to classify handwritten digits and letters. Includes a full training pipeline with live experiment tracking via Weights & Biases, and an interactive web app where you draw a character and get a prediction instantly.
- Handwritten digit + letter classification (47 classes)
- CNN built in PyTorch with BatchNorm and Dropout
- Training augmentation — random rotation, translation, and scaling
- Live experiment tracking with Weights & Biases (loss, accuracy, LR, gradients, sample predictions)
- Best model auto-saved to checkpoints during training
- Per-class accuracy breakdown with confusion matrix
- Local inference server (Flask) + canvas drawing web app
- All hyperparameters controlled from a single config file — no hardcoded values
- Python
- PyTorch + Torchvision
- Weights & Biases
- Flask + Flask-CORS
- scikit-learn
- PyYAML
- Pillow
- NumPy
- Jupyter Notebook
emnist-classifier/
│
├── data/
│ ├── raw/ ← EMNIST downloads here automatically
│ └── processed/
│
├── src/
│ ├── model.py ← CNN architecture
│ ├── train.py ← Training loop + WandB logging
│ ├── evaluate.py ← Test evaluation + per-class report
│ ├── dataset.py ← Data loading, transforms, augmentation
│ └── utils.py ← Config loader, checkpointing, AverageMeter
│
├── configs/
│ └── config.yaml ← All hyperparameters live here
│
├── notebooks/
│ └── exploration.ipynb ← Visualize samples and class distribution
│
├── app/
│ ├── server.py ← Local Flask inference server
│ └── index.html ← Drawing web app
│
├── checkpoints/ ← best_model.pth saved here after training
├── requirements.txt
└── .gitignore
This project uses the EMNIST Balanced dataset:
- 47 classes — digits (0–9) and letters (visually ambiguous pairs like C/c are merged)
- 112,800 training samples, 18,800 test samples
- 28x28 grayscale images, same format as MNIST
Dataset downloads automatically via torchvision.datasets.EMNIST on first run.
Classes: 0 1 2 3 4 5 6 7 8 9
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b d e f g h n q r t
git clone https://github.com/DagaVedant/EMNIST-Character-Classifier.git
cd emnist-classifierpip install -r requirements.txtFree account at wandb.ai, then:
wandb loginTo disable WandB, set wandb.enabled: false in configs/config.yaml.
python src/train.py- Downloads EMNIST automatically (~550MB, one time)
- Trains for 15 epochs on GPU (GTX 1650 ≈ 20–30 min)
- Saves best checkpoint to
checkpoints/best_model.pth - Streams all metrics to WandB in real time
# Console report only
python src/evaluate.py
# Also log confusion matrix to WandB
python src/evaluate.py --wandb# Start the inference server (requires a trained checkpoint)
python app/server.pyThen open app/index.html in your browser. Draw a character on the canvas and click Classify to see the top 5 predictions with confidence bars.
Raw EMNIST images (28x28 grayscale)
│
├─ Orientation fix (EMNIST is rotated by default)
├─ Augmentation: rotation ±10°, translate 5%, scale 0.9–1.1
├─ Normalize
│
▼
Conv Block 1: Conv→BN→ReLU→Conv→BN→ReLU→MaxPool→Dropout
│
Conv Block 2: Conv→BN→ReLU→Conv→BN→ReLU→MaxPool→Dropout
│
Classifier: Flatten→Linear(512)→ReLU→Dropout→Linear(47)
│
▼
Top-5 predictions with confidence scores
Training — Adam optimizer with ReduceLROnPlateau scheduler. Validation split held out from training data. Best model saved automatically based on val accuracy.
Inference — canvas drawing is sent as a base64 PNG to the Flask server, preprocessed (inverted, cropped to bounding box, resized to 28x28), and passed through the model.
Everything lives in configs/config.yaml:
| Setting | Default | What it does |
|---|---|---|
training.epochs |
15 |
Number of full passes through data |
training.batch_size |
128 |
Images per training step |
training.learning_rate |
0.001 |
Initial learning rate |
training.weight_decay |
0.0001 |
L2 regularization |
model.conv_channels |
[32, 64] |
Output channels per conv block |
model.fc_hidden |
512 |
Hidden size of FC layer |
model.dropout_conv |
0.25 |
Dropout after each conv block |
model.dropout_fc |
0.5 |
Dropout before output layer |
data.val_split |
0.1 |
Fraction of train data used for validation |
wandb.log_interval |
50 |
Log batch loss every N steps |
The model is evaluated using:
- Overall test accuracy
- Per-class precision, recall, and F1
- Confusion matrix (via WandB)
Expected test accuracy: ~85–88% on EMNIST Balanced.
- Add symbols and custom shape classes with synthetic data generation
- Export model to ONNX for faster inference
- Deploy web app publicly with a cloud-hosted model
- Add real-time classification as you draw (no click needed)
- Experiment with ResNet or EfficientNet backbones
This project is open-source and available under the MIT License.
Developed as a personal machine learning project exploring CNNs, experiment tracking, and end-to-end model deployment.