Skip to content

Export more metadata from export_model() - #684

Merged
chyunsu3 merged 13 commits into
dmlc:mainlinefrom
chyunsu3:impove_metadata_sklearn_export
Aug 20, 2026
Merged

Export more metadata from export_model()#684
chyunsu3 merged 13 commits into
dmlc:mainlinefrom
chyunsu3:impove_metadata_sklearn_export

Conversation

@chyunsu3

@chyunsu3 chyunsu3 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator
  • Export n_node_samples and weighted_n_node_samples fields when exporting Treelite model as a scikit-learn model.
  • Use correct estimator classes when exporting isolation forests.
  • Add test coverage for round-trip.
  • Fix a bug in data_count() setter in the model builder API.
  • Persist offset_ in the Treelite model object.

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.04651% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.93%. Comparing base (9583c7b) to head (5c25601).

Files with missing lines Patch % Lines
python/treelite/sklearn/exporter.py 81.48% 5 Missing ⚠️
python/treelite/model.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           mainline     #684      +/-   ##
============================================
- Coverage     84.52%   83.93%   -0.60%     
============================================
  Files            75       75              
  Lines          6823     6790      -33     
  Branches        557      557              
============================================
- Hits           5767     5699      -68     
- Misses         1056     1091      +35     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chyunsu3

Copy link
Copy Markdown
Collaborator Author

@JulienAu Can you review this pull request?

@JulienAu JulienAu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chyunsu3 Thanks for the quick turnaround on this. I reviewed the diff and ran the branch's exporter against the compiled treelite 4.7.0 runtime (the changed files are pure Python), on both sklearn round trips and cuML model bytes.

The TreeliteModelBuilderDataCount fix is correct, and the sklearn round trip works as advertised: data_count populated (root equals max_samples_), correct estimator classes, n_node_samples preserved exactly.

Two things worth considering:

  1. The exported IsolationForest cannot score: only estimators_, n_outputs_, n_features_in_ and _sklearn_version are set, so score_samples raises AttributeError: '_max_features'. Most of the missing state is derivable from the trees, and I verified that populating it plus the default offset_ = -0.5 gives exact score parity on the round trip. offset_ itself is not recoverable from the serialized model (base_scores stays 0), so contamination-fitted models would predict differently. Whichever contract you prefer (populate with a documented -0.5 assumption, or keep the export structural with an explicit error on scoring), a score_samples call in test_iforest_round_trip would pin it down.

  2. Present-but-zero data_count flows through silently. cuML's isolation forest export currently emits exactly that (checked on nightly bytes: every node present with value 0, task_type kRegressor), which would export as n_node_samples equal to 0 everywhere and break scoring downstream. Since a reachable node can never hold zero samples, failing loudly on non-positive present values seems safer. It also means NVIDIA/cuml#8483 can only adopt exported.estimators_ once the cuML C++ export writes real counts and tags kIsolationForest; happy to coordinate that follow-up.

Minor: np.bool is missing on numpy 1.24 through 1.26 and the package floor allows them; np.bool_ works everywhere.

@chyunsu3

Copy link
Copy Markdown
Collaborator Author

@JulienAu Can you take another look?

  1. Now the exported IsolationForest can score. Attributes including _max_samples, _max_features, and estimators_features_ are now exported. Importantly, the offset_ value is now stored as part of the Treelite model object, so you can now recover offset_ from the serialized model.
offset = treelite_model.attributes.get("sklearn_iforest_offset", -0.5)
  1. As for the zero data_count, I think cuML should be updated to export the correct data_count and data_count_present when serializing cuML IsolationForest as Treelite.

@JulienAu JulienAu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took another look.

Storing offset_ as a model attribute is a nice solution, I confirmed it survives serialize_bytes and deserialize_bytes, which is the path cuML will use. Scoring parity is exact on my side for the default and contamination cases.

One real issue: with bootstrap=True the scores drift (about 1e-3 in my runs). sklearn fits the bagged trees with sample weights instead of repeating rows, so the root n_node_samples counts distinct rows (63 in my run) and the derived _max_samples comes out wrong. The root weighted_n_node_samples is exactly max_samples on every tree, so I put the fix as a suggestion: with it I get exact parity in every configuration I tested (bootstrap on and off, with and without user sample_weight).

Small note: the public max_samples_ attribute is not set. Scoring only needs the private one, but users may look for it.

Comment thread python/treelite/sklearn/exporter.py Outdated
Comment thread python/treelite/sklearn/importer.py Outdated
@JulienAu

Copy link
Copy Markdown

Forgot to answer your data_count point: that matches what csadorf already scoped in NVIDIA/cuml#8420, populate data_count in the Treelite export, then simplify the Python side.

chyunsu3 and others added 4 commits August 19, 2026 02:34
Co-authored-by:  Julien Audibert <audibert.julien.pro@gmail.com>
Co-authored-by:  Julien Audibert <audibert.julien.pro@gmail.com>
@chyunsu3

Copy link
Copy Markdown
Collaborator Author

@JulienAu Can you look at it one last time? I addressed all your feedback.

@JulienAu JulienAu left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran my test bench once more on the latest head: exact parity everywhere including bootstrap, offset recovery survives serialize_bytes, and max_samples_ is set. Looks good to me, thanks for the quick turnaround.

@chyunsu3
chyunsu3 merged commit b249a42 into dmlc:mainline Aug 20, 2026
19 of 20 checks passed
@chyunsu3
chyunsu3 deleted the impove_metadata_sklearn_export branch August 20, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants