Repository navigation
Conversation
This is the first step in making base tree "regressor" be used by all the tree classifiers. Next step is to make the implementation generic over the used Criterion with callers deciding it. Add a Criterion trait with MSE and Gini implementations in src/tree/criterion.rs. Use MeanStats in BaseTreeRegressor in place of explicit sum/mass bookkeeping. - Store node stats in NodeVisitor; remove true/false_child_output - Keep scratch stats in SplitWorkspace to avoid new allocations - Rank splits by proxy improvement; store the real impurity improvement in split_score - Pass the sample count to find_best_cutoff and check min_samples_split at the caller
Make sure no leaf has a split_core. Add tests to check this. Also check that no "proxy" values are leftover by verifying that the sum of all split_scores adds up to the total impurity decrease.
- Compute the MSE improvement as (S_T·M_F − S_F·M_T)² / (M_T·M_F·M_P). This prevents cancellation for targets with a large offset. - Remove rows with mass 0 in grow(). Zero-weight rows no longer count toward min_samples_leaf/min_samples_split and do not move thresholds. - Remove the MASS_EPS clamp from difference(). - In split(), add the false side directly instead of parent − true. - Add tests for target-offset invariance and zero-weight rows.
Add compute_feature_importances (mean decrease in impurity, as in scikit-learn) to DecisionTreeRegressor, RandomForestRegressor and ExtraTreesRegressor. The forests normalize the importances of each tree, take the mean and normalize again. The fitted models store new fields (root_mass, num_features), so models serialized with an earlier version do not deserialize.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #497 +/- ##
===========================================
+ Coverage 43.97% 85.64% +41.67%
===========================================
Files 85 97 +12
Lines 7281 9456 +2175
===========================================
+ Hits 3202 8099 +4897
+ Misses 4079 1357 -2722 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Fixes #496
Follow-up to #492. This PR is based on the
use-criterionbranch. Merge that PR first. Before then, the diff here also shows its commits.Checklist
use-criterionCurrent behaviour
The regression trees and forests do not give feature importances. The tree nodes keep a
split_score(the weighted decrease of the squared error for the split), but no public method uses it.New expected behaviour
A new
compute_feature_importancesmethod calculates the mean decrease in impurity (MDI), as in scikit-learn (feature_importances_).DecisionTreeRegressor::compute_feature_importances(normalize: bool) -> Result<Vec<f64>, Failed>split_scoreof all the nodes that split on that feature.normalize = false: divide by the total sample mass at the root. The value of a feature is then the decrease of the weighted MSE that comes from its splits. If the tree grows to full depth and all leaves are pure, the sum is the weighted variance ofy.normalize = true: divide by the sum, so the importances add up to 1. If no split decreases the impurity, all values stay 0.RandomForestRegressor::compute_feature_importances()andExtraTreesRegressor::compute_feature_importances()Failed::predict.BaseTreeRegressor::compute_feature_importances(normalize, &mut [f64])writes into a slice that the caller supplies. The forest uses one scratch buffer for all its trees, so it does not allocate a vector for each tree.To support this, the fitted models store some new fields:
BaseTreeRegressor.root_mass: the total sample mass at the root (the sum of bootstrap counts × sample weights).DecisionTreeRegressor.num_featuresandBaseForestRegressor.num_features: the number of columns in the training data.Breaking change (serde)
Because of the new fields, models that were serialized with an earlier version cannot be deserialized. Fit these models again.
CHANGELOG.mdrecords this.Tests
[1, 2, 5, 6]gives importance(17 − 0.5 − 0.5) / 4 = 4, and1when normalized.y(BestandRandomsplitter, with and without weights).max_depth = 1or constanty), all importances are 0.root_massandnum_featuresare restored.Change logs
Added
tree,ensemble:DecisionTreeRegressor,RandomForestRegressorandExtraTreesRegressorget acompute_feature_importancesmethod.Changed
tree,ensemble: the fittedDecisionTreeRegressor,RandomForestRegressorandExtraTreesRegressorstore new fields that the feature importances use. Models serialized with an earlier version cannot be deserialized.Text of PR was written by an LLM.