feat: extend serialization to CausalTree, CausalForest, UpliftTree, U…#932
Conversation
…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
left a comment
There was a problem hiding this comment.
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.
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
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
left a comment
There was a problem hiding this comment.
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.
Closes #931
Summary
This PR extends the
SerializableLearnermixin (introduced in #929 ) to Group A learners as outlined in the issue design spec.Changes
New module:
causalml/inference/serialization.pyLifted 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'scheck_is_fittedbut can be customized per class.Group A learners now support
save()/load():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:load_learner()works across familiesAll 40 serialization tests pass (19 original + 21 new).