Add LRRSettings.init_default (identity|CAR) for cold-start rereferencing - #19
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a configurable cold-start behavior for LRR when no weights are provided or fitted yet, allowing per-cluster leave-one-out CAR to be applied immediately (while keeping legacy identity passthrough as the default).
Changes:
- Introduces
RereferenceInit(IDENTITY,CAR) andLRRSettings.init_default(defaultIDENTITY). - Implements
_car_effective_matrixand uses it as the no-weights fallback in_processwheninit_default == CAR. - Adds unit tests covering CAR cold-start behavior, small-cluster passthrough, default identity behavior, and precedence of provided/fitted weights over CAR.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/ezmsg/learn/process/ssr.py |
Adds the RereferenceInit enum, init_default setting, CAR effective-matrix construction, and CAR fallback path when no weights exist. |
tests/unit/test_ssr.py |
Adds TestCARInit coverage validating CAR cold-start math, clustering behavior, and precedence rules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ezmsg-sigproc 2.34.0 (ezmsg-org/ezmsg-sigproc#186) hoists deterministic cluster-aware rereference matrix construction into ezmsg.sigproc.util.rereference. Use it here: - Replace the local RereferenceInit enum with the imported RereferenceKind (re-exported from this module; same "identity"/"car" config values). - Delete _car_effective_matrix: the cold-start branch in _process is now a single rereference_matrix(...) call. The matrix is built as host-side numpy; AffineTransformTransformer converts it to the message's namespace/dtype/device on first use, so the on-device selection-matrix scatter is no longer needed. - Use the shared validate_channel_clusters() from util.channels for cluster bounds checking (empty-list policy stays here). - Bump ezmsg-sigproc floor to 2.34.0 (and test dep ezmsg-simbiophys to 1.8.0). Behavior is unchanged for float streams; integer-dtype streams now get float64 output from the cold-start transform (weights stay float64 instead of being cast to the message's integer dtype). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a simplification commit (a8d4279) that delegates the CAR cold-start matrix construction to the newly released ezmsg-sigproc 2.34.0 (ezmsg-org/ezmsg-sigproc#186), which hoists deterministic cluster-aware rereference matrices into Changes relative to the original PR:
All tests from this PR pass unchanged apart from the enum rename (34 in test_ssr.py; full unit suite 308 passed). One minor behavior note: integer-dtype streams now get float64 output from the cold-start transform (weights are no longer cast to the message's integer dtype); float streams are bit-identical to before. 🤖 Generated with Claude Code |
New TestBackendPreservation (parametrized over mlx and torch, with importorskip guards for platforms without mlx) asserts that: - the output stays in the input's array namespace, - fitted state (cxx, weights) and the internal affine's weight arrays live in that namespace, and - numpy matrices (cold-start CAR/identity, user-provided settings.weights) are converted to the message's backend on first use, with values matching the numpy reference path in all three cases. The tests exposed a pre-existing bug: MLX's linalg.inv/pinv only run on the CPU stream, so partial_fit crashed for mlx-backed messages. _solve_weights now passes stream=mx.cpu for the mlx namespace -- with unified memory this is a scheduling hint, not a host copy, and results remain mlx arrays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed 373d987 adding backend-preservation tests for
Writing these exposed a pre-existing bug (present on Full unit suite: 314 passed, 2 skipped (macOS/arm64, mlx 0.32.0, torch 2.13.0). 🤖 Generated with Claude Code |
Summary
Make LRR's behavior configurable for when it has no weights yet (
weights=Noneand nothing fit). Today it initializes to identity and waits for an update; this
adds the option to instead apply per-cluster common-average reference (CAR) as a
cold start, so a stream gets common-mode rejection before any fitted weights exist.
The default stays
IDENTITY, so existing behavior is unchanged.Motivating use case: an offline-only LRR workflow where run 1 has no fitted
checkpoint. Rather than run open-loop with identity referencing (or pre-generate a
CAR matrix out-of-band), the transformer can seed CAR directly from the channel
clusters it already resolves at reset.
What changed
RereferenceInitenum:IDENTITY(default) andCAR.LRRSettings.init_default(defaultIDENTITY) — the effective transform usedonly when no weights are provided or fit.
LRRTransformer._car_effective_matrixbuilds the CAR effective (I − W) matrixper cluster: within a cluster of
kchannels the sub-block is(k/(k−1))·I_k − (1/(k−1))·J_k, i.e.y_i = x_i − mean_{j≠i} x_j(leave-one-out).Clusters smaller than
MIN_REREF_CLUSTER_SIZE— and all cross-cluster terms — stayidentity, matching the fit's passthrough for tiny/sliced clusters. Built in the
message's array namespace via a selection-matrix scatter, so GPU-backed arrays stay
on device (mirrors
_solve_weights)._processuses this as the no-weights fallback wheninit_default == CAR.Behavior & backward compatibility
IDENTITY→ no behavior change; existing users are unaffected.Walways wins overinit_default. CAR isonly the cold start and is replaced the moment weights are loaded or fit.
init_defaultaffects only the output path — it does not contaminate the fit(
partial_fitaccumulates covariance on the raw input, not the CAR output).channel_clusters/cluster_by_field/block_size).Usage
Testing
tests/unit/test_ssr.py— 34 passing. New coverage: per-cluster andcluster_by_field="bank"leave-one-out CAR against a reference implementation,small-cluster passthrough, identity-default backward-compat, and both
provided-weights and fitted-weights overriding CAR (
test_fit_overrides_car).