Convolutional Neural Network.
97.88%Same task, harder version: one hundred classes instead of ten. Convolutions written from scratch in NumPy first, then the same model in PyTorch to compare, which makes it very clear what the framework is actually doing for you.
Write a two-digit number below and see how it reads it.

Scale the MLP approach to recognizing two-digit numbers using convolutional neural networks.
Why CNNs?
CNNs are different from MLPs because they have convolutional layers designed to recognize spatial patterns in images.

Kernels (small 3×3 filters) slide across the image, building a feature map that shows where certain visual patterns occur.

Pooling layers (usually MaxPool) reduce spatial dimensions, keeping only the strongest signals. Activation functions (ReLU, GELU) determine which patterns actually matter. After spatial feature extraction, the data is flattened and passed into fully connected layers, just like an MLP.
Through deeper layers, CNNs build increasingly complex representations: from simple edges to high-level features.
Process
The MLP baseline
First I tested the MLP from the previous section:
- ~99% train vs ~87% dev, which is clear overfitting
- The MLP treats pixels independently and lacks spatial bias, so small shifts degraded performance



The CNN implementation
- Data & normalization: scale to [0,1], standardize with training-set mean/std
- Augmentation: random horizontal shifts (±2 px), mild contrast/brightness jitter
- Optimization: Adam (lr=1e-3), He init, L2 regularization (λ=1e-4), batch size 256, up to 20 epochs
- Early stopping: patience=5, min_delta=1e-3 on dev accuracy
- From-scratch ops: NumPy-only conv via im2col/col2im, max-pooling, dropout, vectorized softmax cross-entropy, full backward pass
Results
Test accuracy: 97.88% (10,000 samples)
Hardest classes: 29 (90.2%), 97 (93.4%), 39 (93.8%), 33 (94.1%), 70 (94.1%)


