Repository navigation
Make SymPy factory distributions picklable - #445
Open
alejandroschuler wants to merge 1 commit into
Open
alejandroschuler wants to merge 1 commit into
alejandroschuler wants to merge 1 commit into
Conversation
make_distribution and make_sympy_log_score built their classes with type(), so every generated class reported ngboost.distns.sympy_utils as its module. Pickle, joblib and skops find a class by its module and name, so they failed for any model or predicted distribution that used Beta, BetaBernoulli, BetaBinomial, BetaBinomialEstN, LogitNormal or a user's own factory distribution. The factories now set __module__ to the module that calls them, as collections.namedtuple does, and take a module argument to override it. The new tests pickle each factory class, a predicted distribution of each, and fitted models (pickle and skops), including a distribution built in the test module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 27, 2026
This branch has not been deployed
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.
Problem
A model that uses a distribution made with the SymPy factory (#403) cannot be saved. This applies to
Beta,BetaBernoulli,BetaBinomial,BetaBinomialEstN,LogitNormaland any distribution that a user builds withmake_distribution:make_distributionandmake_sympy_log_scorebuild their classes withtype(). Every generated class therefore reportsngboost.distns.sympy_utilsas its module, where no class of that name exists. Pickle, joblib and skops find a class by its module and name, so all three fail. Releases 0.5.9, 0.5.10 and 0.5.11 have this bug. The pickling tests did not find it, because they use onlyNormal,Bernoulli,LogNormalandMultivariateNormal.I checked each way to save or copy a model, for each factory distribution, on master and on this branch:
pickle/joblibof a fitted modelskopsof a fitted modelpickleofpred_dist(X)copy.deepcopy,sklearn.clonecross_val_score(n_jobs=2)Fix
The factories now set
__module__of each generated class to the module that calls them, ascollections.namedtupleandenum.Enumdo. A newmoduleargument overrides it. The class is then found asngboost.distns.beta.Beta, or asmy_module.MyBetafor a user's own distribution. The docs now tell users to givenamethe name of the variable that holds the class.Tests
tests/test_pickling.pynow does these checks for every factory distribution, and for a distribution built in the test module itself:Without the fix, all 10 new tests fail. With it, the full suite passes (
pytest --slow: 179 passed, 2 xfailed, 22 xpassed).Not in this PR
pickle.dumps(pred_dist)also fails for the hand-written distributions such asNormal, with aRecursionErrorfrom__getattr__. Add mu and sigma to LogNormal and raise AttributeError for unknown names #433 already fixes that.LogitNormal.predictis a Monte Carlo mean of 1000 draws. Two calls on the same model can therefore give different values (up to 0.05 apart in my check). For this reason the new model test compares the predicted parameters, notpredict().🤖 Generated with Claude Code