A production-grade Rust implementation of Echo State Networks (reservoir computing) for time series prediction, classification, and temporal pattern recognition.
ESN implements the reservoir computing paradigm—a computationally efficient approach to recurrent neural networks where only the output layer is trained, while the internal "reservoir" of neurons remains fixed after random initialization.
-
Fast Training: Only the output weights are learned via ridge regression (closed-form solution)
-
Temporal Memory: Reservoir dynamics naturally capture temporal dependencies
-
Low Computational Cost: No backpropagation through time required
-
Hierarchical Processing: Stack multiple reservoirs for complex pattern extraction
┌─────────────────────────────────────┐
│ ECHO STATE NETWORK │
├─────────────────────────────────────┤
Input │ │
───────────────► │ ┌───────────────────────────┐ │
(input_dim) │ │ RESERVOIR │ │ Output
│ │ (sparse recurrent RNN) │─────┼──────────►
│ │ │ │ (output_dim)
│ │ • Sparse connections │ │
│ │ • Leaky integrator │ │
│ │ • Spectral radius < 1 │ │
│ └───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ READOUT (trained) │ │
│ │ Ridge regression │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────┘| Feature | Description |
|---|---|
Sparse Reservoir |
Randomly connected recurrent network with configurable sparsity (default 90%) |
Leaky Integrator Neurons |
Continuous-time dynamics with configurable leaking rate for temporal smoothing |
Spectral Radius Control |
Automatic scaling of reservoir weights to ensure echo state property |
Ridge Regression Training |
Regularized linear readout training with closed-form solution |
Feedback Connections |
Optional output-to-reservoir feedback for generative tasks |
State History |
Automatic tracking of reservoir states for temporal context |
Hierarchical ESN |
Stack multiple reservoirs for multi-scale temporal feature extraction |
pub enum Activation {
Tanh, // Standard hyperbolic tangent
Sigmoid, // Logistic sigmoid (0-1 range)
ReLU, // Rectified linear unit
LeakyReLU(f32), // Leaky ReLU with configurable slope
Identity, // Linear pass-through
}Add to your Cargo.toml:
[dependencies]
esn = "0.1"Or via command line:
cargo add esnuse esn::{EchoStateNetwork, EsnConfig, Activation};
use ndarray::Array1;
// Configure the ESN
let config = EsnConfig {
reservoir_size: 500, // Number of reservoir neurons
spectral_radius: 0.95, // Echo state property guarantee
leaking_rate: 0.3, // Temporal dynamics speed
input_scale: 0.5, // Input weight scaling
..Default::default()
};
// Create the network
let mut esn = EchoStateNetwork::new(config, 10, Activation::Tanh)?;
// Process input (returns reservoir state)
let input = Array1::from_vec(vec![0.5; 10]);
let state = esn.step(&input);
// Train on collected data
let mse = esn.train(&inputs, &targets, washout)?;
// Predict using trained readout
let output = esn.predict()?;Stack multiple reservoirs for complex temporal patterns:
use esn::{HierarchicalEsn, EsnConfig, Activation};
let configs = vec![
EsnConfig { reservoir_size: 100, ..Default::default() },
EsnConfig { reservoir_size: 50, ..Default::default() },
];
let mut h_esn = HierarchicalEsn::new(
configs,
input_dim,
vec![Activation::Tanh, Activation::Tanh]
)?;
// Process through all layers
let output = h_esn.step(&input);
// Get combined state from all layers (150 neurons total)
let combined = h_esn.get_combined_state();| Parameter | Type | Default | Description |
|---|---|---|---|
|
|
500 |
Number of neurons in the reservoir |
|
|
0.95 |
Controls memory/stability tradeoff. Must be < 1 for echo state property |
|
|
0.3 |
Neuron update rate (0-1). Lower = longer memory, slower dynamics |
|
|
0.5 |
Scaling factor for input weights |
|
|
0.0 |
Feedback connection strength (0 = disabled) |
|
|
0.9 |
Reservoir connectivity (0-1). Higher = sparser matrix |
|
|
1e-6 |
Ridge regression regularization strength |
|
|
true |
Include bias term in readout layer |
|
|
1e-4 |
Gaussian noise added to reservoir state |
Forecast future values from historical sequences (stock prices, weather, sensor data).
Learn normal patterns; deviations indicate anomalies.
Temporal pattern recognition for speech, music, or acoustic signals.
Capture dynamics of complex systems (Lorenz attractor, Mackey-Glass).
Integrate multiple sensor streams with different temporal characteristics.
Classify time series patterns (gesture recognition, activity detection).
ESN expects temporal data as sequences of Array1<f32> vectors.
| Requirement | Value | Notes |
|---|---|---|
Data type |
|
Single-precision float |
Value range |
[-1, 1] or [0, 1] |
Normalize before training |
Minimum samples |
500+ |
More is better |
Washout |
~10% of training |
Discard initial transients |
// Training data: Vec of time steps
let inputs: Vec<Array1<f32>> = vec![
Array1::from_vec(vec![0.1, 0.2]), // t=0
Array1::from_vec(vec![0.3, 0.4]), // t=1
// ...
];
let targets: Vec<Array1<f32>> = vec![
Array1::from_vec(vec![0.5]), // target at t=0
Array1::from_vec(vec![0.6]), // target at t=1
// ...
];For detailed data preprocessing guidelines, benchmark datasets, and evaluation metrics, see Model Card.
| Method | Description |
|---|---|
|
Create a new ESN with given configuration |
|
Process one input step, return reservoir state |
|
Train readout weights via ridge regression, return MSE |
|
Generate output from current state using trained weights |
|
Reset reservoir state to zeros |
|
Enable feedback connections |
|
Get current reservoir state |
|
Get reservoir state history |
|
Get reservoir size |
|
Check if readout weights are set |
| Method | Description |
|---|---|
|
Create hierarchical ESN with multiple layers |
|
Process input through all layers |
|
Reset all layer states |
|
Concatenate states from all layers |
The ESN maintains the echo state property: the reservoir state is uniquely determined by the input history, regardless of initial conditions. This is ensured by:
-
Spectral radius < 1 (reservoir weights scaled appropriately)
-
Leaky integrator dynamics
-
Sparse connectivity
Training uses ridge regression (Tikhonov regularization):
Where:
-
X = collected reservoir states
-
Y = target outputs
-
λ = ridge regularization parameter
The initial washout samples are discarded during training to allow the reservoir to "wash out" initial transients and reach a representative operating regime.
ndarray = { version = "0.15", features = ["rayon", "serde"] }
rand = "0.8"
rand_distr = "0.4"
rayon = "1.10" # Parallel computation
serde = "1.0" # Serialization
thiserror = "2.0" # Error handling
tracing = "0.1" # Structured logging| Document | Description |
|---|---|
Dataset expectations, training procedure, evaluation metrics, limitations |
|
Development plans and future features |
|
Security policy and vulnerability reporting |
Run the reproducible Mackey-Glass benchmark:
cargo run --release --example benchmarkExpected output (NRMSE ~0.016):
═══════════════════════════════════════════════════════════════
RESULTS SUMMARY
═══════════════════════════════════════════════════════════════
Total time: ~600ms
Training MSE: ~1.7e-4
Test MSE: ~2.6e-4
Test NRMSE: ~0.016
Status: EXCELLENT (NRMSE < 0.1)-
Jaeger, H. (2001). "The echo state approach to analysing and training recurrent neural networks". GMD Report 148.
-
Lukoševičius, M. (2012). "A practical guide to applying echo state networks". Neural Networks: Tricks of the Trade.
-
Jaeger, H. & Haas, H. (2004). "Harnessing nonlinearity: Predicting chaotic systems and saving energy in wireless communication". Science.
Dual-licensed under your choice of:
-
Palimpsest-MPL License v1.0 (PMPL-1.0) — permissive, attribution required
-
PMPL-1.0-or-later — copyleft, network use triggers distribution
See LICENSE.txt for details.
This project adheres to the Hyperpolymath Standard for language and tooling policy. See CLAUDE.md for details.
-
Rust — all core functionality
-
No TypeScript/Node.js/Go — use Rust or ReScript alternatives
-
Deno — for any JavaScript tooling needs