o
odinpkg.dev
packages / app / odin-neural-network

odin-neural-network

78d634dapp

A modular neural network library in Odin with dense layers, activations, loss functions, and optimizers — ported from C for educational and quantitative finance applications.

No license · updated 5 months ago

Neural Networks in Odin

A neural network library implemented in Odin, ported from a C implementation. Designed for modularity, educational purposes, and practical applications in quantitative finance.

Features

  • 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

Project Structure

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

Prerequisites

Installing Odin

# 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)

Building

# 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

Running

# 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

XOR Example Output

=== 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)

Options Pricing Example Output

============================================================
   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%)
...

Testing

# Run all tests
make test

Usage

Basic Example: XOR Problem

package 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)
    }
}

Matrix Operations

// 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)

Layer Types

// 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)

Optimizers

// 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)

Design Notes

Compared to the C Implementation

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

Memory Management

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)

Thread Safety

This implementation is not thread-safe. Each thread should have its own network instance if parallelizing training.

Roadmap

  • 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