Skip to content

Commit 12b963a

Browse files
razdoburdinnapetrovAlexsandruss
authored
Model builders API update (#1320)
--------- Co-authored-by: Dmitry Razdoburdin <> Co-authored-by: Nikolay Petrov <nikolay.a.petrov@intel.com> Co-authored-by: Alexander Andreev <alexander.andreev@intel.com>
1 parent 27d3f39 commit 12b963a

14 files changed

Lines changed: 352 additions & 107 deletions

daal4py/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@
4949
'[c]sh/psxevars.[c]sh may solve the issue.\n')
5050

5151
raise
52+
53+
from . import mb
54+
from . import sklearn
55+
56+
__all__ = ['mb', 'sklearn']

daal4py/mb/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env python
2+
#===============================================================================
3+
# Copyright 2023 Intel Corporation
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#===============================================================================
17+
18+
from .model_builders import GBTDAALBaseModel, convert_model
19+
20+
__all__ = ['GBTDAALBaseModel', 'convert_model']

daal4py/mb/model_builders.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#===============================================================================
2+
# Copyright 2023 Intel Corporation
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#===============================================================================
16+
17+
# daal4py Model builders API
18+
19+
import numpy as np
20+
import daal4py as d4p
21+
22+
try:
23+
from pandas import DataFrame
24+
from pandas.core.dtypes.cast import find_common_type
25+
pandas_is_imported = True
26+
except (ImportError, ModuleNotFoundError):
27+
pandas_is_imported = False
28+
29+
30+
def parse_dtype(dt):
31+
if dt == np.double:
32+
return "double"
33+
if dt == np.single:
34+
return "float"
35+
raise ValueError(f"Input array has unexpected dtype = {dt}")
36+
37+
38+
def getFPType(X):
39+
if pandas_is_imported:
40+
if isinstance(X, DataFrame):
41+
dt = find_common_type(X.dtypes.tolist())
42+
return parse_dtype(dt)
43+
44+
dt = getattr(X, 'dtype', None)
45+
return parse_dtype(dt)
46+
47+
48+
class GBTDAALBaseModel:
49+
def _get_params_from_lightgbm(self, params):
50+
self.n_classes_ = params["num_tree_per_iteration"]
51+
objective_fun = params["objective"]
52+
if self.n_classes_ <= 2:
53+
if "binary" in objective_fun: # nClasses == 1
54+
self.n_classes_ = 2
55+
56+
self.n_features_in_ = params["max_feature_idx"] + 1
57+
58+
def _get_params_from_xgboost(self, params):
59+
self.n_classes_ = int(params["learner"]["learner_model_param"]["num_class"])
60+
objective_fun = params["learner"]["learner_train_param"]["objective"]
61+
if self.n_classes_ <= 2:
62+
if objective_fun in ["binary:logistic", "binary:logitraw"]:
63+
self.n_classes_ = 2
64+
65+
self.n_features_in_ = int(params["learner"]["learner_model_param"]["num_feature"])
66+
67+
def _get_params_from_catboost(self, params):
68+
if 'class_params' in params['model_info']:
69+
self.n_classes_ = len(params['model_info']['class_params']['class_to_label'])
70+
self.n_features_in_ = len(params['features_info']['float_features'])
71+
72+
def _convert_model_from_lightgbm(self, booster):
73+
lgbm_params = d4p.get_lightgbm_params(booster)
74+
self.daal_model_ = d4p.get_gbt_model_from_lightgbm(booster, lgbm_params)
75+
self._get_params_from_lightgbm(lgbm_params)
76+
77+
def _convert_model_from_xgboost(self, booster):
78+
xgb_params = d4p.get_xgboost_params(booster)
79+
self.daal_model_ = d4p.get_gbt_model_from_xgboost(booster, xgb_params)
80+
self._get_params_from_xgboost(xgb_params)
81+
82+
def _convert_model_from_catboost(self, booster):
83+
catboost_params = d4p.get_catboost_params(booster)
84+
self.daal_model_ = d4p.get_gbt_model_from_catboost(booster)
85+
self._get_params_from_catboost(catboost_params)
86+
87+
def _convert_model(self, model):
88+
(submodule_name, class_name) = (model.__class__.__module__,
89+
model.__class__.__name__)
90+
self_class_name = self.__class__.__name__
91+
92+
# Build GBTDAALClassifier from LightGBM
93+
if (submodule_name, class_name) == ("lightgbm.sklearn", "LGBMClassifier"):
94+
if self_class_name == "GBTDAALClassifier":
95+
self._convert_model_from_lightgbm(model.booster_)
96+
else:
97+
raise TypeError(f"Only GBTDAALClassifier can be created from\
98+
{submodule_name}.{class_name} (got {self_class_name})")
99+
# Build GBTDAALClassifier from XGBoost
100+
elif (submodule_name, class_name) == ("xgboost.sklearn", "XGBClassifier"):
101+
if self_class_name == "GBTDAALClassifier":
102+
self._convert_model_from_xgboost(model.get_booster())
103+
else:
104+
raise TypeError(f"Only GBTDAALClassifier can be created from\
105+
{submodule_name}.{class_name} (got {self_class_name})")
106+
# Build GBTDAALClassifier from CatBoost
107+
elif (submodule_name, class_name) == ("catboost.core", "CatBoostClassifier"):
108+
if self_class_name == "GBTDAALClassifier":
109+
self._convert_model_from_catboost(model)
110+
else:
111+
raise TypeError(f"Only GBTDAALClassifier can be created from\
112+
{submodule_name}.{class_name} (got {self_class_name})")
113+
# Build GBTDAALRegressor from LightGBM
114+
elif (submodule_name, class_name) == ("lightgbm.sklearn", "LGBMRegressor"):
115+
if self_class_name == "GBTDAALRegressor":
116+
self._convert_model_from_lightgbm(model.booster_)
117+
else:
118+
raise TypeError(f"Only GBTDAALRegressor can be created from\
119+
{submodule_name}.{class_name} (got {self_class_name})")
120+
# Build GBTDAALRegressor from XGBoost
121+
elif (submodule_name, class_name) == ("xgboost.sklearn", "XGBRegressor"):
122+
if self_class_name == "GBTDAALRegressor":
123+
self._convert_model_from_xgboost(model.get_booster())
124+
else:
125+
raise TypeError(f"Only GBTDAALRegressor can be created from\
126+
{submodule_name}.{class_name} (got {self_class_name})")
127+
# Build GBTDAALRegressor from CatBoost
128+
elif (submodule_name, class_name) == ("catboost.core", "CatBoostRegressor"):
129+
if self_class_name == "GBTDAALRegressor":
130+
self._convert_model_from_catboost(model)
131+
else:
132+
raise TypeError(f"Only GBTDAALRegressor can be created from\
133+
{submodule_name}.{class_name} (got {self_class_name})")
134+
# Build GBTDAALModel from LightGBM
135+
elif (submodule_name, class_name) == ("lightgbm.basic", "Booster"):
136+
if self_class_name == "GBTDAALModel":
137+
self._convert_model_from_lightgbm(model)
138+
else:
139+
raise TypeError(f"Only GBTDAALModel can be created from\
140+
{submodule_name}.{class_name} (got {self_class_name})")
141+
# Build GBTDAALModel from XGBoost
142+
elif (submodule_name, class_name) == ("xgboost.core", "Booster"):
143+
if self_class_name == "GBTDAALModel":
144+
self._convert_model_from_xgboost(model)
145+
else:
146+
raise TypeError(f"Only GBTDAALModel can be created from\
147+
{submodule_name}.{class_name} (got {self_class_name})")
148+
# Build GBTDAALModel from CatBoost
149+
elif (submodule_name, class_name) == ("catboost.core", "CatBoost"):
150+
if self_class_name == "GBTDAALModel":
151+
self._convert_model_from_catboost(model)
152+
else:
153+
raise TypeError(f"Only GBTDAALModel can be created from\
154+
{submodule_name}.{class_name} (got {self_class_name})")
155+
else:
156+
raise TypeError(f"Unknown model format {submodule_name}.{class_name}")
157+
158+
def _predict_classification(self, X, fptype, resultsToEvaluate):
159+
if X.shape[1] != self.n_features_in_:
160+
raise ValueError('Shape of input is different from what was seen in `fit`')
161+
162+
if not hasattr(self, 'daal_model_'):
163+
raise ValueError((
164+
"The class {} instance does not have 'daal_model_' attribute set. "
165+
"Call 'fit' with appropriate arguments before using this method.")
166+
.format(type(self).__name__))
167+
168+
# Prediction
169+
predict_algo = d4p.gbt_classification_prediction(
170+
fptype=fptype,
171+
nClasses=self.n_classes_,
172+
resultsToEvaluate=resultsToEvaluate)
173+
predict_result = predict_algo.compute(X, self.daal_model_)
174+
175+
if resultsToEvaluate == "computeClassLabels":
176+
return predict_result.prediction.ravel().astype(np.int64, copy=False)
177+
else:
178+
return predict_result.probabilities
179+
180+
def _predict_regression(self, X, fptype):
181+
if X.shape[1] != self.n_features_in_:
182+
raise ValueError('Shape of input is different from what was seen in `fit`')
183+
184+
if not hasattr(self, 'daal_model_'):
185+
raise ValueError((
186+
"The class {} instance does not have 'daal_model_' attribute set. "
187+
"Call 'fit' with appropriate arguments before using this method.").format(
188+
type(self).__name__))
189+
190+
# Prediction
191+
predict_algo = d4p.gbt_regression_prediction(fptype=fptype)
192+
predict_result = predict_algo.compute(X, self.daal_model_)
193+
194+
return predict_result.prediction.ravel()
195+
196+
197+
class GBTDAALModel(GBTDAALBaseModel):
198+
def __init__(self):
199+
pass
200+
201+
def predict(self, X):
202+
fptype = getFPType(X)
203+
if self._is_regression:
204+
return self._predict_regression(X, fptype)
205+
else:
206+
return self._predict_classification(X, fptype, "computeClassLabels")
207+
208+
def predict_proba(self, X):
209+
fptype = getFPType(X)
210+
if self._is_regression:
211+
raise NotImplementedError("Can't predict probabilities for regression task")
212+
else:
213+
return self._predict_classification(X, fptype, "computeClassProbabilities")
214+
215+
216+
def convert_model(model):
217+
gbm = GBTDAALModel()
218+
gbm._convert_model(model)
219+
220+
gbm._is_regression = isinstance(gbm.daal_model_, d4p.gbt_regression_model)
221+
222+
return gbm

daal4py/sklearn/ensemble/GBTDAAL.py

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .._utils import getFPType
2828

2929

30-
class GBTDAALBase(BaseEstimator):
30+
class GBTDAALBase(BaseEstimator, d4p.mb.GBTDAALBaseModel):
3131
def __init__(self,
3232
split_method='inexact',
3333
max_iterations=50,
@@ -101,6 +101,11 @@ def _check_params(self):
101101
raise ValueError('Parameter "min_bin_size" must be '
102102
'non-zero positive integer value.')
103103

104+
allow_nan_ = False
105+
106+
def _more_tags(self):
107+
return {"allow_nan": self.allow_nan_}
108+
104109

105110
class GBTDAALClassifier(GBTDAALBase, ClassifierMixin):
106111
def fit(self, X, y):
@@ -165,41 +170,28 @@ def fit(self, X, y):
165170
return self
166171

167172
def _predict(self, X, resultsToEvaluate):
173+
# Input validation
174+
if not self.allow_nan_:
175+
X = check_array(X, dtype=[np.single, np.double])
176+
else:
177+
X = check_array(X, dtype=[np.single, np.double], force_all_finite='allow-nan')
178+
168179
# Check is fit had been called
169180
check_is_fitted(self, ['n_features_in_', 'n_classes_'])
170181

171-
# Input validation
172-
X = check_array(X, dtype=[np.single, np.double])
173-
if X.shape[1] != self.n_features_in_:
174-
raise ValueError('Shape of input is different from what was seen in `fit`')
175-
176182
# Trivial case
177183
if self.n_classes_ == 1:
178184
return np.full(X.shape[0], self.classes_[0])
179185

180-
if not hasattr(self, 'daal_model_'):
181-
raise ValueError((
182-
"The class {} instance does not have 'daal_model_' attribute set. "
183-
"Call 'fit' with appropriate arguments before using this method.").format(
184-
type(self).__name__))
185-
186-
# Define type of data
187186
fptype = getFPType(X)
188-
189-
# Prediction
190-
predict_algo = d4p.gbt_classification_prediction(
191-
fptype=fptype,
192-
nClasses=self.n_classes_,
193-
resultsToEvaluate=resultsToEvaluate)
194-
predict_result = predict_algo.compute(X, self.daal_model_)
187+
predict_result = self._predict_classification(X, fptype, resultsToEvaluate)
195188

196189
if resultsToEvaluate == "computeClassLabels":
197190
# Decode labels
198191
le = preprocessing.LabelEncoder()
199192
le.classes_ = self.classes_
200-
return le.inverse_transform(
201-
predict_result.prediction.ravel().astype(np.int64, copy=False))
202-
return predict_result.probabilities
193+
return le.inverse_transform(predict_result)
194+
return predict_result
203195

204196
def predict(self, X):
205197
return self._predict(X, "computeClassLabels")
@@ -218,6 +210,14 @@ def predict_log_proba(self, X):
218210

219211
return proba
220212

213+
def convert_model(model):
214+
gbm = GBTDAALClassifier()
215+
gbm._convert_model(model)
216+
217+
gbm.classes_ = model.classes_
218+
gbm.allow_nan_ = True
219+
return gbm
220+
221221

222222
class GBTDAALRegressor(GBTDAALBase, RegressorMixin):
223223
def fit(self, X, y):
@@ -264,25 +264,21 @@ def fit(self, X, y):
264264
return self
265265

266266
def predict(self, X):
267-
# Check is fit had been called
268-
check_is_fitted(self, ['n_features_in_'])
269-
270267
# Input validation
271-
X = check_array(X, dtype=[np.single, np.double])
272-
if X.shape[1] != self.n_features_in_:
273-
raise ValueError('Shape of input is different from what was seen in `fit`')
268+
if not self.allow_nan_:
269+
X = check_array(X, dtype=[np.single, np.double])
270+
else:
271+
X = check_array(X, dtype=[np.single, np.double], force_all_finite='allow-nan')
274272

275-
if not hasattr(self, 'daal_model_'):
276-
raise ValueError((
277-
"The class {} instance does not have 'daal_model_' attribute set. "
278-
"Call 'fit' with appropriate arguments before using this method.").format(
279-
type(self).__name__))
273+
# Check is fit had been called
274+
check_is_fitted(self, ['n_features_in_'])
280275

281-
# Define type of data
282276
fptype = getFPType(X)
277+
return self._predict_regression(X, fptype)
283278

284-
# Prediction
285-
predict_algo = d4p.gbt_regression_prediction(fptype=fptype)
286-
predict_result = predict_algo.compute(X, self.daal_model_)
279+
def convert_model(model):
280+
gbm = GBTDAALRegressor()
281+
gbm._convert_model(model)
287282

288-
return predict_result.prediction.ravel()
283+
gbm.allow_nan_ = True
284+
return gbm

daal4py/sklearn/ensemble/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
from .GBTDAAL import (GBTDAALClassifier, GBTDAALRegressor)
2020
from .AdaBoostClassifier import AdaBoostClassifier
2121

22-
__all__ = ['RandomForestClassifier', 'RandomForestRegressor', 'GBTDAALClassifier',
23-
'GBTDAALRegressor', 'AdaBoostClassifier']
22+
__all__ = ['RandomForestClassifier', 'RandomForestRegressor',
23+
'GBTDAALClassifier', 'GBTDAALRegressor', 'AdaBoostClassifier']

doc/daal4py/examples.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ General usage
2525

2626
Building models from Gradient Boosting frameworks
2727

28-
- `XGBoost* model conversion <https://github.com/intel/scikit-learn-intelex/blob/master/examples/daal4py/gbt_cls_model_create_from_xgboost_batch.py>`_
29-
- `LightGBM* model conversion <https://github.com/intel/scikit-learn-intelex/blob/master/examples/daal4py/gbt_cls_model_create_from_lightgbm_batch.py>`_
30-
- `CatBoost* model conversion <https://github.com/intel/scikit-learn-intelex/blob/master/examples/daal4py/gbt_cls_model_create_from_catboost_batch.py>`_
28+
- `XGBoost* model conversion <https://github.com/intel/scikit-learn-intelex/blob/master/examples/daal4py/model_builders_xgboost.py>`_
29+
- `LightGBM* model conversion <https://github.com/intel/scikit-learn-intelex/blob/master/examples/daal4py/model_builders_lightgbm.py>`_
30+
- `CatBoost* model conversion <https://github.com/intel/scikit-learn-intelex/blob/master/examples/daal4py/model_builders_catboost.py>`_
3131

3232

3333
Principal Component Analysis (PCA) Transform

0 commit comments

Comments
 (0)