A neural network library implemented in Odin, ported from a C implementation. Designed for modularity, educational purposes, and practical applications in quantitative finance.
- Modular layer architecture using tagged unions for type-safe dispatch
- Multiple layer types:
- Dense (Fully Connected) layers
- Activation layers (ReLU, Sigmoid, Tanh, Softmax)
- Loss functions:
- Mean Squared Error (MSE)
- Binary Cross Entropy
- Categorical Cross Entropy
- Optimizers:
- Stochastic Gradient Descent (SGD)
- Momentum
- RMSProp
- Adam
- Black-Scholes analytical pricer for European options
- Options pricing example comparing neural network approximation to analytical solutions
- Allocator-aware - all memory operations go through Odin's context allocator
- Comprehensive test suite with unit and integration tests
neural_network/
├── Makefile # Build configuration
├── README.md # This file
├── docs/
│ └── C_VS_ODIN_COMPARISON.md # Side-by-side C vs Odin comparison
├── src/
│ ├── main.odin # Entry point
│ ├── matrix.odin # Dynamic matrix type and operations
│ ├── layer.odin # Base layer type and dispatch
│ ├── dense.odin # Dense (fully connected) layer
│ ├── activation.odin # Activation functions
│ ├── loss.odin # Loss functions
│ ├── optimizer.odin # Optimization algorithms
│ ├── black_scholes.odin # Analytical Black-Scholes pricer
│ └── options_example.odin # NN vs BS comparison example
└── tests/
├── matrix_test.odin # Matrix operation tests
├── activation_test.odin # Activation function tests
├── dense_test.odin # Dense layer tests
├── loss_test.odin # Loss function tests
├── optimizer_test.odin # Optimizer tests
└── integration_test.odin # End-to-end training tests
- Odin compiler (tested with dev-2024-01 or later)
# Clone Odin
git clone https://github.com/odin-lang/Odin.git
cd Odin
# Build (requires LLVM)
./build_odin.sh
# Add to PATH
export PATH=$PATH:$(pwd)# Build with optimizations
make build
# Build with debug info
make debug
# Build for release (aggressive optimizations)
make release
# Check for errors without building
make check# Build and run all examples (XOR + Options Pricing)
make run
# Run only options pricing example
./build/neural_network options
# Run only XOR example
./build/neural_network xor=== Neural Network in Odin ===
--- XOR Problem (Binary Classification) ---
Epoch 0 | Loss: 0.693147
Epoch 100 | Loss: 0.234521
...
Testing trained network:
Input | Target | Predicted
(0, 0) | 0 | 0.0234 (0)
(0, 1) | 1 | 0.9812 (1)
(1, 0) | 1 | 0.9756 (1)
(1, 1) | 0 | 0.0189 (0)
============================================================
EUROPEAN OPTIONS PRICING: Neural Network vs Black-Scholes
============================================================
--- Part 1: Black-Scholes Analytical Prices ---
Call Option Prices:
S K T r σ | BS Price
--------------------------------------------------
100 100 0.25 0.05 0.20 | $5.8761
105 100 0.25 0.05 0.20 | $9.1254
...
--- Part 2: Training Neural Network ---
Training for 100 epochs with batch size 32...
Epoch 0 | Avg Loss: 0.002341
Epoch 10 | Avg Loss: 0.000523
...
--- Part 3: Comparison Results ---
Test Set Comparison (Call Options):
Moneyness T r σ | BS Price | NN Price | Error
---------------------------------------------------------------------------
0.952 0.34 0.03 0.25 | $ 4.2341 | $ 4.1987 | $0.0354 (0.8%)
...
# Run all tests
make testpackage main
import nn "neural_network/src"
import "core:fmt"
main :: proc() {
// Create layers
dense1, _ := nn.dense_layer_new(2, 4, -1.0, 1.0)
defer nn.layer_free(&dense1)
activation1, _ := nn.activation_layer_new(4, .Sigmoid)
defer nn.layer_free(&activation1)
dense2, _ := nn.dense_layer_new(4, 1, -1.0, 1.0)
defer nn.layer_free(&dense2)
activation2, _ := nn.activation_layer_new(1, .Sigmoid)
defer nn.layer_free(&activation2)
loss_layer, _ := nn.loss_layer_new(1, .Binary_Cross_Entropy)
defer nn.layer_free(&loss_layer)
// Create optimizer
layers := [?]^nn.Layer{&dense1, &activation1, &dense2, &activation2}
opt, _ := nn.optimizer_new(.Adam, 0.1, layers[:])
defer nn.optimizer_free(&opt)
// Training loop
for epoch in 0..<1000 {
// Forward pass
input, _ := nn.matrix_new(2, 1)
nn.matrix_set(&input, 0, 0, 1.0) // x1
nn.matrix_set(&input, 1, 0, 0.0) // x2
h1, _ := nn.layer_forward(&dense1, input)
a1, _ := nn.layer_forward(&activation1, h1)
h2, _ := nn.layer_forward(&dense2, a1)
output, _ := nn.layer_forward(&activation2, h2)
target, _ := nn.matrix_new(1, 1)
nn.matrix_set(&target, 0, 0, 1.0) // XOR(1,0) = 1
loss, _ := nn.loss_forward(&loss_layer, output, target)
// Backward pass
grad, _ := nn.loss_backward(&loss_layer)
grad_a2, _ := nn.layer_backward(&activation2, grad)
grad_d2, _ := nn.layer_backward(&dense2, grad_a2)
grad_a1, _ := nn.layer_backward(&activation1, grad_d2)
grad_d1, _ := nn.layer_backward(&dense1, grad_a1)
// Update weights
nn.optimizer_step(&opt)
// Cleanup (in real code, use defer or batch these)
nn.matrix_free(&input)
nn.matrix_free(&h1)
nn.matrix_free(&a1)
nn.matrix_free(&h2)
nn.matrix_free(&output)
nn.matrix_free(&target)
nn.matrix_free(&loss)
nn.matrix_free(&grad)
nn.matrix_free(&grad_a2)
nn.matrix_free(&grad_d2)
nn.matrix_free(&grad_a1)
nn.matrix_free(&grad_d1)
}
}// Create matrices
mat, ok := nn.matrix_new(3, 4)
rand_mat, _ := nn.matrix_rand(3, 4, -1.0, 1.0)
identity, _ := nn.matrix_eye(3)
// Access elements
val := nn.matrix_at(mat, 0, 0)
nn.matrix_set(&mat, 0, 0, 1.5)
// Arithmetic
sum, _ := nn.matrix_add(m1, m2)
diff, _ := nn.matrix_subtract(m1, m2)
prod, _ := nn.matrix_mult(m1, m2) // Matrix multiplication
hadamard, _ := nn.matrix_hadamard(m1, m2) // Element-wise
// Transformations
transposed, _ := nn.matrix_transpose(mat)
nn.matrix_scale(&mat, 2.0) // In-place scaling
// Cleanup
nn.matrix_free(&mat)// Dense layer: input_dim -> output_dim
dense, _ := nn.dense_layer_new(
input_dim = 10,
output_dim = 5,
weight_min = -0.5,
weight_max = 0.5,
)
// Activation layers
relu, _ := nn.activation_layer_new(5, .ReLU)
sigmoid, _ := nn.activation_layer_new(5, .Sigmoid)
tanh_layer, _ := nn.activation_layer_new(5, .Tanh)
softmax, _ := nn.activation_layer_new(5, .Softmax)
// Loss layers
mse, _ := nn.loss_layer_new(5, .MSE)
bce, _ := nn.loss_layer_new(1, .Binary_Cross_Entropy)
ce, _ := nn.loss_layer_new(10, .Cross_Entropy)// SGD
opt_sgd, _ := nn.optimizer_new(.SGD, learning_rate = 0.01, layers[:])
// Momentum
opt_momentum, _ := nn.optimizer_new(.Momentum, 0.01, layers[:])
nn.optimizer_set_momentum(&opt_momentum, 0.95)
// RMSProp
opt_rmsprop, _ := nn.optimizer_new(.RMSProp, 0.001, layers[:])
// Adam (recommended)
opt_adam, _ := nn.optimizer_new(.Adam, 0.001, layers[:])
nn.optimizer_set_betas(&opt_adam, 0.9, 0.999)
nn.optimizer_set_epsilon(&opt_adam, 1e-8)| Aspect | C Version | Odin Version |
|---|---|---|
| Polymorphism | Function pointers (vtable) | Tagged unions with switch dispatch |
| Memory | Manual malloc/free | Allocator-aware with context |
| Error handling | NULL returns | Multiple return values (result, ok) |
| Variadics | Used for loss layer | Explicit parameters |
| Cleanup | Manual tracking | defer for automatic cleanup |
All allocations go through Odin's context allocator system:
// Use default allocator
mat, _ := nn.matrix_new(10, 10)
// Use custom allocator
mat, _ := nn.matrix_new(10, 10, my_allocator)
// Use temp allocator for short-lived data
context.allocator = context.temp_allocator
temp_mat, _ := nn.matrix_new(10, 10)This implementation is not thread-safe. Each thread should have its own network instance if parallelizing training.
- American options pricing (binomial tree / Longstaff-Schwartz)
- Batch normalization layer
- Dropout layer
- Convolutional layers
- Model serialization (save/load weights)
- GPU acceleration via Odin's vendor libraries
- Implied volatility solver
- Monte Carlo pricing with variance reduction