Fix degenerate default propensity model behavior ( #1027 ) - #1033
Conversation
jeongyoonlee
left a comment
There was a problem hiding this comment.
test_propensity_models_ihdp_regression_1027 (tests/test_propensity.py:74) calls fetch_ihdp() with no network marker and no cache guard, so the ordinary suite downloads ~16MB from fredjo.com. That contradicts pyproject.toml:95-101 — "Tests that touch the network are opt-in (pytest -m network) so the ordinary suite stays offline and a source URL moving cannot redden an unrelated PR" — and the pytestmark = pytest.mark.network plus skip-if-not-cached pattern in tests/test_benchmark_loaders.py.
Please switch to a synthetic imbalanced dataset, which drops both the fetch_ihdp call and the dependency on fredjo.com staying up; test_logistic_regression_propensity_model_cs_grid (:31-44) in the same file already builds one. Marking it network would also satisfy the convention, but then the regression stops running in normal CI.
Also use RANDOM_SEED from tests/const (already imported at :11) rather than hardcoding 42 three times.
On the change itself: the Cs 4→10 bump is not load-bearing. Measured on IHDP replication 0 with the kwargs at propensity.py:105-125, scoring="neg_log_loss" alone selects the identical C_=0.0464 (pred std 0.0771, AUC 0.722) with Cs=4, while Cs=10 under the old scoring stays degenerate (C_=1e-4, std 0.0000, AUC 0.395). Since this is a defaults change with wide blast radius, please drop the Cs bump and keep the diff to the scoring fix plus max_depth.
0a96e0f to
d2d5a02
Compare
|
Done. |
jeongyoonlee
left a comment
There was a problem hiding this comment.
The synthetic data doesn't reproduce the bug, so scoring="neg_log_loss" is untested. On test_propensity_models_imbalanced_1027's dataset (n=200, 5 features, 26% treated), master and this branch give identical results — C_=21.54, pred std 0.2255 — so both np.std(...) > 0.05 assertions pass on master. The test only fails on master via assert pm_gb.model.max_depth == 3, which restates the new default rather than testing behavior.
The collapse needs weak signal plus stronger imbalance in higher dimension. This reproduces on all five seeds I tried (0, 1, 7, 42, 2026):
rng = np.random.RandomState(RANDOM_SEED)
X = rng.normal(size=(400, 25))
logit = 0.3 * X[:, 0] + rng.normal(size=400)
treatment = (logit > np.quantile(logit, 0.90)).astype(int) # 10% treatedmaster selects C_=1e-4 (the grid floor) with pred std ≈ 0.0001; with neg_log_loss it selects C_=0.046 with std 0.028–0.059.
Please also assert the mechanism rather than a magnitude — C_ not pinned to the smallest grid value is binary and seed-stable, whereas std > 0.05 sits inside the run-to-run spread of the fixed model. And roc_auc_score(...) < 1.0 for the depth change is knife-edge: depth 8 gives exactly 1.0 in-sample and depth 3 lands around 0.995–0.999, so a bound like < 0.99 would state the intent without depending on an exact float.
dadde5f to
4f5ca31
Compare
|
Done fixing. |
jeongyoonlee
left a comment
There was a problem hiding this comment.
build (3.12) is red — assert roc_auc_score(...) < 0.99 fails since in-sample AUC is 1.0 at depth 3 too (my < 0.99 suggestion was wrong).
Please drop the whole pm_gb block: no AUC bound separates depth 3 from depth 8 on this data, and max_depth == 3 only restates the default. The C_ > 1e-4 assertions cover the fix.
4f5ca31 to
9c57452
Compare
9c57452 to
fe6dae2
Compare
|
Done Fixing. |
Proposed changes
Fixes #1027.
This PR fixes two stability issues in the default propensity models:
neg_log_lossscoring and a widerCsgrid forLogisticRegressionCV, preventingElasticNetPropensityModelandLogisticRegressionPropensityModelfrom selecting degenerate propensity scores on imbalanced data.max_depthofGradientBoostedPropensityModelfrom 8 to 3 to reduce excessive in-sample overfitting and propensity-score saturation.The changes are limited to
causalml/propensity.pyand the corresponding regression test.Before
After
Test
Before
After
Types of changes
Checklist
Further comments
The fix was verified on IHDP replication 0, where the previous defaults produced degenerate/saturated propensity scores and the updated defaults produce more varied propensity estimates with substantially lower GBM training AUC.
CI checks for Python 3.11, Python 3.12, and lint are passing.