fix sklearn raw model extraction#9050
Conversation
Signed-off-by: Serena Ruan <[email protected]>
Signed-off-by: Serena Ruan <[email protected]>
|
Documentation preview for 9f2b21e will be available here when this CircleCI job completes successfully. More info
|
Signed-off-by: Serena Ruan <[email protected]>
| def test_extract_raw_model_for_sklearn(): | ||
| mlflow_model = Model() | ||
| model_impl = Pipeline([("predict", LogisticRegression())]) | ||
| mlflow.pyfunc.add_to_model(mlflow_model, loader_module="mlflow.sklearn") | ||
| pyfunc_model = mlflow.pyfunc.PyFuncModel(model_meta=mlflow_model, model_impl=model_impl) | ||
| _, raw_model = _extract_raw_model(pyfunc_model) | ||
| assert raw_model == model_impl | ||
|
|
||
| model_impl = _SklearnModelWrapper(model_impl) | ||
| pyfunc_model = mlflow.pyfunc.PyFuncModel(model_meta=mlflow_model, model_impl=model_impl) | ||
| _, raw_model = _extract_raw_model(pyfunc_model) | ||
| assert raw_model == model_impl |
There was a problem hiding this comment.
Instead of testing _extract_raw_model, can we test mlflow.evaluate?
There was a problem hiding this comment.
Just because _extract_raw_model works fine doesn't mean it works fine in mlflow.evaluate.
There was a problem hiding this comment.
In general, we should test a public API, instead of implementatio details like _extract_raw_model.
|
mlflow/mlflow/models/evaluation/default_evaluator.py Lines 722 to 732 in 797cdf5 evaluate doesn't log those images. Should we return .sklearn_model or update the logic in default_evaluator to be compatible with the wrapper instead?
|
Signed-off-by: Serena Ruan <[email protected]>
|
Does |
Signed-off-by: Serena Ruan <[email protected]>
| :param data: Model input data. | ||
| :param params: Additional parameters to pass to the model for inference. | ||
| def predict_proba(self, *args, **kwargs): | ||
| if hasattr(self.sklearn_model, "predict_proba"): |
There was a problem hiding this comment.
This means this function returns None, which can lead to a hard-to-debug error. Just throwing sklearn_model has no attribute predict_proba might easier to debug.
There was a problem hiding this comment.
Yes, for this test: https://github.com/mlflow/mlflow/actions/runs/5527963781/jobs/10084241551#step:8:3032
on commit e06bf73
FAILED tests/evaluate/test_default_evaluator.py::test_svm_classifier_evaluation_disable_logging_metrics_and_artifacts - AttributeError: 'LinearSVC' object has no attribute 'predict_proba'
There was a problem hiding this comment.
Interesting, how did this test pass before you made the inference params change?
There was a problem hiding this comment.
Because self.predict_fn, self.predict_proba_fn = _extract_predict_fn(model, self.raw_model). If the raw_model is the underlying sklearn_model, then from here it is the sklearn model's predict_proba function, instead of the wrapper's predict_proba. But if raw_model is a _SklearnModelWrapper, then self.predict_prob_fn always exist, we might need to update the logic here again since predict_proba_fn = getattr(raw_model, "predict_proba", None) is not None even the function itself returns None 🤔
There was a problem hiding this comment.
Or it's fine, because
if self.predict_proba_fn is not None:
self.y_probs = self.predict_proba_fn(self.X.copy_to_avoid_mutation())
else:
self.y_probs = None
in the first situation it returns None as well
| def test_extract_raw_model_for_sklearn(): | ||
| mlflow_model = Model() | ||
| model_impl = Pipeline([("predict", LogisticRegression())]) | ||
| mlflow.pyfunc.add_to_model(mlflow_model, loader_module="mlflow.sklearn") | ||
| pyfunc_model = mlflow.pyfunc.PyFuncModel(model_meta=mlflow_model, model_impl=model_impl) | ||
| _, raw_model = _extract_raw_model(pyfunc_model) | ||
| assert raw_model == model_impl | ||
|
|
||
| model_impl = _SklearnModelWrapper(model_impl) | ||
| pyfunc_model = mlflow.pyfunc.PyFuncModel(model_meta=mlflow_model, model_impl=model_impl) | ||
| _, raw_model = _extract_raw_model(pyfunc_model) | ||
| assert raw_model == model_impl.sklearn_model |
There was a problem hiding this comment.
I don't think we need this test.
Signed-off-by: Serena Ruan <[email protected]>
| return model_loader_module, model._model_impl.sklearn_model | ||
| if isinstance(model._model_impl, _SklearnModelWrapper): | ||
| return model_loader_module, model._model_impl.sklearn_model | ||
| return model_loader_module, model._model_impl |
There was a problem hiding this comment.
What's the case it is not _SklearnModelWrapper instance ? sklearn model._model_impl should be returned by sklearn flavor _load_pyfunc
There was a problem hiding this comment.
There's a test case in universe/automl that directly construct a PyFuncModel by passing _model_impl which uses sklearn Pipeline model.
There was a problem hiding this comment.
emm... is it a legal usage ?
There was a problem hiding this comment.
I guess other flavor might have similar issue if we directly construct _model_impl, but _model_impl is internal attribute managed by pyfunc if I understand it correctly
There was a problem hiding this comment.
I think if someone want to customize _model_impl attribute in sklearn pyfunc model,
then it should not be regarded as sklearn pyfunc model but he can create a custom pyfunc model that inherits PythonModel base class.
|
Will not merge this PR to master, the failing test will be fixed automatically due to the revert PR: #9059 |
|
As we discussed, in _extract_raw_model function, we still need to check mlflow/mlflow/models/evaluation/default_evaluator.py Lines 83 to 84 in db6a0a6 We can refactor this part later if we wanna add a Base wrapper :) |
| if isinstance(model._model_impl, _SklearnModelWrapper): | ||
| return model_loader_module, model._model_impl.sklearn_model |
There was a problem hiding this comment.
Can we add a comment on why we need this?
There was a problem hiding this comment.
I'm curious about what happens if we evaluate a non-sklearn model like xgboost.
There was a problem hiding this comment.
I think there're two problems:
- The raw_model will never be xgboost.XGBModel as it will be the wrapper, and predict_prob_fn will be None for all wrappers as they're not implemented.
mlflow/mlflow/models/evaluation/default_evaluator.py
Lines 93 to 103 in 4f76275
- Looks like
mlflow/mlflow/models/evaluation/default_evaluator.py
Lines 730 to 732 in 4f76275
shap.Explainerdoesn't accept wrappers
Signed-off-by: Serena Ruan <[email protected]>

Related Issues/PRs
Fix
What changes are proposed in this pull request?
If model_impl is not a _SklearnModelWrapper we directly return it.
How is this patch tested?
Does this PR change the documentation?
Release Notes
Is this a user-facing change?
(Details in 1-2 sentences. You can just refer to another PR with a description if this PR is part of a larger change.)
What component(s), interfaces, languages, and integrations does this PR affect?
Components
area/artifacts: Artifact stores and artifact loggingarea/build: Build and test infrastructure for MLflowarea/docs: MLflow documentation pagesarea/examples: Example codearea/model-registry: Model Registry service, APIs, and the fluent client calls for Model Registryarea/models: MLmodel format, model serialization/deserialization, flavorsarea/recipes: Recipes, Recipe APIs, Recipe configs, Recipe Templatesarea/projects: MLproject format, project running backendsarea/scoring: MLflow Model server, model deployment tools, Spark UDFsarea/server-infra: MLflow Tracking server backendarea/tracking: Tracking Service, tracking client APIs, autologgingInterface
area/uiux: Front-end, user experience, plotting, JavaScript, JavaScript dev serverarea/docker: Docker use across MLflow's components, such as MLflow Projects and MLflow Modelsarea/sqlalchemy: Use of SQLAlchemy in the Tracking Service or Model Registryarea/windows: Windows supportLanguage
language/r: R APIs and clientslanguage/java: Java APIs and clientslanguage/new: Proposals for new client languagesIntegrations
integrations/azure: Azure and Azure ML integrationsintegrations/sagemaker: SageMaker integrationsintegrations/databricks: Databricks integrationsHow should the PR be classified in the release notes? Choose one:
rn/breaking-change- The PR will be mentioned in the "Breaking Changes" sectionrn/none- No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" sectionrn/feature- A new user-facing feature worth mentioning in the release notesrn/bug-fix- A user-facing bug fix worth mentioning in the release notesrn/documentation- A user-facing documentation change worth mentioning in the release notes