Skip to contentSkip to Content
DocsUser GuideTrainingRecursive Least Squares

Recursive Least Squares

Recursive Least Squares (RLS) is a streaming, second-order training method that processes the training series one timestep at a time while converging dramatically faster than the online delta rule. It achieves this by maintaining a running estimate of the inverse correlation matrix of the reservoir state, using it to scale each weight update according to the local curvature of the loss surface.

The Training Problem

As with ridge regression, the goal is to find the readout weight matrix WoutRNout×NW_{\text{out}} \in \mathbb{R}^{N_{\text{out}} \times N} such that y^t=Woutxt\hat{\mathbf{y}}_t = W_{\text{out}}\,\mathbf{x}_t approximates the target yt\mathbf{y}_t over the training sequence. RLS solves this incrementally: at each timestep it updates WoutW_{\text{out}} using only the current reservoir state and target, without storing the full state matrix.

The Algorithm

RLS maintains an N×NN \times N matrix P(t)(ΦΦ)1P(t) \approx \bigl(\Phi^\top \Phi\bigr)^{-1}, initialized as P(0)=δ1IP(0) = \delta^{-1} I. At each timestep tt:

1. Compute the Kalman gain:

k(t)=P(t1)x(t)λ+x(t)P(t1)x(t)\mathbf{k}(t) = \frac{P(t-1)\,\mathbf{x}(t)}{\lambda + \mathbf{x}(t)^\top P(t-1)\,\mathbf{x}(t)}

2. Compute the prediction error:

e(t)=ytarget(t)Wout(t1)x(t)\mathbf{e}(t) = \mathbf{y}_{\text{target}}(t) - W_{\text{out}}(t-1)\,\mathbf{x}(t)

3. Update the readout weights:

Wout(t)=Wout(t1)+e(t)k(t)W_{\text{out}}(t) = W_{\text{out}}(t-1) + \mathbf{e}(t)\,\mathbf{k}(t)^\top

4. Update the inverse correlation matrix:

P(t)=1λ(P(t1)d(t)k(t)k(t))P(t) = \frac{1}{\lambda}\Bigl(P(t-1) - d(t)\,\mathbf{k}(t)\,\mathbf{k}(t)^\top\Bigr)

where d(t)=λ+x(t)P(t1)x(t)d(t) = \lambda + \mathbf{x}(t)^\top P(t-1)\,\mathbf{x}(t) is the gain denominator and λ(0,1]\lambda \in (0, 1] is the forgetting factor.

For the full derivation, see Training Theory.

Parameters

delta — Initial Prior

delta initializes P(0)=δ1IP(0) = \delta^{-1} I. It encodes the prior uncertainty over the readout weights before any data is seen:

  • Small delta (e.g., 0.001): Large initial PP, wide prior. The algorithm adapts quickly at the start but may be noisy.
  • Large delta (e.g., 1.0): Unit prior. A common default that works well for most tasks.
  • Very large delta (e.g., 1000.0): Tight prior, slow initial adaptation.

delta = 1.0 is a sensible default for most reservoir tasks.

lambda — Forgetting Factor

lambda controls how much weight is given to older observations:

lambdaBehavior
1.0No forgetting. All past data weighted equally. Use for stationary tasks.
0.99Effective memory of ~100 steps. Suitable for slowly drifting targets.
0.95Effective memory of ~20 steps. Suitable for moderately non-stationary targets.
< 0.9Very short memory. Typically too aggressive for reservoir tasks.

For most offline training scenarios where the training data is stationary, lambda = 1.0 is recommended. Set lambda < 1.0 only when the target distribution is known to drift over time.

SPIRES API

RLS training is performed with a single call to spires_train_rls():

spires_status spires_train_rls( spires_reservoir *r, const double *input_series, const double *target_series, size_t series_length, double delta, double lambda );

Parameters:

ParameterDescription
rPointer to an initialized reservoir
input_seriesFlat array of input values, length series_length * num_inputs
target_seriesFlat array of target values, length series_length * num_outputs
series_lengthNumber of timesteps in the training data
deltaInitial prior scaling: P(0)=δ1IP(0) = \delta^{-1} I
lambdaForgetting factor in (0,1](0, 1]

Returns: SPIRES_OK on success, or an error status code.

Example Usage

#include <spires.h> #include <stdio.h> /* Assume reservoir r is already created */ /* Assume input_train[] and target_train[] are populated */ double delta = 1.0; /* unit prior */ double lambda = 1.0; /* no forgetting — stationary task */ spires_status s = spires_train_rls(r, input_train, target_train, N_TRAIN, delta, lambda); if (s != SPIRES_OK) { fprintf(stderr, "RLS training failed with status %d\n", s); spires_reservoir_destroy(r); return 1; } /* Reservoir is now trained -- run inference */ spires_reservoir_reset(r); for (size_t t = 0; t < N_TEST; t++) { spires_step(r, &input_test[t * num_inputs]); double y[NUM_OUTPUTS]; spires_compute_output(r, y); }

Internal Implementation

When you call spires_train_rls(), SPIRES:

  1. Resets the reservoir to its initial state and zeroes WoutW_{\text{out}}.
  2. Allocates the inverse correlation matrix PRN×NP \in \mathbb{R}^{N \times N} and workspace vectors.
  3. Initializes P=δ1IP = \delta^{-1} I.
  4. Processes each timestep in sequence: steps the reservoir, reads the state, and applies the four-step RLS update using BLAS dgemv, ddot, dscal, and dger.
  5. Frees PP and workspace buffers. The trained WoutW_{\text{out}} is stored inside the reservoir.

The PP matrix is local to the training call and is not retained afterward. If you need to continue training from where you left off across multiple calls, use the online delta rule instead.

Memory Considerations

The dominant cost of RLS is the PP matrix:

  • Memory: N×N×8N \times N \times 8 bytes (double precision)
  • For N=400N = 400: ~1.3 MB
  • For N=1000N = 1000: ~8 MB
  • For N=2000N = 2000: ~32 MB

Unlike ridge regression, RLS does not store the full state matrix Φ\Phi (which costs T×N×8T \times N \times 8 bytes). For long training sequences, RLS can therefore use substantially less memory than ridge while converging faster than the delta rule.

Comparison with Other Methods

PropertyRidge RegressionRLSOnline Delta Rule
Training modeBatchStreamingStreaming
ConvergenceGlobal optimum (one pass)Exact after NN stepsSlow (many passes)
MemoryO(T×N)O(T \times N) for Φ\PhiO(N2)O(N^2) for PPO(NNout)O(N \cdot N_{\text{out}})
Per-step costO(N2)O(N^2)O(NNout)O(N \cdot N_{\text{out}})
Hyperparametersλ\lambdadelta, lambdaLearning rate η\eta
Non-stationaryNoYes (λ<1\lambda < 1)Yes

← Ridge Regression | Online Delta Rule →

Last updated on