[MT] CPU implementation of partition-based categorical split.#12299
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the CPU multi-target histogram split evaluator to support partition-based categorical splits (in addition to one-hot), and adds/adjusts tests to validate the new behavior across configurations.
Changes:
- Implement partition-based categorical split enumeration for
HistMultiEvaluator(multi-target CPU hist). - Refactor and expand C++ unit tests to cover both one-hot and partition categorical splits (including missing-value behavior).
- Adjust Python categorical OHE test logic to conditionally run the partition-based comparison depending on device + multi-target support.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/cpp/tree/hist/test_evaluate_splits.cc | Refactors multi-target evaluator test setup into a fixture and adds a new partition-split test. |
| src/tree/hist/evaluate_splits.h | Adds partition-based categorical split support to HistMultiEvaluator and small style/type tweaks. |
| python-package/xgboost/testing/updater.py | Updates typing for device and gates partition-based check by device/multi-target support. |
Comments suppressed due to low confidence (1)
src/tree/hist/evaluate_splits.h:168
- The partition-split doc comment still contains a typo ("pseudo-cateogry") and awkward grammar, which makes the description harder to follow. Please correct the spelling/wording while you’re touching this block.
* @brief Enumerate with partition-based splits.
*
* The implementation is different from LightGBM. Firstly we don't have a
* pseudo-cateogry for missing value, instead of we make 2 complete scans over the
* histogram. Secondly, both scan directions generate splits in the same
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
RAMitchell
left a comment
There was a problem hiding this comment.
Do you think we can refine the variable naming around n_targets? It has a complicated meaning because n_targets can be 1 while the model output targets can be > 1. What if you use leaf_n_targets in the context of vector leaf?
Its not really documented in the code or elsewhere that this projection is a heuristic as compared to the scalar exact solution. I'd like to leave something to future developers as to how we got here.
Whats nice about the projection used is that if n_targets=1 you get the same ordering (or the reverse ordering if parent weight is negative) of the scalar sorting. So this projection is a generalisation of the scalar case (if weight is not zero).
So if weight zero you just get the default ordering right? That seens reasonable. Although it breaks the generalisation above.
We can make more verbose names across the codebase.
Yeah, we can recover the single-target sorting, and we can interpret the projection in a meaningful way
I will write the document with all the math once we have the complete implementation.
Yes, that's the stable_sort. |
Ref: #9043
The following datasets are from UCI, categorical features are forced to use partition-based splits. The comparison is between scalar trees (one tree per target) and vector leaf.
The two features
Wilderness_AreaandSoil_Typein thecovtypeare recovered from OHE results.We might run into issues with zero-weight parent nodes, and there are fallback strategies, but I don't want to make things too complicated at the moment, it's an approximation anyway.