Skip to content

Commit

Permalink
Fix conversion for XGBClassifier and XGBRegressor (#1157)
Browse files Browse the repository at this point in the history
Signed-off-by: xadupre <[email protected]>
  • Loading branch information
xadupre authored Jan 23, 2025
1 parent ffb17c5 commit 6652a5c
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 12 deletions.
31 changes: 19 additions & 12 deletions docs/tutorial/plot_gexternal_xgboost.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
calculate_linear_classifier_output_shapes,
calculate_linear_regressor_output_shapes,
)
from skl2onnx.convert import may_switch_bases_classes_order
from onnxmltools.convert.xgboost.operator_converters.XGBoost import convert_xgboost
from onnxmltools.convert import convert_xgboost as convert_xgboost_booster

Expand Down Expand Up @@ -93,12 +94,15 @@
# Convert again
# +++++++++++++

model_onnx = convert_sklearn(
pipe,
"pipeline_xgboost",
[("input", FloatTensorType([None, 2]))],
target_opset={"": 12, "ai.onnx.ml": 2},
)
with may_switch_bases_classes_order(XGBClassifier):
# This context should not be needed anymore once this issue
# is fixed in XGBoost.
model_onnx = convert_sklearn(
pipe,
"pipeline_xgboost",
[("input", FloatTensorType([None, 2]))],
target_opset={"": 12, "ai.onnx.ml": 2},
)

# And save.
with open("pipeline_xgboost.onnx", "wb") as f:
Expand All @@ -110,8 +114,9 @@
#
# Predictions with XGBoost.

print("predict", pipe.predict(X[:5]))
print("predict_proba", pipe.predict_proba(X[:1]))
with may_switch_bases_classes_order(XGBClassifier):
print("predict", pipe.predict(X[:5]))
print("predict_proba", pipe.predict_proba(X[:1]))

##########################
# Predictions with onnxruntime.
Expand Down Expand Up @@ -141,14 +146,16 @@
pipe = Pipeline([("scaler", StandardScaler()), ("xgb", XGBRegressor(n_estimators=3))])
pipe.fit(X_train, y_train)

print("predict", pipe.predict(X_test[:5]))
with may_switch_bases_classes_order(XGBRegressor):
print("predict", pipe.predict(X_test[:5]))

#############################
# ONNX

onx = to_onnx(
pipe, X_train.astype(numpy.float32), target_opset={"": 12, "ai.onnx.ml": 2}
)
with may_switch_bases_classes_order(XGBRegressor):
onx = to_onnx(
pipe, X_train.astype(numpy.float32), target_opset={"": 12, "ai.onnx.ml": 2}
)

sess = rt.InferenceSession(onx.SerializeToString(), providers=["CPUExecutionProvider"])
pred_onx = sess.run(None, {"X": X_test[:5].astype(numpy.float32)})
Expand Down
25 changes: 25 additions & 0 deletions skl2onnx/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import warnings
from uuid import uuid4
from contextlib import contextmanager
from typing import Callable, Dict, List, Optional, Sequence, Set, Tuple, Union
import numpy as np
import sklearn.base
Expand Down Expand Up @@ -357,3 +358,27 @@ def wrap_as_onnx_mixin(model, target_opset=None):
obj.__setstate__(state)
obj.op_version = target_opset
return obj


@contextmanager
def may_switch_bases_classes_order(cls):
"""
XGBClassifier and XGBRegressor do not inherit from those classes
in the right order. We change it when it is registered to avoid
``AttributeError: 'super' object has no attribute '__sklearn_tags__'``
to be raised.
"""
bases = cls.__bases__
if len(bases) == 2 and bases[1] in (
sklearn.base.ClassifierMixin,
sklearn.base.RegressorMixin,
):
cls.__bases__ = bases[1], bases[0]
revert = True
else:
revert = False
try:
yield
finally:
if revert:
cls.__bases__ = bases

0 comments on commit 6652a5c

Please sign in to comment.