[ENH] ExtraTree: Add random splitting via deterministic hash to support up to 2**24 possible categories - #33972
Conversation
|
Note: if you merge main/rebase, I'll be happy to take a look at the diff. The idea (deterministic hash with random seed) sounds fun and promising! |
|
Will do. Was planning on revisiting these PRs this weekend. I can probably finish this off since it's close. Are you looking at other features in the list of #33965 ? |
I commented there. But I won't have the bandwidth to really work on trees for the next few weeks. |
|
This is ready for an initial review if you want to take a quick look, and have any opinions on the open questions @cakedev0 and @lorentzenchr ? cc: @adrinjalali and @thomasjpfan if you have any quick opinions Afterwards, I can iterate and then this will be completely ready for review. |
cakedev0
left a comment
There was a problem hiding this comment.
I started reviewing without having read your "open questions" 😅
My answers:
- Yes! Let's allow >256 categories when random splitting (=> some changes to do in
_classes.py - Let's not do it for now, I'll explain why in a subsequent comment.
- just pick one completely at random! (see my reasons in a comment of this review)
I'm really excited by this PR! It's fairly simple but so powerful! 🚀
I have a couple of minor/easy-to-resolve comments plus a critical one about MAX_RANDOM_CATEGORICAL_SPLIT_ATTEMPTS.
|
Re 2 (random splits "best" splitter): I don't think we should do it for now, as it will just be too slow: 256 x n_node_samples... But let's revisit this idea once we have histogram-based splitting: this would allow adapting the same idea to get a 256 x n_categories complexity. Or let's try it in HGB first. And we should clearly not be done it in this PR:
|
|
This was all started with @adrinjalali original work. I’ll wait to see if @lorentzenchr , @thomasjpfan, @adrinjalali or perhaps @ogrisel want to take a second look. IIRC, I think we still need one more maintainer review for this non trivial change(?) but lmk if me and Arthur are sufficient 😅 |
ogrisel
left a comment
There was a problem hiding this comment.
Thanks for the PR. Here is a first pass of review.
We will need a follow-up PR to add the categorical_features argument to ExtraTrees ensemble classes.
|
|
||
|
|
||
| def random_categorical_goes_left(seed, x): | ||
| return np.array([_py_mix_uint32(seed ^ int(category)) & 1 for category in x]) |
There was a problem hiding this comment.
Please add an inline comment to explain the maths behind this line of code.
There was a problem hiding this comment.
Also, let's rename category to category_idx to make it explicit that we expected the ordinal representation of categorical values.
| # - for SPLIT_CATEGORICAL_BITSET: left_cat_bitset stores the set of | ||
| # categories that go to the left child; | ||
| # - for SPLIT_CATEGORICAL_HASH: left_cat_bitset[0] stores the hash seed. | ||
| BITSET_DTYPE_C left_cat_bitset |
There was a problem hiding this comment.
Shouldn't we rename this field to left_cat_bitset_or_hashseed?
Note that since we have to also store a new split_kind field, pickling backward compat is broken between scikit-learn versions anyways.
| n_categories=int(tree.tree_._n_categories[ftr]), | ||
| ) | ||
| elif split_kind == SPLIT_CATEGORICAL_HASH: | ||
| threshold = int(tree.tree_._left_cat_bitset[0, 0]) |
There was a problem hiding this comment.
I don't understand this line. Maybe an inline comment could help improve readability.
|
This PR also need some conflict resolution and a changelog entry. |
|
Thanks for the review!
Resolved conflicts, and the changelog entry error I don't understand. Perhaps it's because I changed an older changelog entry? I added one for this PR it seems |
adam2392
left a comment
There was a problem hiding this comment.
I added a changelog entry in doc/whats_new/upcoming_changes/sklearn.tree/33972.major-feature.rst. Unsure why the CI is still erroring.
|
The retry strategy looks good 👍 (much cleaner to implement than what I thought, nice!) |
| ('n_node_samples', np.intp), # 8 bytes (offset 72) | ||
| ('weighted_n_node_samples', np.float64), # 8 bytes (offset 80) | ||
| ('missing_go_to_left', np.uint8), # 1 byte (offset 88) | ||
| ('split_kind', np.int8), # 1 byte (offset 89) |
There was a problem hiding this comment.
I guess this extension does not matter much as long as we stay under 3*32 = 96 bytes (or even 2 * 64 = 128 bytes).
There was a problem hiding this comment.
Just added for completeness :p
| n_left = current_split.pos - start | ||
| n_right = end - current_split.pos | ||
| if n_left != 0 and n_right != 0: | ||
| break |
There was a problem hiding this comment.
I tried to investigate if it was possible to trigger an infinite loop here passing float values and declaring them as categorical values, but apparently it's not possible from the public API because of the internal ordinal encoding preprocessing.
Still, wouldn't it be safer to cap the number of retries to 100 or something (and raise a warning asking the user to report a minimal reproducer if that limit is reached)?
There was a problem hiding this comment.
You're saying cap the retries here directly within Cython? Sure I can add that
There was a problem hiding this comment.
Capped hash-seed retries at MAX_RANDOM_CATEGORICAL_SPLIT_RETRIES = 100. If that limit is reached we emit a UserWarning asking for a minimal reproducer and skip the feature. That branch is marked # pragma: no cover and left untested, since ordinal encoding makes it unreachable from the public API.
|
Does @cakedev0 approval count for the +2? heh. I'll let @lorentzenchr take a look and merge if he's happy with the changes now? As next steps, I think we can now enable in |
|
Could you please duplicate |
Done. Let me know if this is what you had in mind @ogrisel |
|
Thanks @adam2392 for the final push. Merged! |
…rt up to 2**24 possible categories (scikit-learn#33972)
Reference Issues/PRs
Follow-up to #33354 (review)
Towards: #33965
What does this implement/fix? Explain your changes.
AI usage disclosure
Used AI to find the random splitting algorithm mix uint32 function, and then help implement.
Any other comments?
Open questions:MAX_RANDOM_CATEGORICAL_SPLIT_ATTEMPTS