Repository navigation
Conversation
_predict() returned the argmax column index as the predicted label. That is only the label id when the surrogate saw every class. The RSB residual stage is trained on the rows stage 1 rejected, which usually lack some classes, so its predictions were shifted to the wrong labels. Calibration then measured the shifted labels and nearly always refused stage 2, losing its coverage. Map the columns through clf.classes_.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
_predict()insrc/tracer/fit/pipeline.pyreturnedprobs.argmax(axis=1)as the predicted label. That is the column index ofpredict_proba, which followsclf.classes_. It equals the label id only when the surrogate was trained on every label.The RSB residual stage (
build_rsb, stage 2) is trained only on the rows stage 1 rejected. That pool usually lacks some labels. For example, an easy class that stage 1 absorbs completely is missing. Thenclasses_is something like[1, 2, 3], and column 0 is reported as label 0 instead of label 1. The fix maps the argmax throughclf.classes_:Stage 1 and
globalalways see every label (_split_bufferputs each class in train), so their outputs don't change. Only a stage trained on a label subset behaves differently.Why it matters
Calibration and routing both go through
_predict/apply_stage. The stage-2 surrogate's predictions therefore get compared with the teacher under the wrong labels. On the reject pool below, stage-2 agreement measured via_predictis 0.13 onmain, compared with 0.71 for the same model'sclf.predict. The parity gate then almost always refuses stage 2, so RSB quietly loses the coverage it exists to add. If stage 2 does get certified, the router emits shifted labels for the classes affected.Benchmark (fixed seed, public API, same data,
mainvs this branch)The data is synthetic and uses 4 classes, 4,000 traces, 6-dim embeddings,
RandomState(0). Class 0 is trivially separable, and classes 1-3 have an "easy" group and a "hard" group that a single linear surrogate can't resolve. The generator is_residual_tracesin the new test. Command:main@ 093792aAgreement holds at target while coverage rises by about 15 points. The gate, thresholds and defaults are unchanged. This only corrects which label a stage reports.
Tests
The new file is
tests/test_stage_labels.py:test_predict_maps_proba_columns_to_class_labelstrains a surrogate on labels{1, 3}and checks that_predict/apply_stagereturn{1, 3}. Onmainit fails with{0, 1} != {1, 3}.test_rsb_residual_stage_routes_with_teacher_labelsruns the end-to-endfit→load_router→predict_batchflow on the data above. It checks that RSB is selected and that stage-2 rows carry their teacher label. Onmainit fails with'l2d' == 'rsb'.pytest tests/: 129 passed, 3 skipped (Python 3.11). Both new tests fail onmainand pass here.I found this bug and prepared the fix with AI assistance (Breken). I checked the code path, test output and benchmark numbers above by running them locally.