This project implements a simple Convolutional Neural Network (CNN) using NumPy only, without any deep learning frameworks like TensorFlow or PyTorch.
It trains on the MNIST handwritten digit dataset (28×28 grayscale images) and includes forward and backward propagation, convolution, pooling, dense layers, softmax + cross-entropy loss, and SGD optimizer with momentum + decay.
Achieves ~98% accuracy on MNIST test set after 5 epochs.
✅ Convolutional layer (Conv2D
) with Xavier/He initialization
✅ Max Pooling layer (MaxPool2D
)
✅ Fully-connected (dense) layers
✅ ReLU and Softmax activations
✅ Categorical cross-entropy loss
✅ SGD optimizer with momentum and learning rate decay
✅ Custom forward + backward pass
✅ Model saving and loading
✅ Support for custom image predictions
- MNIST digits (via
keras.datasets.mnist
) - 60,000 train / 10,000 test samples
- Images normalized to
[0,1]
Input: 28x28x1 Conv2D: 3x3 kernel, 32 filters, padding=1 → ReLU → MaxPool2D: 2x2 Flatten Dense: 128 units → ReLU Dense: 10 units → Softmax
- Optimizer: SGD with momentum = 0.9, decay = 1e-3
- Batch size: 64
- Epochs: 5
Example output:
Epoch 1: Loss = 0.5501, Accuracy = 0.8502 Epoch 2: Loss = 0.4003, Accuracy = 0.8904 ...
pip install keras nnfs matplotlib opencv-python pillow
👉 Run the notebook or script:
# Example forward + training loss = forward_pass(X_batch, y_batch) backward_pass(loss_func.output, y_batch) optimizer.pre_update_params() optimizer.update_params(layer2) optimizer.update_params(layer1) optimizer.update_params(conv1) optimizer.post_update_params()
You can load your own image:
test_image = cv2.imread("pred1.png", cv2.IMREAD_GRAYSCALE) test_image = cv2.resize(test_image, (28,28)) test_image = cv2.bitwise_not(test_image) / 255.0
And predict:
conv1.forward(img) ... print("Predicted digit:", predicted_class[0])
Example output:
0: 0.000001 % 1: 0.000002 % ... 8: 99.998234 % 9: 0.000100 % Predicted digit: 8
# Save model saver = ModelSaver() saver.save_model(conv1, layer1, layer2, optimizer) # Load model saver.load_model(conv1, layer1, layer2, optimizer)
Weights, biases, optimizer state saved in .npz
file.
Plots incorrect guesses with predicted + true labels:
for idx in first_10_incorrect: plt.imshow(X_test[idx], cmap="gray") print(f"Predicted: {test_predictions[idx]}, True: {test_true[idx]}")
├── cnn_mnist.py / notebook.ipynb ├── model.npz # Saved model ├── pred1.png # Example custom test image └── README.md
Inspired by NNFS book and built using NumPy, OpenCV, Matplotlib.
- Add more conv layers
- Add dropout / batchnorm
- Add support for other datasets (e.g. CIFAR-10)