JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Adaptive Decoding: CLDA, ReFIT and Fighting Nonstationarity

The decoder-side algorithms that keep a BCI working online — intention estimation (ReFIT), CLDA blending, and the stability–plasticity dial that fights nonstationarity.

Why decoders drift

Even with a perfect initial fit, a fixed decoder decays. Neural signals are nonstationary: electrodes micro-move relative to tissue, unit yields change over days, arousal and attention shift the baseline, and the user's own plasticity rewrites the code. Collectively this is neural nonstationarity and signal drift, and from the decoder's viewpoint it appears as covariate shift — the input distribution moves out from under a mapping that was correct yesterday. The answer is to make the decoder adapt; the danger is adapting it wrongly. Recalibration is unavoidable, so we want it cheap, safe, and ideally invisible.

Intention estimation: the ReFIT idea

The core obstacle to online retraining is that you do not know the label. Supervised fitting needs the intended velocity at each moment, but in closed loop the true intent is hidden. ReFIT resolves this with a beautifully simple task assumption: in a reach-to-target task the user always intends to move straight toward the target. So take the decoded velocity, keep its speed, and rotate its direction to point at the goal — that rotated vector is your intention estimate, and you retrain on it. Applied to a velocity Kalman filter this yields the ReFIT Kalman filter, one of the highest-performing motor decoders yet demonstrated.

\tilde v_k = \lVert \hat v_k \rVert \; \frac{g_k - p_k}{\lVert g_k - p_k \rVert}

Intention estimate: keep the decoded speed \lVert \hat v_k\rVert but redirect it from the current cursor p_k toward the goal g_k. On-target samples get zero velocity. Retraining on \tilde v_k removes the confound between user and decoder error.

Keep the speed the decoder produced, but point it straight at the goal instead of wherever it was drifting. This assumes the user always wants to head toward the target, so retraining on these corrected velocities untangles the user's error from the decoder's.

\tilde v_k
The corrected 'intention' velocity used for retraining.
\lVert \hat v_k \rVert
The decoded speed (magnitude) — kept unchanged.
g_k - p_k
The vector from the cursor p_k to the goal g_k.
\frac{g_k - p_k}{\lVert g_k - p_k \rVert}
That vector shrunk to length 1 — pure direction to the goal.

A cursor drifting sideways keeps its speed but has its direction snapped straight toward the target; on-target samples get zero velocity.

Closed-loop decoder adaptation (CLDA)

ReFIT supplies labels; closed-loop decoder adaptation (CLDA) supplies the update rule that folds them in without destabilising the loop. The family shares a shape: periodically estimate fresh decoder parameters from a recent batch, then blend them into the running decoder rather than replacing it. SmoothBatch takes a convex combination; recursive maximum likelihood (RML) does the same continuously with a forgetting factor; adaptive Kalman variants update the model's own covariances online.

\Theta_n = (1-\rho)\,\Theta_{n-1} + \rho\,\hat\Theta_n^{\mathrm{batch}}, \qquad \rho = 1 - 2^{-\Delta t_{\text{batch}} / \tau_{1/2}}

SmoothBatch: blend the new batch estimate into the current parameters. The half-life \tau_{1/2} is the master knob — a long half-life means slow, stable adaptation; a short one means fast, twitchy adaptation.

Instead of jumping to each new estimate, gently blend it into the old parameters. How fast you blend is set by a half-life: a long half-life means slow, steady adaptation; a short one means fast but twitchy.

\Theta_n
The updated decoder parameters after blending.
\hat\Theta_n^{\mathrm{batch}}
The fresh estimate computed from the latest batch of data.
\rho
The blend fraction between 0 and 1 — how much of the new estimate to take.
\tau_{1/2}
The half-life — the master knob for adaptation speed.

A long half-life barely nudges the decoder each batch, while a short one lets it swing to the new estimate quickly.

# One CLDA batch update for a velocity Kalman filter
rho = 1 - 2**(-dt_batch / half_life)              # blend weight from half-life
for batch in closed_loop_stream():
    p, v_dec = run_decoder(batch)                 # cursor path + decoded velocity
    v_int = refit_intention(v_dec, goal=p_goal, pos=p)   # rotate toward target
    Theta_batch = fit_kalman(neural=batch.y, kin=v_int)  # fresh estimate
    Theta = (1 - rho) * Theta + rho * Theta_batch        # SmoothBatch blend
    deploy(Theta)                                 # keep driving the loop
A CLDA loop: estimate intention, refit on a batch, blend slowly, keep running — no supervised downtime.

Stability vs plasticity: the adaptation-rate knob

The blend weight is where control theory meets machine learning. Adapt too fast and the decoder chases measurement noise and, worse, couples badly with the user's own learning — the moving-target instability of the last guide, now driven by your update rate. Adapt too slowly and you cannot track real drift. Decoder stability is therefore a tuning problem in the half-life / forgetting factor, not a property you get for free.

\hat\Theta_n = \arg\min_{\Theta} \sum_{i \le n} \lambda^{\,n-i}\, \ell\big(\Theta;\, y_i, \tilde v_i\big), \qquad 0 < \lambda < 1

Exponentially-weighted (forgetting-factor) fitting: recent samples dominate, old ones fade at rate \lambda. Small \lambda tracks drift fast but is noisy; \lambda \to 1 is stable but slow — the same stability–plasticity dial as SmoothBatch's half-life.

Fit the decoder, but weight recent data more and let old data fade exponentially. A small \lambda forgets fast (tracks drift quickly but jumpily); \lambda near 1 remembers long (steady but slow) — the same stability-versus-plasticity dial as SmoothBatch's half-life.

\hat\Theta_n
The best-fit decoder parameters at step n.
\lambda
The forgetting factor between 0 and 1.
\lambda^{\,n-i}
The weight on sample i; older samples get exponentially less say.
\ell(\cdot)
The per-sample loss you're summing up and minimizing.

With \lambda = 0.95, a sample twenty steps old counts only about a third as much as the newest one.

Semi-supervised & self-recalibration

The frontier of adaptive decoding is removing the calibration block entirely. Self-recalibration uses the task's own structure — a cursor stopping on a target, a click, a decoded ErrP, the statistics of natural movement — to generate labels unsupervised, so the decoder tracks drift while the user just works. The payoff is no downtime; the risk is a feedback pathology: if the pseudo-labels are derived from the decoder's own outputs, errors can be reinforced and the loop can drift into a confident wrong state. Guarding against that self-reinforcement is an active research problem we return to in the final guide.