Skip to content

feat: extend serialization to CausalTree, CausalForest, UpliftTree, U…#932

Merged
jeongyoonlee merged 3 commits into
uber:masterfrom
genrichez:feature/extend-serialization
Jul 7, 2026
Merged

feat: extend serialization to CausalTree, CausalForest, UpliftTree, U…#932
jeongyoonlee merged 3 commits into
uber:masterfrom
genrichez:feature/extend-serialization

Conversation

@genrichez

Copy link
Copy Markdown
Contributor

Closes #931

Summary

This PR extends the SerializableLearner mixin (introduced in #929 ) to Group A learners as outlined in the issue design spec.

Changes

New module: causalml/inference/serialization.py

Lifted the mixin to a neutral location so it can be used by any learner family without circular imports. Added an overridable _is_fitted() hook that defaults to sklearn's check_is_fitted but can be customized per class.

Group A learners now support save() / load():

Learner Fitted check
CausalTreeRegressor sklearn check_is_fitted (checks tree_)
CausalRandomForestRegressor sklearn check_is_fitted (checks estimators_)
UpliftTreeClassifier fitted_uplift_tree is not None
UpliftRandomForestClassifier has uplift_forest attribute
BaseDRIVLearner (and subclasses) has t_groups attribute
IVRegressor has iv_fit attribute

Backward compatibility preserved:

The old import path (from causalml.inference.meta.serialization import ...) still works via re-export.

For Cython uplift classes:

Since UpliftTreeClassifier and UpliftRandomForestClassifier are defined in a .pyx file, the mixin methods are injected at import time in causalml/inference/tree/__init__.py. This avoids modifying the Cython source while giving users the same API.

Tests

21 new tests in tests/test_serialization_extended.py:

  • Round-trip save/load for each learner family
  • Unfitted model raises ValueError
  • Class mismatch raises ValueError
  • Generic load_learner() works across families
  • Version mismatch produces warning (not error)
  • Multiple save/load cycles
  • Backward compat import path
  • Cross-family type safety

All 40 serialization tests pass (19 original + 21 new).

genrichez added 2 commits July 6, 2026 10:13
…pliftForest, IV/DRIV

Lift SerializableLearner mixin to causalml/inference/serialization.py and
add an overridable _is_fitted() hook so each learner family can define its
own fitted-state check.

Group A learners now support save/load:
- CausalTreeRegressor (sklearn check_is_fitted)
- CausalRandomForestRegressor (sklearn check_is_fitted)
- UpliftTreeClassifier (fitted_uplift_tree is not None)
- UpliftRandomForestClassifier (has uplift_forest attr)
- BaseDRIVLearner and subclasses (has t_groups attr)
- IVRegressor (has iv_fit attr)

Backward compat: old import path still works via re-export.

Closes uber#931

@jeongyoonlee jeongyoonlee left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One gap: BaseDRIVLearner gets the mixin + _is_fitted and is listed as supported, but test_serialization_extended.py only covers CausalTree/Forest, UpliftTree/Forest, and IV — no DRIV. A BaseDRIVLearner/DRIVLearner save→load→predict round-trip would close it.

Everything else checks out — the round-trips, the _is_fitted hooks, and the import-time patching of the Cython uplift classes all look correct.

@jeongyoonlee jeongyoonlee added the enhancement New feature or request label Jul 6, 2026
Addresses review feedback from jeongyoonlee: adds a driv_data fixture
and TestDRIVLearnerRoundTrip class with 4 tests covering:
- prediction equality after save/load cycle
- unfitted save raises ValueError
- generic load_learner returns correct type
- metadata fields present with correct learner_class
@genrichez

Copy link
Copy Markdown
Contributor Author

One gap: BaseDRIVLearner gets the mixin + _is_fitted and is listed as supported, but test_serialization_extended.py only covers CausalTree/Forest, UpliftTree/Forest, and IV — no DRIV. A BaseDRIVLearner/DRIVLearner save→load→predict round-trip would close it.

Everything else checks out — the round-trips, the _is_fitted hooks, and the import-time patching of the Cython uplift classes all look correct.

Thanks for the review! Added the BaseDRIVLearner round-trip tests in dd93ac8 : covers save→load→predict equality, unfitted save guard, generic load_learner dispatch, and metadata validation. All passing.

@jeongyoonlee jeongyoonlee left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for adding the BaseDRIVLearner round-trip tests — that closes the gap. Approving.

One optional nit for a follow-up (non-blocking): tests/test_serialization_extended.py hardcodes RANDOM_SEED = 42; the project convention is to import it from tests/const.py (as the cate-scoring tests do). CONTROL_NAME = 0 is fine to keep local since the causal forest needs an integer label.

@jeongyoonlee jeongyoonlee merged commit 8eaabfa into uber:master Jul 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend save/load serialization to non-meta learners (causalml.inference.*)

2 participants