Skip to content

Give each regression member its own target pipeline [RES-2639] - #1198

Open
bejaeger wants to merge 2 commits into
mainfrom
benjamin/res-2639-target-encoder-refactor
Open

Give each regression member its own target pipeline [RES-2639]#1198
bejaeger wants to merge 2 commits into
mainfrom
benjamin/res-2639-target-encoder-refactor

Conversation

@bejaeger

Copy link
Copy Markdown
Collaborator

Refactor for RES-2639, no intended behaviour change. PR 2 of this stack does the actual ticket and should be read after this one.

Why

The regressor z-normalised the target before handing it to the ensemble preprocessing, and everything downstream was then expressed relative to that: a member's target transform received standardized values, and its inverse_transform had to return standardized values, so that the bar-distribution borders and the ±1e3 sanity limits applied to them stayed in z-units. Reordering the transform under that arrangement forces the frame's statistics to be baked into every pipeline when it is built, and rebound whenever it is refitted on a different split.

What

Each member now owns one invertible pipeline from the target, in its own units, to the target the model is fitted on, and standardizes the target itself. y_train_mean_/y_train_std_ keep only the role they always had for raw_space_bardist_: the affine frame the ensemble is aggregated in and decoded from. The estimator owns that frame and applies it to the borders after the inverse, so nothing is baked into a pipeline and nothing needs rebinding.

The frame now reaches the two other border-mapping sites explicitly:

  • predict_batched — per dataset. The worker is refitted per dataset, so its own attributes only ever hold the last one's; a new test with targets three orders of magnitude apart pins this.
  • fine-tuning — through RegressorBatch. fit_from_preprocessed never sees the target, and y_train_mean_/y_train_std_ were previously never set on that path at all, so a member with a target transform would have hit an AttributeError once the border mapping needed them.

Aggregation deliberately stays in the frame of the checkpoint rather than moving to the target's own units. translate_probs_across_borders resolves positions within a bucket in float32; for a target with a large offset (1e8 with σ≈1e2) the borders in original units are spaced below float32 resolution at that magnitude and would collapse into duplicates. Keeping that frame also keeps the sanity limits meaning "this many standard deviations of the target" — as an absolute bound in the target's own units they would reject every border of any large-magnitude target.

Neutrality

StandardizeTarget reproduces the estimator's former arithmetic (np.mean, np.std + 1e-20) exactly, so every model input is bit-identical — verified directly on the fitted members, not just at the output.

Predictions were compared against main over 64 configurations: 5 targets (including a 1e8-offset one and a 1e-6-scale one), 4 model versions, 1/2/8 estimators, float64 inference, plus predict_batched and three non-default target transforms.

  • 32 of 64 bit-identical
  • worst relative deviation 2.4e-5, on a 0.9 quantile of a heavy-tailed target
  • median deviation 0 for median and the quantiles, 8e-8 for mean

The residual comes from a transformed member's borders now being narrowed to the dtype they arrived in; the frame round trip itself runs in float64, because in float32 its two halves cancel and cost about six digits of the borders.

Incidentally fixes a latent inconsistency: a row-subsampled estimator's target was normalised with the whole training set's statistics while its borders were mapped back as if the normalisation had been its own.

Tests

New tests/test_preprocessing/test_target_transform.py, plus regressor-level tests for the bit-identical member target, frame independence from the target's units, and the per-dataset frame in predict_batched. Full local suite passes (1639 tests).

🤖 Generated with Claude Code

Preparation for moving the target transform later in the pipeline, with no
intended change in behaviour.

The regressor z-normalised the target before handing it to the ensemble
preprocessing. Everything downstream then had to be expressed relative to
that: a member's target transform received standardized values, and its
`inverse_transform` was expected to return standardized values so that the
bar-distribution borders and the sanity limits applied to them stayed in
z-units.

Each member now owns one invertible pipeline from the target, in its own
units, to the target the model is fitted on, and standardizes the target
itself. `y_train_mean_`/`y_train_std_` keep only the role they always had for
`raw_space_bardist_`: the affine frame the ensemble is aggregated in and
decoded from. The estimator owns that frame and applies it to the borders
after the inverse, so nothing is baked into a pipeline when it is built and
nothing has to be rebound when it is refitted on another split.

Aggregation stays in the frame of the checkpoint rather than moving to the
target's own units, deliberately: `translate_probs_across_borders` resolves
positions within a bucket in float32, and for a target with a large offset the
borders in original units are spaced below float32 resolution at that
magnitude. The sanity limits keep the same frame, and therefore their meaning
of "this many standard deviations of the target".

The frame reaches the two other border-mapping sites explicitly: per dataset
in `predict_batched`, where the refitted worker's own attributes only hold the
last dataset's, and through `RegressorBatch` for fine-tuning, where
`fit_from_preprocessed` never sees the target and the attributes were
previously never set at all.

Neutrality: `StandardizeTarget` reproduces the estimator's former arithmetic
exactly, so every model input is bit-identical. Over 64 configurations
(5 targets including a 1e8-offset and a 1e-6-scale one, 4 model versions,
1/2/8 estimators, float64 inference, plus `predict_batched` and three
non-default target transforms), 32 are bit-identical and the worst relative
deviation is 2.4e-5, on a 0.9 quantile of a heavy-tailed target. It comes from
a transformed member's borders now being narrowed to the dtype they arrived in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d5e41e0. Configure here.

znorm_space_bardist=znorm_space_bardist_,
X_query_raw=x_test_raw,
y_query_raw=y_test_raw,
y_train_mean=float(train_mean),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finetuning uses unfitted target pipelines

Medium Severity

RegressorBatch still takes configs=list(conf) — the preprocessor template list — while this refactor makes every member carry a fitted target_transform that border mapping must use. With n_preprocessing_jobs > 1, those transforms are fitted only on the worker copies returned on ensemble_members; the template list stays unfitted. The same path in predict_batched already switches to [m.config for m in members] for that reason, so finetuning now hits AttributeError or wrong borders on the parallel path that previously skipped mapping when target_transform was None.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Use executor_.ensemble_members configs, not ensemble_configs_, for fitted transforms

Reviewed by Cursor Bugbot for commit d5e41e0. Configure here.

return Pipeline(
steps=[
# The preset reshapes the standardized target, as it always has;
# the ordering of these two steps is what RES-2639 changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment narrates ticket history

Low Severity

The step-order comment describes what RES-2639 will change and how the ordering “always has” worked, which narrates ticket history rather than stating current behavior only. Team convention is that comments and docstrings document the present contract, not planned or prior arrangements.

Fix in Cursor Fix in Web

Triggered by learned rule: Docstrings describe current behavior only — no call sites or code history

Reviewed by Cursor Bugbot for commit d5e41e0. Configure here.

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