A lightweight C++ neural network implementation built from scratch with support for multiple activation functions, modular layer architecture, loss functions, and optimizers. This project is inspired by the book "Deep Learning from Scratch: Building with Python from First Principles" by Seth Weidman.
├── example/
│ ├── main.cpp # Example usage
│ └── Makefile # Build configuration
├── include/
│ ├── Activation.h # Activation function utilities
│ ├── layers/
│ │ ├── Layer.h # Abstract layer interface
│ │ ├── ReLULayer.h # ReLU layer implementation
│ │ ├── SigmoidLayer.h # Sigmoid layer implementation
│ │ └── TanhLayer.h # Tanh layer implementation
│ ├── loss/
│ │ ├── Loss.h # Abstract loss interface
│ │ └── MSE.h # Mean Squared Error implementation
│ ├── optimizers/
│ │ ├── Optimizer.h # Abstract optimizer interface
│ │ └── SGD.h # Stochastic Gradient Descent implementation
│ └── SequentialModel.h # Neural network model
├── src/
│ ├── Activation.cpp
│ ├── layers/
│ │ ├── ReLULayer.cpp
│ │ ├── SigmoidLayer.cpp
│ │ └── TanhLayer.cpp
│ ├── loss/
│ │ └── MSE.cpp
│ ├── optimizers/
│ │ └── SGD.cpp
│ └── SequentialModel.cpp
├── LICENSE
└── README.md
- Modular Layer Architecture: Easily extendable layer system with abstract base class
- Multiple Activation Functions:
- Sigmoid with Xavier/Glorot initialization
- Tanh with Xavier/Glorot initialization
- ReLU with He initialization
- Loss Functions: Mean Squared Error (MSE) implementation
- Optimizers: Stochastic Gradient Descent (SGD)
- Sequential Model: Simple feedforward neural network builder with integrated training loop
- Backpropagation: Full backpropagation implementation with separated gradient computation and weight update steps
- Model Persistence: Save and load layer weights and biases to/from files
- XOR Problem Demo: Ready-to-run examples demonstrating different architectures
- C++ compiler with C++11 support (g++ recommended)
- Make build system
cd example
make
./example.out All layers implement the abstract Layer class with these key methods:
forward(): Perform forward propagationbackward(): Perform backpropagation (gradient computation only)getWeights()/setWeights(): Access layer parametersgetWeightGrads()/getBiasGrads(): Access computed gradientssaveParams()/downloadParams(): Serialize/deserialize layer state
The framework includes abstract Loss class with:
computeLoss(): Calculate loss between prediction and targetcomputeGrad(): Compute gradient for backpropagation Currently implemented: Mean Squared Error (MSE)
The Optimizer abstract class defines the interface for weight update algorithms:
step(): Update layer parameters using computed gradients Currently implemented: Stochastic Gradient Descent (SGD)
The SequentialModel class manages a sequence of layers and provides:
- Prediction with
predict() - Training loop with
train() - Backward pass coordination with
backward() - Full model serialization
| Function | Range | Initialization | Derivative | Use Case |
|---|---|---|---|---|
| Sigmoid | (0, 1) | Xavier/Glorot | f(x)(1-f(x)) | Binary classification, output layer |
| Tanh | (-1, 1) | Xavier/Glorot | 1 - f(x)² | Hidden layers, regression |
| ReLU | [0, ∞) | He | 0 if x≤0, 1 if x>0 | Hidden layers, deep networks |
#include "../include/SequentialModel.h"
#include "../include/layers/ReLULayer.h"
#include "../include/layers/SigmoidLayer.h"
#include "../include/layers/TanhLayer.h"
#include "../include/loss/MSE.h"
#include "../include/optimizers/SGD.h"
#include <iostream>
#include <memory>
#include <ostream>
#include <vector>
std::vector<std::vector<double>> inputs = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
std::vector<std::vector<double>> targets = {{0}, {1}, {1}, {0}};
int main() {
std::cout << "=== Sigmoid net for XOR ===" << std::endl;
// build layers
std::vector<std::unique_ptr<Layer>> layers;
layers.emplace_back(std::make_unique<ReLULayer>(2, 8, "layer1.txt"));
layers.emplace_back(std::make_unique<TanhLayer>(8, 4, "layer2.txt"));
layers.emplace_back(std::make_unique<SigmoidLayer>(4, 1, "layer3.txt"));
// create model
SequentialModel model(std::move(layers), std::make_unique<MSE>(),
std::make_unique<SGD>(0.1), 1000);
// train model
model.train(inputs, targets);
std::cout << "Results:" << std::endl;
for (size_t i = 0; i < inputs.size(); i++) {
vector<double> prediction = model.predict(inputs[i]);
std::cout << inputs[i][0] << " XOR " << inputs[i][1] << " = "
<< prediction[0] << " (expected: " << targets[i][0] << ")"
<< std::endl;
}
return 0;
}- Create a new layer class inheriting from
Layer - Implement the required virtual methods
- Add appropriate weight initialization (Xavier for sigmoid/tanh, He for ReLU)
- Implement the activation function and its derivative in backward pass
- Create a new class inheriting from
Loss - Implement
computeLoss()andcomputeGrad()methods - Integrate with
SequentialModelconstructor
- Create a new class inheriting from
Optimizer - Implement
step()method to update weights using gradients - Use with
SequentialModelfor training
The framework implements a clear separation of concerns:
- Forward pass: Layers compute activations and cache intermediate values
- Loss computation: Loss function calculates error and gradient
- Backward pass: Layers compute gradients (stored in
*_gradsmembers) - Optimization: Optimizer updates weights using computed gradients
This separation allows for flexible optimizer implementations and easy debugging.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- Implementation of multiple optimization algorithms (Adam, RMSprop)
- Better error handling and validation
- More layer types (Dropout, BatchNorm)
- Multi-thread architecture
- GPU acceleration
- Saving model to ONNX format
- Fixed-size architecture (cannot change layer sizes after construction)
- Basic error handling
- No GPU acceleration
- Limited to fully connected layers
- Single-threaded implementation