Skip to content

Keep gradients finite when electrodes are driven to zero amplitude - #4

Open
charliefay38221-sudo wants to merge 1 commit into
neuralcodinglab:mainfrom
charliefay38221-sudo:fix/nan-gradient-at-zero-amplitude
Open

Keep gradients finite when electrodes are driven to zero amplitude#4
charliefay38221-sudo wants to merge 1 commit into
neuralcodinglab:mainfrom
charliefay38221-sudo:fix/nan-gradient-at-zero-amplitude

Conversation

@charliefay38221-sudo

Copy link
Copy Markdown

Sigma's default size equation is sqrt(x / current_spread). The derivative of
sqrt is infinite at zero, so an electrode driven to exactly 0 A emits a NaN
gradient that propagates into every parameter, while the forward pass still
looks correct. With one electrode in four at zero, 25% of the amplitude
gradients are NaN.

This is reachable in ordinary use: sparse stimulation is the goal for a
charge-limited device, sigmoid encoder outputs underflow to zero, and a charge
penalty drives amplitudes there. It presents as training diverging rather than
as a bug, because nothing is visibly wrong until the whole model is NaN.

Both other implementations of this model already guard against it:

  • viseon's SafetyLayer.forward ends .clamp(1e-32, None)
  • pulse2percept's DynaphosModel does
    sigma = np.where(amp > 0, np.clip(P / 2, 1e-22, None), sigma),
    commented "only update sigma if amplitude > 0"

This clamps the argument instead. Forward output is unchanged: worst
absolute difference exactly 0.000e+00 over 30 random varied-amplitude batches.
The sigmoid size equation was never affected.

Adds a regression test covering 100%, 50% and 25% of electrodes off, plus a
check that the forward percept is untouched. Mutation tested: with the clamp
removed the three gradient cases fail and the forward case still passes, so the
suite is detecting the defect rather than merely detecting change.

Scope note: this fixes NaN gradients only. It does not by itself let an encoder
that has drifted below rheobase recover, since relu in get_current and the
greater visibility gate are separately flat there. Happy to raise that
separately if it is of interest.

The default sqrt size equation (Tehovnik 2007) computes

    sqrt(x / current_spread)

whose derivative 1/(2*sqrt(x)) is infinite at x = 0. An electrode driven to
exactly zero amplitude therefore emits a NaN gradient that propagates into
every parameter of the model, while the forward pass continues to look
correct. Nothing signals the corruption until the whole network is NaN, which
presents as a diverged model rather than a bug.

This is reachable in ordinary use rather than a corner case: sparse
stimulation is the goal for a charge-limited device, sigmoid encoder outputs
underflow to zero, and any charge penalty drives amplitudes down. With one
electrode in four at zero, 25% of the amplitude gradients are NaN.

Clamping the argument keeps the derivative finite. The bound is far below any
physical stimulation current, so the forward output is unchanged: the worst
absolute difference over 30 random varied-amplitude batches is exactly zero.

The sigmoid size equation is smooth at zero and is unaffected.

Adds a regression test covering 100%, 50% and 25% of electrodes off, plus a
check that the forward percept is untouched. The gradient tests fail on the
previous behaviour and the forward test passes either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant