Repository navigation
Keep TAO from lowering training accuracy at lambda=0 - #86
Merged
Merged
Conversation
With 3+ children, a sample can be correct in several children, so the routing problem at a node is multi-label. TAO split credit 1/numCorrect across those children, so its objective stopped tracking accuracy and it could accept rules (including the dummy) that lowered training accuracy. - Care rewards give full credit (1) to every correct child; router training rows split the sample's weight across them instead. - Each discretizer bin is relabelled to the child with the largest summed care reward, not the argmax of the fitted histogram. Empty bins and an unused NaN bin get dummyChild. - Current, dummy and candidate rules are all scored on the same full-credit rewards, so at lambda=0 an accepted change never lowers the care-set score. - Two-child nodes also try the exact best threshold on each feature, materialized as a depth-1 numeric discretizer checked against the scan. Closes #80 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regression TAO now relabels bins by true loss and tries the exact K=2 threshold, so the printed R^2 values move slightly. The other tutorials' printed outputs and plots are unchanged by the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # docs/tutorials/regression.ipynb
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.
Closes #80.
Problem
At a node with 3+ children, one sample can be correct in several children (two children predicting the same class), so routing is multi-label. TAO split credit
1/numCorrectacross those children. Its objective then stopped tracking accuracy, and it could accept rules (including the dummy) that lowered training accuracy. On the issue's repro, accuracy dropped from 0.6608 to 0.4487.Fix (the four parts in the issue)
ClassificationTaoAdapter::buildCareSet): on the single-output path, each expanded row carriesw / |correct children|.childCountskeeps full weight, sodummyChildis still the child that is correct for the most care weight. The multi-output path is unchanged.childRewardsgives 1 to every correct child.TaoObjective::scoreDiscretizernow maps each bin to the argmax of summedcareWeight * careRewards(ties go to the lowest index), instead of the argmax of the fitted histogram. Bins with zero care weight, including an unused trailing NaN bin, go todummyChild. Onetransformis shared by the relabel and the scoring.penalizedScore. The acceptance rules are unchanged. Atlambda_=0an accepted change can't lower the care-set score, and non-care samples score the same under every rule.TaoObjective::bestThresholdCut): an exact prefix-sum scan per feature tries both orientations. NaN care samples go to the better child. Each side must hold at leastminLeafSizecare samples, and the scan uses the splitter's1e-7tie rule. The best cut is turned into a real depth-1 numericClassificationDiscretizer, trained on the care samples labelled by side. That split is pure, so it reproduces the cut, and the code checks this and throws if not. The candidate is scored with the new publicTaoObjective::scoreBinAssignmentand competes in the existingbestSinglecomparison. Export, plotting and pickling work unchanged. The pair-candidate loop is untouched.Behaviour change for regression:
TaoObjectiveis shared, so regression TAO also uses the per-bin argmax of the true reward (-loss), and the K=2 exact cut. All existing regression TAO tests pass. A new test checks that squared and absolute training loss don't rise atlambda_=0.Tests
New tests in
tests/test_tao.py:num_partitions∈ {2,3,4} × {no weights,sample_weight, class weights} × {tao_n_runs=0, default}. It checks weighted training accuracy with the effective float32 weights;cpp/tests/test_tao_objective.cppcovers the issue's 3-child bin example (histogram (5,5,8) → child 2, but full credit (10,10,8) → child 0), the threshold scan, and itsminLeafSizeguard.Checks that the tests catch the bug:
pytest testsgives 1330 passed, 2 skipped, and every C++ Catch2 test passes (401 assertions in 46 test cases).Tutorials
I re-executed all 10 tutorials, once with the original TAO code and once with the fix, on the same environment. Only
regression.ipynbprints different numbers, and I re-executed and committed it in 5dbfde4. Regression TAO now relabels bins by true loss and also gets the K=2 exact cut, which is why it moves:regression.ipynbsquared_errorR² testabsolute_errorR² testtao.ipynbprints the same numbers: its datasets are binary with two-way splits, where parts 1–3 change nothing. Every plot is byte-identical between the two builds. Their bytes differ from the committed plots only because of the environment: the original build shows the same differences. So I left the other notebooks alone.docs/tutorials/bivariate-branching.ipynbcell 4 usestao_n_runs=0, which hid this bug. With TAO enabled on that same data, the univariate model dropped from 0.6608 to 0.4487 training accuracy onmain. With this fix it reaches 0.6621.The guarantee in
docs/api/tao.rst("With the default 0.0, weighted training accuracy / loss does not decrease") now holds, and I left it unchanged.Heads-up for anyone re-running tutorials from a worktree: the user-level
python3Jupyter kernelspec points at the main checkout's.venv, sonbconvertthere silently uses that build.Note
SGTClassifierdoes not acceptclass_weight="balanced". The string is treated as a sequence, which raises a misleading ValueError. The property test therefore uses an explicit dict with the same weights. Adding real support is out of scope here.Pre-existing bugs found while fuzzing this change. They reproduce on
mainand are not touched here:fit()(defaulttao_n_runs) andTAO_refinethen raise "Cannot use discretizer outputs without first training it", and the model is left broken.Criterion::absoluteErrorPresorteduses an absolute1e-12tolerance for the weighted-median half-weight tie. With total sample weight around 1e-12 or less, the leaf refit picks a non-median value, soabsolute_errorTAO can raise training loss.🤖 Generated with Claude Code