Skip to content

✨ Ensemble of SOMs needs a diversity source beyond vmap #58

Description

@nstarman

and is a separate concern from making the SOM a default — see the note at the
end.

phasecurvefit.som is vmap-able over its initial prototypes, which was the
guarantee #56 set out to make (pure functions, explicit init and key, static
shapes, lax.scan over epochs, no host callbacks). tests/test_som_ensemble.py
makes that executable and it holds. But it is necessary, not sufficient, for
a Statistically Combined Ensemble of SOMs.

The finding

Batch Kohonen is strongly contractive. Each epoch replaces every prototype with
a neighbourhood-weighted mean of the data; the previous prototype value
enters only through which prototype currently wins each datum. So once two
ensemble members' best-matching-unit assignments coincide — which happens within
a few epochs on a smooth, well-sampled curve — every subsequent epoch is
byte-identical and the members stay merged forever.

Measured while building the guarantee test: with a shared PCA init jittered per
member at 0.05, ensemble members converged bit-for-bit identical. A sweep
over 5 seeds × jitter magnitudes found only ~20× that jitter reliably keeps
members apart. So the obvious construction — map over perturbed initializations —
yields a degenerate posterior: zero spread, no uncertainty information, and
nothing to combine.

Switching to genuinely independent initializations (a per-member random
ordering into init_prototypes, rather than perturbing one shared init) does
separate them: pairwise separations of 0.44–1.64 on fitted prototypes and
4.7–6.6 on chords, seven to eight orders of magnitude above jnp.allclose
tolerances. That is what _ensemble_inits in tests/test_som_ensemble.py now
does, with a comment explaining why the diversity has to be large so a future
tidy-up does not silently re-merge the ensemble.

What a real posterior will need

Independent initializations are enough to make members differ, but an ensemble
whose only diversity source is the starting lattice is measuring initialization
sensitivity, not data uncertainty. A meaningful posterior of orderings probably
wants one of:

  • bootstrap-resampling the data per member — the standard choice, and it
    propagates sampling noise into the spread;
  • per-member subsampling — cheaper, similar effect;
  • a stochastic (online) update — closer to the paper's own (A9)/(A10), where
    presentation order injects randomness that batch Kohonen has removed by
    construction.

Worth deciding deliberately, because the choice determines what the resulting
spread actually means.

Already documented

  • docs/guides/som.md, "A note on ensembles" — states plainly that vmap-ability
    is a necessary building block but not a sufficient one.
  • tests/test_som_ensemble.py::_ensemble_inits — carries the contractivity
    explanation and a "do not tidy this back down" warning.

This is arguably a distinct follow-up from "make the SOM part of the default
pipeline": that one is about the order() default and its compatibility
surface, this one is about designing the SCESOM feature. Happy to split it into
its own issue if that is cleaner — say the word.

Relationship to #57

Split out of a comment on #57 (make the SOM the default pipeline). That issue is
about the order() default and its compatibility surface — minimum-N, return
type, gamma_range, migration. This one is about designing the SCESOM feature
itself. Related only in that both build on the SOM stage added in #56.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions