SuperModelingFactory 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ExcelMaster/ExcelFormatTool.py +487 -0
- ExcelMaster/ExcelMaster.py +917 -0
- ExcelMaster/Template.py +525 -0
- ExcelMaster/Utility.py +233 -0
- ExcelMaster/__init__.py +3 -0
- Modeling_Tool/Core/Binning_Tool.py +1608 -0
- Modeling_Tool/Core/Binning_Tool.pyi +48 -0
- Modeling_Tool/Core/Check_DuckDB_Compatibility.py +835 -0
- Modeling_Tool/Core/Json_Data_Converter.py +621 -0
- Modeling_Tool/Core/Model_Registry_Tool.py +199 -0
- Modeling_Tool/Core/ODPS_Tool.py +284 -0
- Modeling_Tool/Core/Slope_Tool.py +356 -0
- Modeling_Tool/Core/Slope_Tool.pyi +31 -0
- Modeling_Tool/Core/XOR_Encryptor.py +207 -0
- Modeling_Tool/Core/XOR_Encryptor.pyi +26 -0
- Modeling_Tool/Core/__init__.py +99 -0
- Modeling_Tool/Core/kDataFrame.py +228 -0
- Modeling_Tool/Core/kDataFrame.pyi +43 -0
- Modeling_Tool/Core/sample_weight_utils.py +77 -0
- Modeling_Tool/Core/utils.py +2672 -0
- Modeling_Tool/Eval/Evaluation_Tool.py +1452 -0
- Modeling_Tool/Eval/Evaluation_Tool.pyi +47 -0
- Modeling_Tool/Eval/Model_Eval_Tool.py +2548 -0
- Modeling_Tool/Eval/Model_Eval_Tool.pyi +37 -0
- Modeling_Tool/Eval/__init__.py +54 -0
- Modeling_Tool/Eval/evaluate_model.py +2008 -0
- Modeling_Tool/Eval/evaluate_model.pyi +50 -0
- Modeling_Tool/Eval/weighted_eval_utils.py +326 -0
- Modeling_Tool/Explainability/Coalition_Structure.py +305 -0
- Modeling_Tool/Explainability/Model_Explainer.py +743 -0
- Modeling_Tool/Explainability/__init__.py +24 -0
- Modeling_Tool/Feature/Distribution_Tool.py +509 -0
- Modeling_Tool/Feature/Distribution_Tool.pyi +42 -0
- Modeling_Tool/Feature/Feature_Insights.py +762 -0
- Modeling_Tool/Feature/Feature_Insights.pyi +33 -0
- Modeling_Tool/Feature/PSI_Tool.py +1195 -0
- Modeling_Tool/Feature/PSI_Tool.pyi +29 -0
- Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +355 -0
- Modeling_Tool/Feature/__init__.py +40 -0
- Modeling_Tool/Model/Backward_Tool.py +778 -0
- Modeling_Tool/Model/Backward_Tool.pyi +45 -0
- Modeling_Tool/Model/GBM_Search_Tool.py +251 -0
- Modeling_Tool/Model/GBM_Tool.py +1610 -0
- Modeling_Tool/Model/GBM_Tool.pyi +90 -0
- Modeling_Tool/Model/LRM_Tool.py +1198 -0
- Modeling_Tool/Model/LRM_Tool.pyi +47 -0
- Modeling_Tool/Model/__init__.py +61 -0
- Modeling_Tool/Sample/Distribution_Adaptation.py +131 -0
- Modeling_Tool/Sample/Distribution_Adaptation.pyi +30 -0
- Modeling_Tool/Sample/Reject_Infer.py +413 -0
- Modeling_Tool/Sample/Reject_Infer.pyi +43 -0
- Modeling_Tool/Sample/Sample_Split.py +520 -0
- Modeling_Tool/Sample/Sample_Split.pyi +43 -0
- Modeling_Tool/Sample/__init__.py +31 -0
- Modeling_Tool/UAT/UAT_Consistency_Checker.py +1180 -0
- Modeling_Tool/UAT/__init__.py +19 -0
- Modeling_Tool/WOE/WOE_Adapter.py +204 -0
- Modeling_Tool/WOE/WOE_Adapter.pyi +21 -0
- Modeling_Tool/WOE/WOE_Master.py +491 -0
- Modeling_Tool/WOE/WOE_Master.pyi +40 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.py +3324 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +71 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.py +919 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.pyi +46 -0
- Modeling_Tool/WOE/WOE_Report_Builder.py +214 -0
- Modeling_Tool/WOE/WOE_Report_Builder.pyi +24 -0
- Modeling_Tool/WOE/WOE_Tool.py +1094 -0
- Modeling_Tool/WOE/WOE_Tool.pyi +41 -0
- Modeling_Tool/WOE/__init__.py +86 -0
- Modeling_Tool/WOE/plot_woe_tool.py +290 -0
- Modeling_Tool/WOE/plot_woe_tool.pyi +25 -0
- Modeling_Tool/__init__.py +176 -0
- Report/Report_Tool.py +380 -0
- Report/__init__.py +3 -0
- supermodelingfactory-0.2.0.dist-info/METADATA +268 -0
- supermodelingfactory-0.2.0.dist-info/RECORD +79 -0
- supermodelingfactory-0.2.0.dist-info/WHEEL +5 -0
- supermodelingfactory-0.2.0.dist-info/licenses/LICENSE +102 -0
- supermodelingfactory-0.2.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,1610 @@
|
|
|
1
|
+
"""
|
|
2
|
+
梯度提升模型训练工具包
|
|
3
|
+
=============================
|
|
4
|
+
|
|
5
|
+
本模块提供LightGBM、XGBoost和CatBoost模型的快速训练和评估功能,
|
|
6
|
+
包括模型训练、特征重要性提取等常用操作。
|
|
7
|
+
|
|
8
|
+
Functions
|
|
9
|
+
---------
|
|
10
|
+
set_num_leaves
|
|
11
|
+
根据最大深度计算叶子节点数,避免过拟合。
|
|
12
|
+
lgb_model
|
|
13
|
+
快速训练LightGBM模型。
|
|
14
|
+
lgb_varimp
|
|
15
|
+
获取LightGBM特征重要性。
|
|
16
|
+
lgbm_quick_train
|
|
17
|
+
快速训练LightGBM模型(使用DataFrame接口)。
|
|
18
|
+
xgb_model
|
|
19
|
+
训练XGBoost模型。
|
|
20
|
+
xgb_varimp
|
|
21
|
+
获取XGBoost特征重要性。
|
|
22
|
+
xgbm_quick_train
|
|
23
|
+
快速训练XGBoost模型(使用DataFrame接口)。
|
|
24
|
+
catboost_model
|
|
25
|
+
训练CatBoost模型。
|
|
26
|
+
catboost_varimp
|
|
27
|
+
获取CatBoost特征重要性。
|
|
28
|
+
catboost_quick_train
|
|
29
|
+
快速训练CatBoost模型(使用DataFrame接口)。
|
|
30
|
+
|
|
31
|
+
Classes
|
|
32
|
+
-------
|
|
33
|
+
LightGBMModel
|
|
34
|
+
LightGBM模型封装类,提供统一的训练和评估接口。
|
|
35
|
+
XGBoostModel
|
|
36
|
+
XGBoost模型封装类,提供统一的训练和评估接口。
|
|
37
|
+
CatBoostModel
|
|
38
|
+
CatBoost模型封装类,提供统一的训练和评估接口。
|
|
39
|
+
GradientBoostingModel
|
|
40
|
+
统一封装类,支持LightGBM、XGBoost和CatBoost切换。
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
# 函数式调用
|
|
45
|
+
>>> model = lgb_model(x_train, y_train, x_val, y_val, params)
|
|
46
|
+
>>> varimp = lgb_varimp(model)
|
|
47
|
+
|
|
48
|
+
# 类封装调用
|
|
49
|
+
>>> lgb_model = LightGBMModel(params)
|
|
50
|
+
>>> lgb_model.fit(x_train, y_train, x_val, y_val)
|
|
51
|
+
>>> varimp = lgb_model.get_feature_importance()
|
|
52
|
+
|
|
53
|
+
# 统一接口调用
|
|
54
|
+
>>> model = GradientBoostingModel('lgb', params)
|
|
55
|
+
>>> model.fit(x_train, y_train, x_val, y_val)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import pandas as pd
|
|
59
|
+
import numpy as np
|
|
60
|
+
from Modeling_Tool.Core.utils import load_model, save_model
|
|
61
|
+
from sklearn.calibration import CalibratedClassifierCV, calibration_curve
|
|
62
|
+
from sklearn.metrics import brier_score_loss, roc_auc_score
|
|
63
|
+
|
|
64
|
+
# lightgbm, xgboost, and catboost are imported lazily inside each function/method to
|
|
65
|
+
# avoid triggering the lightgbm → dask → numpy_compat → np.float chain at
|
|
66
|
+
# module import time. Old dask versions (<2022) use np.float which was
|
|
67
|
+
# removed in NumPy 1.20+, causing AttributeError on import.
|
|
68
|
+
def _get_lgb():
|
|
69
|
+
try:
|
|
70
|
+
import lightgbm as lgb
|
|
71
|
+
return lgb
|
|
72
|
+
except ImportError:
|
|
73
|
+
raise ImportError("lightgbm is required. Install with: pip install lightgbm>=3.3.0")
|
|
74
|
+
|
|
75
|
+
def _get_xgb():
|
|
76
|
+
try:
|
|
77
|
+
import xgboost as xgb
|
|
78
|
+
return xgb
|
|
79
|
+
except ImportError:
|
|
80
|
+
raise ImportError("xgboost is required. Install with: pip install xgboost>=1.7.0")
|
|
81
|
+
|
|
82
|
+
def _get_catboost():
|
|
83
|
+
try:
|
|
84
|
+
from catboost import CatBoostClassifier
|
|
85
|
+
return CatBoostClassifier
|
|
86
|
+
except ImportError:
|
|
87
|
+
raise ImportError("catboost is required. Install with: pip install catboost>=1.2.0")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _coerce_feature_names(feature_names):
|
|
91
|
+
"""Normalize estimator feature-name metadata to a non-empty list."""
|
|
92
|
+
if feature_names is None:
|
|
93
|
+
return None
|
|
94
|
+
try:
|
|
95
|
+
names = list(feature_names)
|
|
96
|
+
except TypeError:
|
|
97
|
+
return None
|
|
98
|
+
return names or None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _extract_estimator_feature_names(estimator):
|
|
102
|
+
"""Extract feature names across sklearn / LightGBM / XGBoost / CatBoost versions."""
|
|
103
|
+
for attr in ("feature_names_in_", "feature_name_", "feature_names_"):
|
|
104
|
+
names = _coerce_feature_names(getattr(estimator, attr, None))
|
|
105
|
+
if names is not None:
|
|
106
|
+
return names
|
|
107
|
+
|
|
108
|
+
booster = getattr(estimator, "booster_", None)
|
|
109
|
+
feature_name = getattr(booster, "feature_name", None)
|
|
110
|
+
if callable(feature_name):
|
|
111
|
+
names = _coerce_feature_names(feature_name())
|
|
112
|
+
if names is not None:
|
|
113
|
+
return names
|
|
114
|
+
|
|
115
|
+
get_booster = getattr(estimator, "get_booster", None)
|
|
116
|
+
if callable(get_booster):
|
|
117
|
+
try:
|
|
118
|
+
booster = get_booster()
|
|
119
|
+
except Exception:
|
|
120
|
+
booster = None
|
|
121
|
+
names = _coerce_feature_names(getattr(booster, "feature_names", None))
|
|
122
|
+
if names is not None:
|
|
123
|
+
return names
|
|
124
|
+
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _ensure_feature_names_in(estimator, feature_names):
|
|
129
|
+
"""Backfill sklearn-style feature_names_in_ on legacy estimators if possible."""
|
|
130
|
+
if feature_names is None or hasattr(estimator, "feature_names_in_"):
|
|
131
|
+
return
|
|
132
|
+
try:
|
|
133
|
+
estimator.feature_names_in_ = np.asarray(feature_names, dtype=object)
|
|
134
|
+
except Exception:
|
|
135
|
+
# Some third-party estimators expose read-only / slot-backed metadata.
|
|
136
|
+
# __getattr__ on the wrapper still provides a compatible fallback.
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# CatBoost metric names are case-sensitive; map common lower-case aliases to the
|
|
141
|
+
# spellings CatBoost expects. Unlisted strings are passed through unchanged.
|
|
142
|
+
_CATBOOST_METRIC_ALIASES = {'auc': 'AUC', 'logloss': 'Logloss'}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _normalize_catboost_params(params_dict):
|
|
146
|
+
"""Normalize unified GBM params to CatBoost classifier kwargs.
|
|
147
|
+
|
|
148
|
+
Maps sklearn-style names (n_estimators, max_depth, random_state) to CatBoost
|
|
149
|
+
equivalents and extracts fit-only arguments. ``eval_metric`` is a CatBoost
|
|
150
|
+
constructor argument (it is not accepted by ``fit()``), so it is folded into
|
|
151
|
+
the returned classifier params.
|
|
152
|
+
|
|
153
|
+
``eval_metric`` defaults to ``'AUC'`` when neither ``eval_metric`` nor
|
|
154
|
+
``metric`` is supplied. Common lower-case aliases (e.g. ``'auc'``,
|
|
155
|
+
``'logloss'``) are normalized to the case-sensitive names CatBoost expects;
|
|
156
|
+
any other string is left untouched.
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
tuple
|
|
161
|
+
(classifier_params, early_stopping_rounds, eval_metric, cat_features)
|
|
162
|
+
"""
|
|
163
|
+
params = dict(params_dict)
|
|
164
|
+
early_stopping_rounds = params.pop('early_stopping_rounds', None)
|
|
165
|
+
eval_metric = params.pop('eval_metric', params.pop('metric', None))
|
|
166
|
+
cat_features = params.pop('cat_features', None)
|
|
167
|
+
|
|
168
|
+
if 'n_estimators' in params:
|
|
169
|
+
params['iterations'] = params.pop('n_estimators')
|
|
170
|
+
if 'max_depth' in params:
|
|
171
|
+
params['depth'] = params.pop('max_depth')
|
|
172
|
+
if 'random_state' in params:
|
|
173
|
+
params['random_seed'] = params.pop('random_state')
|
|
174
|
+
|
|
175
|
+
params.setdefault('loss_function', 'Logloss')
|
|
176
|
+
params.setdefault('verbose', False)
|
|
177
|
+
|
|
178
|
+
# eval_metric must be passed to the CatBoostClassifier constructor, not fit().
|
|
179
|
+
if eval_metric is None:
|
|
180
|
+
eval_metric = 'AUC'
|
|
181
|
+
elif isinstance(eval_metric, str):
|
|
182
|
+
eval_metric = _CATBOOST_METRIC_ALIASES.get(eval_metric.lower(), eval_metric)
|
|
183
|
+
params['eval_metric'] = eval_metric
|
|
184
|
+
|
|
185
|
+
return params, early_stopping_rounds, eval_metric, cat_features
|
|
186
|
+
|
|
187
|
+
# ============================================================================
|
|
188
|
+
# 工具函数(保持独立)
|
|
189
|
+
# ============================================================================
|
|
190
|
+
|
|
191
|
+
def set_num_leaves(max_depth=5, wgt=1):
|
|
192
|
+
"""根据最大深度设置叶子节点数,避免过拟合。
|
|
193
|
+
|
|
194
|
+
根据给定的最大深度和权重系数,计算合适的叶子节点数量。
|
|
195
|
+
计算公式:2^max_depth - 2^max_depth * wgt
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
max_depth : int, default 5
|
|
200
|
+
树的最大深度
|
|
201
|
+
wgt : float, default 1
|
|
202
|
+
权重系数,取値范围[0, 1]
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
int
|
|
207
|
+
建议的叶子节点数
|
|
208
|
+
|
|
209
|
+
Examples
|
|
210
|
+
--------
|
|
211
|
+
>>> set_num_leaves(max_depth=5)
|
|
212
|
+
32
|
|
213
|
+
>>> set_num_leaves(max_depth=5, wgt=0.5)
|
|
214
|
+
16
|
|
215
|
+
"""
|
|
216
|
+
return int(2 ** max_depth - 2 ** max_depth * wgt)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def lgb_model(x, y, valx, valy, params_dict, wgt=None, init_score=None, eval_sample_weight=None):
|
|
220
|
+
"""快速训练LightGBM模型。
|
|
221
|
+
|
|
222
|
+
使用训练集和验证集训练LightGBM模型,支持早停机制。
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
x : array-like or pd.DataFrame
|
|
227
|
+
训练集特征
|
|
228
|
+
y : array-like
|
|
229
|
+
训练集标签
|
|
230
|
+
valx : array-like or pd.DataFrame
|
|
231
|
+
验证集特征
|
|
232
|
+
valy : array-like
|
|
233
|
+
验证集标签
|
|
234
|
+
params_dict : dict
|
|
235
|
+
LightGBM参数字典
|
|
236
|
+
wgt : array-like, optional
|
|
237
|
+
样本权重
|
|
238
|
+
init_score : array-like, optional
|
|
239
|
+
初始化分数
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
lgb.LGBMClassifier
|
|
244
|
+
训练好的LightGBM模型
|
|
245
|
+
|
|
246
|
+
Examples
|
|
247
|
+
--------
|
|
248
|
+
>>> params = {
|
|
249
|
+
... 'n_estimators': 100,
|
|
250
|
+
... 'max_depth': 5,
|
|
251
|
+
... 'learning_rate': 0.1,
|
|
252
|
+
... 'early_stopping_rounds': 20,
|
|
253
|
+
... 'eval_metric': 'auc'
|
|
254
|
+
... }
|
|
255
|
+
>>> model = lgb_model(x_train, y_train, x_val, y_val, params)
|
|
256
|
+
"""
|
|
257
|
+
lgb = _get_lgb()
|
|
258
|
+
|
|
259
|
+
lgb_params = {k: v for k, v in params_dict.items() if k != 'eval_metric'}
|
|
260
|
+
model = lgb.LGBMClassifier(**lgb_params)
|
|
261
|
+
# NOTE: `verbose=False` was removed from .fit() in lightgbm>=4. Log control
|
|
262
|
+
# is now done via callbacks only (log_evaluation).
|
|
263
|
+
model.fit(
|
|
264
|
+
x, y,
|
|
265
|
+
eval_set=[(valx, valy)],
|
|
266
|
+
eval_metric=params_dict['eval_metric'] if 'eval_metric' in params_dict else params_dict.get('metric', 'auc'),
|
|
267
|
+
callbacks=[
|
|
268
|
+
lgb.early_stopping(stopping_rounds=params_dict['early_stopping_rounds'], verbose=False),
|
|
269
|
+
lgb.log_evaluation(period=0),
|
|
270
|
+
],
|
|
271
|
+
sample_weight=wgt,
|
|
272
|
+
eval_sample_weight=[eval_sample_weight] if eval_sample_weight is not None else None,
|
|
273
|
+
init_score=init_score
|
|
274
|
+
)
|
|
275
|
+
return model
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def lgb_varimp(model):
|
|
279
|
+
"""获取LightGBM模型特征重要性。
|
|
280
|
+
|
|
281
|
+
返回按特征重要性排序的DataFrame。
|
|
282
|
+
|
|
283
|
+
Parameters
|
|
284
|
+
----------
|
|
285
|
+
model : lgb.LGBMClassifier
|
|
286
|
+
训练好的LightGBM模型
|
|
287
|
+
|
|
288
|
+
Returns
|
|
289
|
+
-------
|
|
290
|
+
pd.DataFrame
|
|
291
|
+
包含 feature 和 importance 列的DataFrame,按重要性降序排列
|
|
292
|
+
|
|
293
|
+
Examples
|
|
294
|
+
--------
|
|
295
|
+
>>> varimp = lgb_varimp(model)
|
|
296
|
+
>>> varimp.head(10)
|
|
297
|
+
"""
|
|
298
|
+
feature_names = model.booster_.feature_name()
|
|
299
|
+
importance = model.booster_.feature_importance(importance_type='gain')
|
|
300
|
+
varimp_df = pd.DataFrame({'feature': feature_names, 'importance': importance})
|
|
301
|
+
varimp_df = varimp_df.sort_values('importance', ascending=False).reset_index(drop=True)
|
|
302
|
+
return varimp_df
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def lgbm_quick_train(train_data, validation_data, x, y, params, wgt_col = None, val_wgt_col = None, cat_x_train=None):
|
|
306
|
+
"""快速训练LightGBM模型(使用DataFrame接口)。
|
|
307
|
+
|
|
308
|
+
接受DataFrame格式的训练集和验证集,自动提取特征和标签。
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
train_data : pd.DataFrame
|
|
313
|
+
训练数据集
|
|
314
|
+
validation_data : pd.DataFrame
|
|
315
|
+
验证数据集
|
|
316
|
+
x : list of str
|
|
317
|
+
特征列名列表
|
|
318
|
+
y : str
|
|
319
|
+
目标变量列名
|
|
320
|
+
params : dict
|
|
321
|
+
LightGBM参数字典
|
|
322
|
+
wgt_col : str, optional
|
|
323
|
+
样本权重列名
|
|
324
|
+
cat_x_train : list of str, optional
|
|
325
|
+
类别型特征列名列表
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
lgb.LGBMClassifier
|
|
330
|
+
训练好的LightGBM模型
|
|
331
|
+
|
|
332
|
+
Examples
|
|
333
|
+
--------
|
|
334
|
+
>>> model = lgbm_quick_train(
|
|
335
|
+
... train_data=train_df,
|
|
336
|
+
... validation_data=val_df,
|
|
337
|
+
... x=['feat1', 'feat2'],
|
|
338
|
+
... y='target',
|
|
339
|
+
... params=params_dict
|
|
340
|
+
... )
|
|
341
|
+
"""
|
|
342
|
+
lgb = _get_lgb()
|
|
343
|
+
|
|
344
|
+
wgt = train_data[wgt_col] if wgt_col is not None else None
|
|
345
|
+
eval_wgt = validation_data[val_wgt_col] if val_wgt_col is not None else None
|
|
346
|
+
model = lgb_model(
|
|
347
|
+
x=train_data[x],
|
|
348
|
+
y=train_data[y],
|
|
349
|
+
valx=validation_data[x],
|
|
350
|
+
valy=validation_data[y],
|
|
351
|
+
params_dict=params,
|
|
352
|
+
wgt=wgt,
|
|
353
|
+
eval_sample_weight=eval_wgt
|
|
354
|
+
)
|
|
355
|
+
return model
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def xgb_model(x, y, valx, valy, params_dict, sample_weight=None, sample_weight_eval_set=None, base_margin=None):
|
|
359
|
+
"""训练XGBoost模型。
|
|
360
|
+
|
|
361
|
+
使用训练集和验证集训练XGBoost模型,支持早停机制。
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
x : array-like or pd.DataFrame
|
|
366
|
+
训练集特征
|
|
367
|
+
y : array-like
|
|
368
|
+
训练集标签
|
|
369
|
+
valx : array-like or pd.DataFrame
|
|
370
|
+
验证集特征
|
|
371
|
+
valy : array-like
|
|
372
|
+
验证集标签
|
|
373
|
+
params_dict : dict
|
|
374
|
+
XGBoost参数字典
|
|
375
|
+
sample_weight : array-like, optional
|
|
376
|
+
训练集样本权重
|
|
377
|
+
sample_weight_eval_set : list, optional
|
|
378
|
+
验证集样本权重列表
|
|
379
|
+
base_margin : array-like, optional
|
|
380
|
+
基础边际度
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
xgb.XGBClassifier
|
|
385
|
+
训练好的XGBoost模型
|
|
386
|
+
|
|
387
|
+
Examples
|
|
388
|
+
--------
|
|
389
|
+
>>> params = {
|
|
390
|
+
... 'n_estimators': 100,
|
|
391
|
+
... 'max_depth': 5,
|
|
392
|
+
... 'learning_rate': 0.1,
|
|
393
|
+
... 'early_stopping_rounds': 20,
|
|
394
|
+
... 'eval_metric': 'auc'
|
|
395
|
+
... }
|
|
396
|
+
>>> model = xgb_model(x_train, y_train, x_val, y_val, params)
|
|
397
|
+
"""
|
|
398
|
+
xgb = _get_xgb()
|
|
399
|
+
|
|
400
|
+
xgb_params = {k: v for k, v in params_dict.items() if k not in ('eval_metric',)}
|
|
401
|
+
model = xgb.XGBClassifier(**xgb_params)
|
|
402
|
+
model.fit(
|
|
403
|
+
x, y,
|
|
404
|
+
eval_set=[(valx, valy)],
|
|
405
|
+
verbose=False,
|
|
406
|
+
sample_weight=sample_weight,
|
|
407
|
+
sample_weight_eval_set=sample_weight_eval_set,
|
|
408
|
+
base_margin=base_margin
|
|
409
|
+
)
|
|
410
|
+
return model
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def xgb_varimp(model):
|
|
414
|
+
"""获取XGBoost模型特征重要性。
|
|
415
|
+
|
|
416
|
+
返回按特征重要性排序的DataFrame。
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
model : xgb.XGBClassifier
|
|
421
|
+
训练好的XGBoost模型
|
|
422
|
+
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
pd.DataFrame
|
|
426
|
+
包含 feature 和 importance 列的DataFrame,按重要性降序排列
|
|
427
|
+
|
|
428
|
+
Examples
|
|
429
|
+
--------
|
|
430
|
+
>>> varimp = xgb_varimp(model)
|
|
431
|
+
>>> varimp.head(10)
|
|
432
|
+
"""
|
|
433
|
+
importance = model.get_booster().get_fscore()
|
|
434
|
+
varimp_df = pd.DataFrame(
|
|
435
|
+
list(importance.items()),
|
|
436
|
+
columns=['feature', 'importance']
|
|
437
|
+
).sort_values('importance', ascending=False).reset_index(drop=True)
|
|
438
|
+
return varimp_df
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def xgbm_quick_train(train_data, validation_data, x, y, wgt_col=None, params=None,
|
|
442
|
+
sample_weight_eval_set=None, val_wgt_col=None):
|
|
443
|
+
"""快速训练XGBoost模型(使用DataFrame接口)。
|
|
444
|
+
|
|
445
|
+
接受DataFrame格式的训练集和验证集,自动提取特征和标签。
|
|
446
|
+
|
|
447
|
+
Parameters
|
|
448
|
+
----------
|
|
449
|
+
train_data : pd.DataFrame
|
|
450
|
+
训练数据集
|
|
451
|
+
validation_data : pd.DataFrame
|
|
452
|
+
验证数据集
|
|
453
|
+
x : list of str
|
|
454
|
+
特征列名列表
|
|
455
|
+
y : str
|
|
456
|
+
目标变量列名
|
|
457
|
+
wgt_col : str
|
|
458
|
+
样本权重列名
|
|
459
|
+
params : dict
|
|
460
|
+
XGBoost参数字典
|
|
461
|
+
sample_weight_eval_set : list, optional
|
|
462
|
+
验证集样本权重列表
|
|
463
|
+
|
|
464
|
+
Returns
|
|
465
|
+
-------
|
|
466
|
+
xgb.XGBClassifier
|
|
467
|
+
训练好的XGBoost模型
|
|
468
|
+
|
|
469
|
+
Examples
|
|
470
|
+
--------
|
|
471
|
+
>>> model = xgbm_quick_train(
|
|
472
|
+
... train_data=train_df,
|
|
473
|
+
... validation_data=val_df,
|
|
474
|
+
... x=['feat1', 'feat2'],
|
|
475
|
+
... y='target',
|
|
476
|
+
... wgt_col='weight',
|
|
477
|
+
... params=params_dict
|
|
478
|
+
... )
|
|
479
|
+
"""
|
|
480
|
+
xgb = _get_xgb()
|
|
481
|
+
|
|
482
|
+
wgt = train_data[wgt_col] if wgt_col is not None else None
|
|
483
|
+
if sample_weight_eval_set is None and val_wgt_col is not None:
|
|
484
|
+
sample_weight_eval_set = [validation_data[val_wgt_col]]
|
|
485
|
+
model = xgb_model(
|
|
486
|
+
x=train_data[x],
|
|
487
|
+
y=train_data[y],
|
|
488
|
+
valx=validation_data[x],
|
|
489
|
+
valy=validation_data[y],
|
|
490
|
+
params_dict=params,
|
|
491
|
+
sample_weight=wgt,
|
|
492
|
+
sample_weight_eval_set=sample_weight_eval_set
|
|
493
|
+
)
|
|
494
|
+
return model
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def catboost_model(x, y, valx, valy, params_dict, sample_weight=None):
|
|
498
|
+
"""训练CatBoost模型。
|
|
499
|
+
|
|
500
|
+
使用训练集和验证集训练CatBoost模型,支持早停机制。
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
x : array-like or pd.DataFrame
|
|
505
|
+
训练集特征
|
|
506
|
+
y : array-like
|
|
507
|
+
训练集标签
|
|
508
|
+
valx : array-like or pd.DataFrame
|
|
509
|
+
验证集特征
|
|
510
|
+
valy : array-like
|
|
511
|
+
验证集标签
|
|
512
|
+
params_dict : dict
|
|
513
|
+
CatBoost参数字典(支持 n_estimators / max_depth / random_state 别名)
|
|
514
|
+
sample_weight : array-like, optional
|
|
515
|
+
训练集样本权重
|
|
516
|
+
|
|
517
|
+
Returns
|
|
518
|
+
-------
|
|
519
|
+
CatBoostClassifier
|
|
520
|
+
训练好的CatBoost模型
|
|
521
|
+
|
|
522
|
+
Examples
|
|
523
|
+
--------
|
|
524
|
+
>>> params = {
|
|
525
|
+
... 'n_estimators': 100,
|
|
526
|
+
... 'max_depth': 5,
|
|
527
|
+
... 'learning_rate': 0.1,
|
|
528
|
+
... 'early_stopping_rounds': 20,
|
|
529
|
+
... 'eval_metric': 'AUC'
|
|
530
|
+
... }
|
|
531
|
+
>>> model = catboost_model(x_train, y_train, x_val, y_val, params)
|
|
532
|
+
"""
|
|
533
|
+
CatBoostClassifier = _get_catboost()
|
|
534
|
+
cb_params, early_stopping_rounds, eval_metric, cat_features = (
|
|
535
|
+
_normalize_catboost_params(params_dict)
|
|
536
|
+
)
|
|
537
|
+
# eval_metric is folded into cb_params by _normalize_catboost_params because
|
|
538
|
+
# CatBoost only accepts it as a constructor argument, not in fit().
|
|
539
|
+
model = CatBoostClassifier(**cb_params)
|
|
540
|
+
fit_kwargs = {
|
|
541
|
+
'eval_set': (valx, valy),
|
|
542
|
+
'verbose': cb_params.get('verbose', False),
|
|
543
|
+
}
|
|
544
|
+
if early_stopping_rounds is not None:
|
|
545
|
+
fit_kwargs['early_stopping_rounds'] = early_stopping_rounds
|
|
546
|
+
if cat_features is not None:
|
|
547
|
+
fit_kwargs['cat_features'] = cat_features
|
|
548
|
+
if sample_weight is not None:
|
|
549
|
+
fit_kwargs['sample_weight'] = sample_weight
|
|
550
|
+
model.fit(x, y, **fit_kwargs)
|
|
551
|
+
return model
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def catboost_varimp(model):
|
|
555
|
+
"""获取CatBoost模型特征重要性。
|
|
556
|
+
|
|
557
|
+
返回按特征重要性排序的DataFrame。
|
|
558
|
+
|
|
559
|
+
Parameters
|
|
560
|
+
----------
|
|
561
|
+
model : CatBoostClassifier
|
|
562
|
+
训练好的CatBoost模型
|
|
563
|
+
|
|
564
|
+
Returns
|
|
565
|
+
-------
|
|
566
|
+
pd.DataFrame
|
|
567
|
+
包含 feature 和 importance 列的DataFrame,按重要性降序排列
|
|
568
|
+
|
|
569
|
+
Examples
|
|
570
|
+
--------
|
|
571
|
+
>>> varimp = catboost_varimp(model)
|
|
572
|
+
>>> varimp.head(10)
|
|
573
|
+
"""
|
|
574
|
+
feature_names = _extract_estimator_feature_names(model)
|
|
575
|
+
if feature_names is None:
|
|
576
|
+
feature_names = list(range(model.feature_count_))
|
|
577
|
+
importance = model.get_feature_importance()
|
|
578
|
+
varimp_df = pd.DataFrame({'feature': feature_names, 'importance': importance})
|
|
579
|
+
varimp_df = varimp_df.sort_values('importance', ascending=False).reset_index(drop=True)
|
|
580
|
+
return varimp_df
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def catboost_quick_train(train_data, validation_data, x, y, params, wgt_col=None,
|
|
584
|
+
val_wgt_col=None, cat_features=None):
|
|
585
|
+
"""快速训练CatBoost模型(使用DataFrame接口)。
|
|
586
|
+
|
|
587
|
+
接受DataFrame格式的训练集和验证集,自动提取特征和标签。
|
|
588
|
+
|
|
589
|
+
Parameters
|
|
590
|
+
----------
|
|
591
|
+
train_data : pd.DataFrame
|
|
592
|
+
训练数据集
|
|
593
|
+
validation_data : pd.DataFrame
|
|
594
|
+
验证数据集
|
|
595
|
+
x : list of str
|
|
596
|
+
特征列名列表
|
|
597
|
+
y : str
|
|
598
|
+
目标变量列名
|
|
599
|
+
params : dict
|
|
600
|
+
CatBoost参数字典
|
|
601
|
+
wgt_col : str, optional
|
|
602
|
+
样本权重列名
|
|
603
|
+
cat_features : list, optional
|
|
604
|
+
类别型特征列名或索引列表
|
|
605
|
+
|
|
606
|
+
Returns
|
|
607
|
+
-------
|
|
608
|
+
CatBoostClassifier
|
|
609
|
+
训练好的CatBoost模型
|
|
610
|
+
|
|
611
|
+
Examples
|
|
612
|
+
--------
|
|
613
|
+
>>> model = catboost_quick_train(
|
|
614
|
+
... train_data=train_df,
|
|
615
|
+
... validation_data=val_df,
|
|
616
|
+
... x=['feat1', 'feat2'],
|
|
617
|
+
... y='target',
|
|
618
|
+
... params=params_dict
|
|
619
|
+
... )
|
|
620
|
+
"""
|
|
621
|
+
_get_catboost()
|
|
622
|
+
|
|
623
|
+
params_dict = dict(params)
|
|
624
|
+
if cat_features is not None:
|
|
625
|
+
params_dict['cat_features'] = cat_features
|
|
626
|
+
wgt = train_data[wgt_col] if wgt_col is not None else None
|
|
627
|
+
model = catboost_model(
|
|
628
|
+
x=train_data[x],
|
|
629
|
+
y=train_data[y],
|
|
630
|
+
valx=validation_data[x],
|
|
631
|
+
valy=validation_data[y],
|
|
632
|
+
params_dict=params_dict,
|
|
633
|
+
sample_weight=wgt,
|
|
634
|
+
)
|
|
635
|
+
return model
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class LightGBMModel:
|
|
639
|
+
"""
|
|
640
|
+
LightGBM模型封装类。
|
|
641
|
+
|
|
642
|
+
提供统一的LightGBM模型训练、预测、保存和加载接口。
|
|
643
|
+
支持模型校准、特征重要性获取等功能。
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
----------
|
|
647
|
+
params : dict
|
|
648
|
+
LightGBM模型参数字典
|
|
649
|
+
model : lgb.LGBMClassifier, optional
|
|
650
|
+
预加载的模型实例
|
|
651
|
+
|
|
652
|
+
Examples
|
|
653
|
+
--------
|
|
654
|
+
>>> lgb_clf = LightGBMModel(params)
|
|
655
|
+
>>> lgb_clf.fit(x_train, y_train, x_val, y_val)
|
|
656
|
+
>>> preds = lgb_clf.predict(x_test)
|
|
657
|
+
"""
|
|
658
|
+
|
|
659
|
+
def __init__(self, params, model=None):
|
|
660
|
+
"""
|
|
661
|
+
初始化LightGBM模型封装类。
|
|
662
|
+
|
|
663
|
+
Parameters
|
|
664
|
+
----------
|
|
665
|
+
params : dict
|
|
666
|
+
LightGBM模型参数字典
|
|
667
|
+
model : lgb.LGBMClassifier, optional
|
|
668
|
+
预加载的模型实例
|
|
669
|
+
"""
|
|
670
|
+
lgb = _get_lgb()
|
|
671
|
+
self.params = params
|
|
672
|
+
self.model = model
|
|
673
|
+
self.feature_names_ = None
|
|
674
|
+
|
|
675
|
+
def fit(self, x, y, valx, valy, wgt=None, init_score=None, sample_weight=None, eval_sample_weight=None):
|
|
676
|
+
"""训练LightGBM模型。
|
|
677
|
+
|
|
678
|
+
使用训练集和验证集训练模型,支持早停机制。
|
|
679
|
+
|
|
680
|
+
Parameters
|
|
681
|
+
----------
|
|
682
|
+
x : array-like or pd.DataFrame
|
|
683
|
+
训练集特征
|
|
684
|
+
y : array-like
|
|
685
|
+
训练集标签
|
|
686
|
+
valx : array-like or pd.DataFrame
|
|
687
|
+
验证集特征
|
|
688
|
+
valy : array-like
|
|
689
|
+
验证集标签
|
|
690
|
+
wgt : array-like, optional
|
|
691
|
+
样本权重
|
|
692
|
+
init_score : array-like, optional
|
|
693
|
+
初始化分数
|
|
694
|
+
|
|
695
|
+
Returns
|
|
696
|
+
-------
|
|
697
|
+
self
|
|
698
|
+
"""
|
|
699
|
+
if wgt is None:
|
|
700
|
+
wgt = sample_weight
|
|
701
|
+
self.model = lgb_model(
|
|
702
|
+
x=x, y=y, valx=valx, valy=valy,
|
|
703
|
+
params_dict=self.params, wgt=wgt, init_score=init_score,
|
|
704
|
+
eval_sample_weight=eval_sample_weight
|
|
705
|
+
)
|
|
706
|
+
if hasattr(x, 'columns'):
|
|
707
|
+
self.feature_names_ = list(x.columns)
|
|
708
|
+
return self
|
|
709
|
+
|
|
710
|
+
def predict(self, x):
|
|
711
|
+
"""预测样本的概率。
|
|
712
|
+
|
|
713
|
+
Parameters
|
|
714
|
+
----------
|
|
715
|
+
x : array-like or pd.DataFrame
|
|
716
|
+
预测特征
|
|
717
|
+
|
|
718
|
+
Returns
|
|
719
|
+
-------
|
|
720
|
+
np.ndarray
|
|
721
|
+
预测概率
|
|
722
|
+
"""
|
|
723
|
+
return self.model.predict_proba(x)[:, 1]
|
|
724
|
+
|
|
725
|
+
def get_feature_importance(self, importance_type='gain'):
|
|
726
|
+
"""获取特征重要性。
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
----------
|
|
730
|
+
importance_type : str, default 'gain'
|
|
731
|
+
特征重要性类型,可选 'gain' 或 'split'
|
|
732
|
+
|
|
733
|
+
Returns
|
|
734
|
+
-------
|
|
735
|
+
pd.DataFrame
|
|
736
|
+
包含 feature 和 importance 列的DataFrame
|
|
737
|
+
"""
|
|
738
|
+
return lgb_varimp(self.model)
|
|
739
|
+
|
|
740
|
+
def save(self, path):
|
|
741
|
+
"""保存模型。
|
|
742
|
+
|
|
743
|
+
Parameters
|
|
744
|
+
----------
|
|
745
|
+
path : str
|
|
746
|
+
模型保存路径
|
|
747
|
+
"""
|
|
748
|
+
save_model(self.model, path)
|
|
749
|
+
|
|
750
|
+
def load(self, path):
|
|
751
|
+
"""加载模型。
|
|
752
|
+
|
|
753
|
+
Parameters
|
|
754
|
+
----------
|
|
755
|
+
path : str
|
|
756
|
+
模型文件路径
|
|
757
|
+
|
|
758
|
+
Returns
|
|
759
|
+
-------
|
|
760
|
+
self
|
|
761
|
+
"""
|
|
762
|
+
self.model = load_model(path)
|
|
763
|
+
return self
|
|
764
|
+
|
|
765
|
+
def calibrate(self, x, y, method='sigmoid', cv='prefit'):
|
|
766
|
+
"""模型概率校准。
|
|
767
|
+
|
|
768
|
+
Parameters
|
|
769
|
+
----------
|
|
770
|
+
x : array-like
|
|
771
|
+
校准特征
|
|
772
|
+
y : array-like
|
|
773
|
+
校准标签
|
|
774
|
+
method : str, default 'sigmoid'
|
|
775
|
+
校准方法,'sigmoid' 或 'isotonic'
|
|
776
|
+
cv : str or int, default 'prefit'
|
|
777
|
+
交叉验证方式
|
|
778
|
+
|
|
779
|
+
Returns
|
|
780
|
+
-------
|
|
781
|
+
self
|
|
782
|
+
"""
|
|
783
|
+
self.model = CalibratedClassifierCV(self.model, method=method, cv=cv)
|
|
784
|
+
self.model.fit(x, y)
|
|
785
|
+
return self
|
|
786
|
+
|
|
787
|
+
def calibration_curve(self, x, y, n_bins=10):
|
|
788
|
+
"""获取校准曲线数据。
|
|
789
|
+
|
|
790
|
+
Parameters
|
|
791
|
+
----------
|
|
792
|
+
x : array-like
|
|
793
|
+
特征
|
|
794
|
+
y : array-like
|
|
795
|
+
标签
|
|
796
|
+
n_bins : int, default 10
|
|
797
|
+
分箱数
|
|
798
|
+
|
|
799
|
+
Returns
|
|
800
|
+
-------
|
|
801
|
+
tuple
|
|
802
|
+
(fraction_of_positives, mean_predicted_value)
|
|
803
|
+
"""
|
|
804
|
+
y_prob = self.predict(x)
|
|
805
|
+
return calibration_curve(y, y_prob, n_bins=n_bins)
|
|
806
|
+
|
|
807
|
+
def brier_score(self, x, y):
|
|
808
|
+
"""计算Brier分数。
|
|
809
|
+
|
|
810
|
+
Parameters
|
|
811
|
+
----------
|
|
812
|
+
x : array-like
|
|
813
|
+
特征
|
|
814
|
+
y : array-like
|
|
815
|
+
标签
|
|
816
|
+
|
|
817
|
+
Returns
|
|
818
|
+
-------
|
|
819
|
+
float
|
|
820
|
+
Brier分数
|
|
821
|
+
"""
|
|
822
|
+
y_prob = self.predict(x)
|
|
823
|
+
return brier_score_loss(y, y_prob)
|
|
824
|
+
|
|
825
|
+
def roc_auc(self, x, y):
|
|
826
|
+
"""计算ROC AUC。
|
|
827
|
+
|
|
828
|
+
Parameters
|
|
829
|
+
----------
|
|
830
|
+
x : array-like
|
|
831
|
+
特征
|
|
832
|
+
y : array-like
|
|
833
|
+
标签
|
|
834
|
+
|
|
835
|
+
Returns
|
|
836
|
+
-------
|
|
837
|
+
float
|
|
838
|
+
ROC AUC分数
|
|
839
|
+
"""
|
|
840
|
+
y_prob = self.predict(x)
|
|
841
|
+
return roc_auc_score(y, y_prob)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class XGBoostModel:
|
|
845
|
+
"""
|
|
846
|
+
XGBoost模型封装类。
|
|
847
|
+
|
|
848
|
+
提供统一的XGBoost模型训练、预测、保存和加载接口。
|
|
849
|
+
支持模型校准、特征重要性获取等功能。
|
|
850
|
+
|
|
851
|
+
Parameters
|
|
852
|
+
----------
|
|
853
|
+
params : dict
|
|
854
|
+
XGBoost模型参数字典
|
|
855
|
+
model : xgb.XGBClassifier, optional
|
|
856
|
+
预加载的模型实例
|
|
857
|
+
|
|
858
|
+
Examples
|
|
859
|
+
--------
|
|
860
|
+
>>> xgb_clf = XGBoostModel(params)
|
|
861
|
+
>>> xgb_clf.fit(x_train, y_train, x_val, y_val)
|
|
862
|
+
>>> preds = xgb_clf.predict(x_test)
|
|
863
|
+
"""
|
|
864
|
+
|
|
865
|
+
def __init__(self, params, model=None):
|
|
866
|
+
"""
|
|
867
|
+
初始化XGBoost模型封装类。
|
|
868
|
+
|
|
869
|
+
Parameters
|
|
870
|
+
----------
|
|
871
|
+
params : dict
|
|
872
|
+
XGBoost模型参数字典
|
|
873
|
+
model : xgb.XGBClassifier, optional
|
|
874
|
+
预加载的模型实例
|
|
875
|
+
"""
|
|
876
|
+
xgb = _get_xgb()
|
|
877
|
+
self.params = params
|
|
878
|
+
self.model = model
|
|
879
|
+
self.feature_names_ = None
|
|
880
|
+
|
|
881
|
+
def fit(self, x, y, valx, valy, sample_weight=None, sample_weight_eval_set=None, base_margin=None):
|
|
882
|
+
"""训练XGBoost模型。
|
|
883
|
+
|
|
884
|
+
Parameters
|
|
885
|
+
----------
|
|
886
|
+
x : array-like or pd.DataFrame
|
|
887
|
+
训练集特征
|
|
888
|
+
y : array-like
|
|
889
|
+
训练集标签
|
|
890
|
+
valx : array-like or pd.DataFrame
|
|
891
|
+
验证集特征
|
|
892
|
+
valy : array-like
|
|
893
|
+
验证集标签
|
|
894
|
+
sample_weight : array-like, optional
|
|
895
|
+
样本权重
|
|
896
|
+
sample_weight_eval_set : list, optional
|
|
897
|
+
验证集样本权重列表
|
|
898
|
+
base_margin : array-like, optional
|
|
899
|
+
基础边际(init_score / log-odds 偏移),用于增量训练(warm-start)。
|
|
900
|
+
|
|
901
|
+
Returns
|
|
902
|
+
-------
|
|
903
|
+
self
|
|
904
|
+
"""
|
|
905
|
+
self.model = xgb_model(
|
|
906
|
+
x=x, y=y, valx=valx, valy=valy,
|
|
907
|
+
params_dict=self.params,
|
|
908
|
+
sample_weight=sample_weight,
|
|
909
|
+
sample_weight_eval_set=sample_weight_eval_set,
|
|
910
|
+
base_margin=base_margin
|
|
911
|
+
)
|
|
912
|
+
if hasattr(x, 'columns'):
|
|
913
|
+
self.feature_names_ = list(x.columns)
|
|
914
|
+
return self
|
|
915
|
+
|
|
916
|
+
def predict(self, x):
|
|
917
|
+
"""预测样本的概率。
|
|
918
|
+
|
|
919
|
+
Parameters
|
|
920
|
+
----------
|
|
921
|
+
x : array-like or pd.DataFrame
|
|
922
|
+
预测特征
|
|
923
|
+
|
|
924
|
+
Returns
|
|
925
|
+
-------
|
|
926
|
+
np.ndarray
|
|
927
|
+
预测概率
|
|
928
|
+
"""
|
|
929
|
+
return self.model.predict_proba(x)[:, 1]
|
|
930
|
+
|
|
931
|
+
def get_feature_importance(self, importance_type='gain'):
|
|
932
|
+
"""获取特征重要性。
|
|
933
|
+
|
|
934
|
+
Parameters
|
|
935
|
+
----------
|
|
936
|
+
importance_type : str, default 'gain'
|
|
937
|
+
特征重要性类型
|
|
938
|
+
|
|
939
|
+
Returns
|
|
940
|
+
-------
|
|
941
|
+
pd.DataFrame
|
|
942
|
+
包含 feature 和 importance 列的DataFrame
|
|
943
|
+
"""
|
|
944
|
+
return xgb_varimp(self.model)
|
|
945
|
+
|
|
946
|
+
def save(self, path):
|
|
947
|
+
"""保存模型。
|
|
948
|
+
|
|
949
|
+
Parameters
|
|
950
|
+
----------
|
|
951
|
+
path : str
|
|
952
|
+
模型保存路径
|
|
953
|
+
"""
|
|
954
|
+
save_model(self.model, path)
|
|
955
|
+
|
|
956
|
+
def load(self, path):
|
|
957
|
+
"""加载模型。
|
|
958
|
+
|
|
959
|
+
Parameters
|
|
960
|
+
----------
|
|
961
|
+
path : str
|
|
962
|
+
模型文件路径
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
self
|
|
967
|
+
"""
|
|
968
|
+
self.model = load_model(path)
|
|
969
|
+
return self
|
|
970
|
+
|
|
971
|
+
def calibrate(self, x, y, method='sigmoid', cv='prefit'):
|
|
972
|
+
"""模型概率校准。
|
|
973
|
+
|
|
974
|
+
Parameters
|
|
975
|
+
----------
|
|
976
|
+
x : array-like
|
|
977
|
+
校准特征
|
|
978
|
+
y : array-like
|
|
979
|
+
校准标签
|
|
980
|
+
method : str, default 'sigmoid'
|
|
981
|
+
校准方法
|
|
982
|
+
cv : str or int, default 'prefit'
|
|
983
|
+
交叉验证方式
|
|
984
|
+
|
|
985
|
+
Returns
|
|
986
|
+
-------
|
|
987
|
+
self
|
|
988
|
+
"""
|
|
989
|
+
self.model = CalibratedClassifierCV(self.model, method=method, cv=cv)
|
|
990
|
+
self.model.fit(x, y)
|
|
991
|
+
return self
|
|
992
|
+
|
|
993
|
+
def calibration_curve(self, x, y, n_bins=10):
|
|
994
|
+
"""获取校准曲线数据。
|
|
995
|
+
|
|
996
|
+
Parameters
|
|
997
|
+
----------
|
|
998
|
+
x : array-like
|
|
999
|
+
特征
|
|
1000
|
+
y : array-like
|
|
1001
|
+
标签
|
|
1002
|
+
|
|
1003
|
+
Returns
|
|
1004
|
+
-------
|
|
1005
|
+
tuple
|
|
1006
|
+
"""
|
|
1007
|
+
y_prob = self.predict(x)
|
|
1008
|
+
return calibration_curve(y, y_prob, n_bins=n_bins)
|
|
1009
|
+
|
|
1010
|
+
def brier_score(self, x, y):
|
|
1011
|
+
"""计算Brier分数。
|
|
1012
|
+
|
|
1013
|
+
Parameters
|
|
1014
|
+
----------
|
|
1015
|
+
x : array-like
|
|
1016
|
+
y : array-like
|
|
1017
|
+
|
|
1018
|
+
Returns
|
|
1019
|
+
-------
|
|
1020
|
+
float
|
|
1021
|
+
"""
|
|
1022
|
+
y_prob = self.predict(x)
|
|
1023
|
+
return brier_score_loss(y, y_prob)
|
|
1024
|
+
|
|
1025
|
+
def roc_auc(self, x, y):
|
|
1026
|
+
"""计算ROC AUC。
|
|
1027
|
+
|
|
1028
|
+
Parameters
|
|
1029
|
+
----------
|
|
1030
|
+
x : array-like
|
|
1031
|
+
y : array-like
|
|
1032
|
+
|
|
1033
|
+
Returns
|
|
1034
|
+
-------
|
|
1035
|
+
float
|
|
1036
|
+
"""
|
|
1037
|
+
y_prob = self.predict(x)
|
|
1038
|
+
return roc_auc_score(y, y_prob)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
class CatBoostModel:
|
|
1042
|
+
"""
|
|
1043
|
+
CatBoost模型封装类。
|
|
1044
|
+
|
|
1045
|
+
提供统一的CatBoost模型训练、预测、保存和加载接口。
|
|
1046
|
+
支持模型校准、特征重要性获取等功能。
|
|
1047
|
+
|
|
1048
|
+
Parameters
|
|
1049
|
+
----------
|
|
1050
|
+
params : dict
|
|
1051
|
+
CatBoost模型参数字典
|
|
1052
|
+
model : CatBoostClassifier, optional
|
|
1053
|
+
预加载的模型实例
|
|
1054
|
+
|
|
1055
|
+
Examples
|
|
1056
|
+
--------
|
|
1057
|
+
>>> cat_clf = CatBoostModel(params)
|
|
1058
|
+
>>> cat_clf.fit(x_train, y_train, x_val, y_val)
|
|
1059
|
+
>>> preds = cat_clf.predict(x_test)
|
|
1060
|
+
"""
|
|
1061
|
+
|
|
1062
|
+
def __init__(self, params, model=None):
|
|
1063
|
+
"""
|
|
1064
|
+
初始化CatBoost模型封装类。
|
|
1065
|
+
|
|
1066
|
+
Parameters
|
|
1067
|
+
----------
|
|
1068
|
+
params : dict
|
|
1069
|
+
CatBoost模型参数字典
|
|
1070
|
+
model : CatBoostClassifier, optional
|
|
1071
|
+
预加载的模型实例
|
|
1072
|
+
"""
|
|
1073
|
+
_get_catboost()
|
|
1074
|
+
self.params = params
|
|
1075
|
+
self.model = model
|
|
1076
|
+
self.feature_names_ = None
|
|
1077
|
+
|
|
1078
|
+
def fit(self, x, y, valx, valy, sample_weight=None):
|
|
1079
|
+
"""训练CatBoost模型。
|
|
1080
|
+
|
|
1081
|
+
Parameters
|
|
1082
|
+
----------
|
|
1083
|
+
x : array-like or pd.DataFrame
|
|
1084
|
+
训练集特征
|
|
1085
|
+
y : array-like
|
|
1086
|
+
训练集标签
|
|
1087
|
+
valx : array-like or pd.DataFrame
|
|
1088
|
+
验证集特征
|
|
1089
|
+
valy : array-like
|
|
1090
|
+
验证集标签
|
|
1091
|
+
sample_weight : array-like, optional
|
|
1092
|
+
样本权重
|
|
1093
|
+
|
|
1094
|
+
Returns
|
|
1095
|
+
-------
|
|
1096
|
+
self
|
|
1097
|
+
"""
|
|
1098
|
+
self.model = catboost_model(
|
|
1099
|
+
x=x, y=y, valx=valx, valy=valy,
|
|
1100
|
+
params_dict=self.params,
|
|
1101
|
+
sample_weight=sample_weight,
|
|
1102
|
+
)
|
|
1103
|
+
if hasattr(x, 'columns'):
|
|
1104
|
+
self.feature_names_ = list(x.columns)
|
|
1105
|
+
return self
|
|
1106
|
+
|
|
1107
|
+
def predict(self, x):
|
|
1108
|
+
"""预测样本的概率。
|
|
1109
|
+
|
|
1110
|
+
Parameters
|
|
1111
|
+
----------
|
|
1112
|
+
x : array-like or pd.DataFrame
|
|
1113
|
+
预测特征
|
|
1114
|
+
|
|
1115
|
+
Returns
|
|
1116
|
+
-------
|
|
1117
|
+
np.ndarray
|
|
1118
|
+
预测概率
|
|
1119
|
+
"""
|
|
1120
|
+
return self.model.predict_proba(x)[:, 1]
|
|
1121
|
+
|
|
1122
|
+
def get_feature_importance(self, importance_type='gain'):
|
|
1123
|
+
"""获取特征重要性。
|
|
1124
|
+
|
|
1125
|
+
Parameters
|
|
1126
|
+
----------
|
|
1127
|
+
importance_type : str, default 'gain'
|
|
1128
|
+
特征重要性类型(CatBoost 使用 PredictionValuesChange)
|
|
1129
|
+
|
|
1130
|
+
Returns
|
|
1131
|
+
-------
|
|
1132
|
+
pd.DataFrame
|
|
1133
|
+
包含 feature 和 importance 列的DataFrame
|
|
1134
|
+
"""
|
|
1135
|
+
return catboost_varimp(self.model)
|
|
1136
|
+
|
|
1137
|
+
def save(self, path):
|
|
1138
|
+
"""保存模型。
|
|
1139
|
+
|
|
1140
|
+
Parameters
|
|
1141
|
+
----------
|
|
1142
|
+
path : str
|
|
1143
|
+
模型保存路径
|
|
1144
|
+
"""
|
|
1145
|
+
save_model(self.model, path)
|
|
1146
|
+
|
|
1147
|
+
def load(self, path):
|
|
1148
|
+
"""加载模型。
|
|
1149
|
+
|
|
1150
|
+
Parameters
|
|
1151
|
+
----------
|
|
1152
|
+
path : str
|
|
1153
|
+
模型文件路径
|
|
1154
|
+
|
|
1155
|
+
Returns
|
|
1156
|
+
-------
|
|
1157
|
+
self
|
|
1158
|
+
"""
|
|
1159
|
+
self.model = load_model(path)
|
|
1160
|
+
return self
|
|
1161
|
+
|
|
1162
|
+
def calibrate(self, x, y, method='sigmoid', cv='prefit'):
|
|
1163
|
+
"""模型概率校准。
|
|
1164
|
+
|
|
1165
|
+
Parameters
|
|
1166
|
+
----------
|
|
1167
|
+
x : array-like
|
|
1168
|
+
校准特征
|
|
1169
|
+
y : array-like
|
|
1170
|
+
校准标签
|
|
1171
|
+
method : str, default 'sigmoid'
|
|
1172
|
+
校准方法
|
|
1173
|
+
cv : str or int, default 'prefit'
|
|
1174
|
+
交叉验证方式
|
|
1175
|
+
|
|
1176
|
+
Returns
|
|
1177
|
+
-------
|
|
1178
|
+
self
|
|
1179
|
+
"""
|
|
1180
|
+
self.model = CalibratedClassifierCV(self.model, method=method, cv=cv)
|
|
1181
|
+
self.model.fit(x, y)
|
|
1182
|
+
return self
|
|
1183
|
+
|
|
1184
|
+
def calibration_curve(self, x, y, n_bins=10):
|
|
1185
|
+
"""获取校准曲线数据。
|
|
1186
|
+
|
|
1187
|
+
Parameters
|
|
1188
|
+
----------
|
|
1189
|
+
x : array-like
|
|
1190
|
+
特征
|
|
1191
|
+
y : array-like
|
|
1192
|
+
标签
|
|
1193
|
+
n_bins : int, default 10
|
|
1194
|
+
分箱数
|
|
1195
|
+
|
|
1196
|
+
Returns
|
|
1197
|
+
-------
|
|
1198
|
+
tuple
|
|
1199
|
+
"""
|
|
1200
|
+
y_prob = self.predict(x)
|
|
1201
|
+
return calibration_curve(y, y_prob, n_bins=n_bins)
|
|
1202
|
+
|
|
1203
|
+
def brier_score(self, x, y):
|
|
1204
|
+
"""计算Brier分数。
|
|
1205
|
+
|
|
1206
|
+
Parameters
|
|
1207
|
+
----------
|
|
1208
|
+
x : array-like
|
|
1209
|
+
y : array-like
|
|
1210
|
+
|
|
1211
|
+
Returns
|
|
1212
|
+
-------
|
|
1213
|
+
float
|
|
1214
|
+
"""
|
|
1215
|
+
y_prob = self.predict(x)
|
|
1216
|
+
return brier_score_loss(y, y_prob)
|
|
1217
|
+
|
|
1218
|
+
def roc_auc(self, x, y):
|
|
1219
|
+
"""计算ROC AUC。
|
|
1220
|
+
|
|
1221
|
+
Parameters
|
|
1222
|
+
----------
|
|
1223
|
+
x : array-like
|
|
1224
|
+
y : array-like
|
|
1225
|
+
|
|
1226
|
+
Returns
|
|
1227
|
+
-------
|
|
1228
|
+
float
|
|
1229
|
+
"""
|
|
1230
|
+
y_prob = self.predict(x)
|
|
1231
|
+
return roc_auc_score(y, y_prob)
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
class GradientBoostingModel:
|
|
1235
|
+
"""
|
|
1236
|
+
统一梯度提升模型封装类。
|
|
1237
|
+
|
|
1238
|
+
支持LightGBM、XGBoost和CatBoost三种框架的统一接口。
|
|
1239
|
+
通过model_type参数切换框架,其他接口保持一致。
|
|
1240
|
+
|
|
1241
|
+
Parameters
|
|
1242
|
+
----------
|
|
1243
|
+
model_type : str
|
|
1244
|
+
模型类型,'lgb'、'xgb' 或 'cat'('catboost' 别名)
|
|
1245
|
+
params : dict
|
|
1246
|
+
模型参数字典
|
|
1247
|
+
|
|
1248
|
+
Examples
|
|
1249
|
+
--------
|
|
1250
|
+
>>> model = GradientBoostingModel('lgb', params)
|
|
1251
|
+
>>> model.fit(x_train, y_train, x_val, y_val)
|
|
1252
|
+
>>> preds = model.predict(x_test)
|
|
1253
|
+
|
|
1254
|
+
# 增量学习(warm-start)
|
|
1255
|
+
>>> base_margin = init_model.get_base_margin(x_train)
|
|
1256
|
+
>>> new_model = GradientBoostingModel('xgb', params)
|
|
1257
|
+
>>> new_model.fit(x_train, y_train, x_val, y_val, init_score=base_margin)
|
|
1258
|
+
>>> proba = new_model.predict_with_base_margin(
|
|
1259
|
+
... x_score, init_model.get_base_margin(x_score))
|
|
1260
|
+
|
|
1261
|
+
# 适配已训练好的裸估计器(如历史直接 pickle 的 XGBClassifier)
|
|
1262
|
+
>>> init_model = GradientBoostingModel.from_fitted(load_model(path))
|
|
1263
|
+
>>> init_model.get_base_margin(x_train)
|
|
1264
|
+
"""
|
|
1265
|
+
|
|
1266
|
+
def __init__(self, model_type, params):
|
|
1267
|
+
"""
|
|
1268
|
+
初始化统一模型封装类。
|
|
1269
|
+
|
|
1270
|
+
Parameters
|
|
1271
|
+
----------
|
|
1272
|
+
model_type : str
|
|
1273
|
+
模型类型,'lgb'、'xgb' 或 'cat'('catboost' 别名)
|
|
1274
|
+
params : dict
|
|
1275
|
+
模型参数字典
|
|
1276
|
+
|
|
1277
|
+
Raises
|
|
1278
|
+
------
|
|
1279
|
+
ValueError
|
|
1280
|
+
当model_type不为支持类型时
|
|
1281
|
+
"""
|
|
1282
|
+
if model_type == 'catboost':
|
|
1283
|
+
model_type = 'cat'
|
|
1284
|
+
if model_type not in ('lgb', 'xgb', 'cat'):
|
|
1285
|
+
raise ValueError(
|
|
1286
|
+
f"model_type must be 'lgb', 'xgb', or 'cat', got: {model_type!r}"
|
|
1287
|
+
)
|
|
1288
|
+
self.model_type = model_type
|
|
1289
|
+
self.params = params
|
|
1290
|
+
if model_type == 'lgb':
|
|
1291
|
+
self._model = LightGBMModel(params)
|
|
1292
|
+
elif model_type == 'xgb':
|
|
1293
|
+
self._model = XGBoostModel(params)
|
|
1294
|
+
else:
|
|
1295
|
+
self._model = CatBoostModel(params)
|
|
1296
|
+
|
|
1297
|
+
@staticmethod
|
|
1298
|
+
def _detect_model_type(estimator):
|
|
1299
|
+
"""从已训练的估计器类名 / 模块推断 'lgb'、'xgb' 或 'cat'。
|
|
1300
|
+
|
|
1301
|
+
Parameters
|
|
1302
|
+
----------
|
|
1303
|
+
estimator : object
|
|
1304
|
+
已 fit 的 XGBClassifier / LGBMClassifier / CatBoostClassifier 等。
|
|
1305
|
+
|
|
1306
|
+
Returns
|
|
1307
|
+
-------
|
|
1308
|
+
str
|
|
1309
|
+
'lgb'、'xgb' 或 'cat'。
|
|
1310
|
+
|
|
1311
|
+
Raises
|
|
1312
|
+
------
|
|
1313
|
+
ValueError
|
|
1314
|
+
无法从类型推断时。
|
|
1315
|
+
"""
|
|
1316
|
+
cls = type(estimator)
|
|
1317
|
+
tag = f"{cls.__module__}.{cls.__name__}".lower()
|
|
1318
|
+
if 'catboost' in tag:
|
|
1319
|
+
return 'cat'
|
|
1320
|
+
if 'xgboost' in tag or 'xgb' in tag:
|
|
1321
|
+
return 'xgb'
|
|
1322
|
+
if 'lightgbm' in tag or 'lgb' in tag:
|
|
1323
|
+
return 'lgb'
|
|
1324
|
+
raise ValueError(
|
|
1325
|
+
f"cannot infer model_type from {cls.__name__!r}; "
|
|
1326
|
+
"pass model_type='lgb', 'xgb', or 'cat' explicitly"
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
@classmethod
|
|
1330
|
+
def from_fitted(cls, model, model_type=None, params=None):
|
|
1331
|
+
"""用一个【已训练好】的估计器(或封装)构造 GradientBoostingModel。
|
|
1332
|
+
|
|
1333
|
+
用于适配历史上直接以 sklearn 估计器(``XGBClassifier`` /
|
|
1334
|
+
``LGBMClassifier`` / ``CatBoostClassifier``)形式保存的模型,使其无需重训即可使用
|
|
1335
|
+
:meth:`get_base_margin` / :meth:`predict_with_base_margin` 等统一接口。
|
|
1336
|
+
|
|
1337
|
+
Parameters
|
|
1338
|
+
----------
|
|
1339
|
+
model : object
|
|
1340
|
+
已 fit 的 ``XGBClassifier`` / ``LGBMClassifier`` / ``CatBoostClassifier``,或
|
|
1341
|
+
``LightGBMModel`` / ``XGBoostModel`` / ``CatBoostModel`` / ``GradientBoostingModel`` 封装。
|
|
1342
|
+
model_type : {'lgb', 'xgb', 'cat'}, optional
|
|
1343
|
+
不传则按估计器类型自动推断。
|
|
1344
|
+
params : dict, optional
|
|
1345
|
+
参数字典;不传则尽量从估计器的 ``get_params()`` 读取。
|
|
1346
|
+
|
|
1347
|
+
Returns
|
|
1348
|
+
-------
|
|
1349
|
+
GradientBoostingModel
|
|
1350
|
+
|
|
1351
|
+
Raises
|
|
1352
|
+
------
|
|
1353
|
+
ValueError
|
|
1354
|
+
传入未 fit / 空模型时。
|
|
1355
|
+
"""
|
|
1356
|
+
if isinstance(model, cls):
|
|
1357
|
+
try:
|
|
1358
|
+
object.__getattribute__(model, '_model')
|
|
1359
|
+
return model
|
|
1360
|
+
except AttributeError:
|
|
1361
|
+
raise ValueError(
|
|
1362
|
+
"Loaded GradientBoostingModel is missing _model (Cython pickle bug "
|
|
1363
|
+
"in a previous package version). Please retrain and re-save the model."
|
|
1364
|
+
)
|
|
1365
|
+
# 解包 LightGBMModel / XGBoostModel / CatBoostModel(它们把裸估计器放在 .model)
|
|
1366
|
+
estimator = getattr(model, 'model', model)
|
|
1367
|
+
if estimator is None:
|
|
1368
|
+
raise ValueError("from_fitted received an unfitted / empty model")
|
|
1369
|
+
mt = model_type or cls._detect_model_type(estimator)
|
|
1370
|
+
if mt == 'catboost':
|
|
1371
|
+
mt = 'cat'
|
|
1372
|
+
if params is None:
|
|
1373
|
+
params = estimator.get_params() if hasattr(estimator, 'get_params') else {}
|
|
1374
|
+
obj = cls(mt, params)
|
|
1375
|
+
obj._model.model = estimator
|
|
1376
|
+
feat = _extract_estimator_feature_names(estimator)
|
|
1377
|
+
if feat is not None:
|
|
1378
|
+
obj._model.feature_names_ = feat
|
|
1379
|
+
_ensure_feature_names_in(estimator, feat)
|
|
1380
|
+
return obj
|
|
1381
|
+
|
|
1382
|
+
def __getattr__(self, name):
|
|
1383
|
+
"""把未知属性委托给底层已训练估计器。
|
|
1384
|
+
|
|
1385
|
+
使封装实例成为原始 LGBM/XGB/CatBoost 估计器的超集(如 ``get_params`` /
|
|
1386
|
+
``feature_names_in_`` / ``predict_proba`` 等可直接透传)。仅在常规
|
|
1387
|
+
属性查找失败时调用;对 dunder 名与 ``_model`` 缺失(如反序列化
|
|
1388
|
+
中途)安全抛出 AttributeError,以免干扰 pickle / copy。
|
|
1389
|
+
"""
|
|
1390
|
+
if name.startswith('__') and name.endswith('__'):
|
|
1391
|
+
raise AttributeError(name)
|
|
1392
|
+
if name == '_model':
|
|
1393
|
+
raise AttributeError(name)
|
|
1394
|
+
try:
|
|
1395
|
+
model = object.__getattribute__(self, '_model')
|
|
1396
|
+
except AttributeError:
|
|
1397
|
+
raise AttributeError(name)
|
|
1398
|
+
inner = getattr(model, 'model', None)
|
|
1399
|
+
if inner is not None and hasattr(inner, name):
|
|
1400
|
+
return getattr(inner, name)
|
|
1401
|
+
if name == 'feature_names_in_':
|
|
1402
|
+
feat = getattr(model, 'feature_names_', None)
|
|
1403
|
+
if feat is None and inner is not None:
|
|
1404
|
+
feat = _extract_estimator_feature_names(inner)
|
|
1405
|
+
if feat is not None:
|
|
1406
|
+
return np.asarray(feat, dtype=object)
|
|
1407
|
+
raise AttributeError(name)
|
|
1408
|
+
|
|
1409
|
+
def __getstate__(self):
|
|
1410
|
+
return {
|
|
1411
|
+
'model_type': self.model_type,
|
|
1412
|
+
'params': self.params,
|
|
1413
|
+
'_model_params': self._model.params,
|
|
1414
|
+
'_model_model': self._model.model,
|
|
1415
|
+
'_model_feature_names_': getattr(self._model, 'feature_names_', None),
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
def __setstate__(self, state):
|
|
1419
|
+
self.model_type = state['model_type']
|
|
1420
|
+
self.params = state['params']
|
|
1421
|
+
mt = self.model_type
|
|
1422
|
+
mp = state.get('_model_params', self.params)
|
|
1423
|
+
if mt == 'lgb':
|
|
1424
|
+
self._model = LightGBMModel(mp)
|
|
1425
|
+
elif mt == 'xgb':
|
|
1426
|
+
self._model = XGBoostModel(mp)
|
|
1427
|
+
else:
|
|
1428
|
+
self._model = CatBoostModel(mp)
|
|
1429
|
+
self._model.model = state.get('_model_model')
|
|
1430
|
+
self._model.feature_names_ = state.get('_model_feature_names_')
|
|
1431
|
+
|
|
1432
|
+
@staticmethod
|
|
1433
|
+
def _sigmoid(z):
|
|
1434
|
+
"""数値稳定的 Sigmoid(log-odds → 概率)。"""
|
|
1435
|
+
z = np.clip(np.asarray(z, dtype=float), -709, 709)
|
|
1436
|
+
return 1.0 / (1.0 + np.exp(-z))
|
|
1437
|
+
|
|
1438
|
+
def fit(self, x, y, valx, valy, init_score=None, sample_weight=None, eval_sample_weight=None, sample_weight_eval_set=None, **kwargs):
|
|
1439
|
+
"""训练模型(支持增量学习 warm-start)。
|
|
1440
|
+
|
|
1441
|
+
当传入 ``init_score`` 时,以其作为 log-odds 偏移在新数据上继续训练:
|
|
1442
|
+
LightGBM 走 ``init_score``,XGBoost 走 ``base_margin``(两者语义一致,
|
|
1443
|
+
本方法统一对外暴露为 ``init_score``)。CatBoost 不支持 ``init_score``。
|
|
1444
|
+
|
|
1445
|
+
Parameters
|
|
1446
|
+
----------
|
|
1447
|
+
x : array-like or pd.DataFrame
|
|
1448
|
+
训练集特征
|
|
1449
|
+
y : array-like
|
|
1450
|
+
训练集标签
|
|
1451
|
+
valx : array-like or pd.DataFrame
|
|
1452
|
+
验证集特征
|
|
1453
|
+
valy : array-like
|
|
1454
|
+
验证集标签
|
|
1455
|
+
init_score : array-like, optional
|
|
1456
|
+
初始 log-odds 偏移(增量学习起点)。一般由基准模型的
|
|
1457
|
+
:meth:`get_base_margin` 产生。``None`` 时即普通从头训练。
|
|
1458
|
+
**kwargs
|
|
1459
|
+
其余参数透传给底层模型(如 lgb 的 ``wgt``、xgb 的
|
|
1460
|
+
``sample_weight`` / ``sample_weight_eval_set``、cat 的
|
|
1461
|
+
``sample_weight``)。
|
|
1462
|
+
|
|
1463
|
+
Returns
|
|
1464
|
+
-------
|
|
1465
|
+
self
|
|
1466
|
+
|
|
1467
|
+
Raises
|
|
1468
|
+
------
|
|
1469
|
+
NotImplementedError
|
|
1470
|
+
CatBoost 不支持 ``init_score`` 增量学习。
|
|
1471
|
+
|
|
1472
|
+
Notes
|
|
1473
|
+
-----
|
|
1474
|
+
与既有生产流程一致,偏移仅作用于训练集;验证集未注入偏移,因此早停
|
|
1475
|
+
的 eval 指标是在"未加偏移"的空间上评估的。如需严格一致,可后续透传
|
|
1476
|
+
lgb 的 ``eval_init_score`` / xgb 的 ``base_margin_eval_set``。
|
|
1477
|
+
"""
|
|
1478
|
+
if self.model_type == 'cat':
|
|
1479
|
+
if init_score is not None:
|
|
1480
|
+
raise NotImplementedError(
|
|
1481
|
+
"CatBoost does not support init_score in GradientBoostingModel.fit"
|
|
1482
|
+
)
|
|
1483
|
+
self._model.fit(x, y, valx, valy, sample_weight=sample_weight, **kwargs)
|
|
1484
|
+
elif self.model_type == 'lgb':
|
|
1485
|
+
self._model.fit(
|
|
1486
|
+
x, y, valx, valy,
|
|
1487
|
+
init_score=init_score,
|
|
1488
|
+
sample_weight=sample_weight,
|
|
1489
|
+
eval_sample_weight=eval_sample_weight,
|
|
1490
|
+
**kwargs,
|
|
1491
|
+
)
|
|
1492
|
+
else:
|
|
1493
|
+
if sample_weight_eval_set is None and eval_sample_weight is not None:
|
|
1494
|
+
sample_weight_eval_set = [eval_sample_weight]
|
|
1495
|
+
self._model.fit(
|
|
1496
|
+
x, y, valx, valy,
|
|
1497
|
+
base_margin=init_score,
|
|
1498
|
+
sample_weight=sample_weight,
|
|
1499
|
+
sample_weight_eval_set=sample_weight_eval_set,
|
|
1500
|
+
**kwargs,
|
|
1501
|
+
)
|
|
1502
|
+
return self
|
|
1503
|
+
|
|
1504
|
+
def get_base_margin(self, x):
|
|
1505
|
+
"""返回本模型对 ``x`` 的原始 log-odds(base margin / init score)。
|
|
1506
|
+
|
|
1507
|
+
统一兼容三种框架取"未经 sigmoid 的原始分数":
|
|
1508
|
+
|
|
1509
|
+
- XGBoost: ``predict(x, output_margin=True)``
|
|
1510
|
+
- LightGBM: ``predict(x, raw_score=True)``
|
|
1511
|
+
- CatBoost: ``predict(x, prediction_type='RawFormulaVal')``
|
|
1512
|
+
|
|
1513
|
+
该结果可作为下一个增量模型 :meth:`fit` 的 ``init_score``,或喂给
|
|
1514
|
+
:meth:`predict_with_base_margin` 做融合预测。
|
|
1515
|
+
|
|
1516
|
+
Parameters
|
|
1517
|
+
----------
|
|
1518
|
+
x : array-like or pd.DataFrame
|
|
1519
|
+
待计算的特征
|
|
1520
|
+
|
|
1521
|
+
Returns
|
|
1522
|
+
-------
|
|
1523
|
+
np.ndarray
|
|
1524
|
+
一维 log-odds 数组,形状 ``(n_samples,)``
|
|
1525
|
+
|
|
1526
|
+
Raises
|
|
1527
|
+
------
|
|
1528
|
+
RuntimeError
|
|
1529
|
+
当模型尚未训练(``fit`` 之前)时
|
|
1530
|
+
"""
|
|
1531
|
+
est = self._model.model
|
|
1532
|
+
if est is None:
|
|
1533
|
+
raise RuntimeError("model is not fitted yet; call fit() first")
|
|
1534
|
+
if self.model_type == 'lgb':
|
|
1535
|
+
margin = est.predict(x, raw_score=True)
|
|
1536
|
+
elif self.model_type == 'cat':
|
|
1537
|
+
margin = est.predict(x, prediction_type='RawFormulaVal')
|
|
1538
|
+
else:
|
|
1539
|
+
margin = est.predict(x, output_margin=True)
|
|
1540
|
+
return np.asarray(margin).ravel()
|
|
1541
|
+
|
|
1542
|
+
def predict_with_base_margin(self, x, base_margin, return_prob=True):
|
|
1543
|
+
"""融合预测:``sigmoid(base_margin + 本模型 raw score)``。
|
|
1544
|
+
|
|
1545
|
+
把一个基准模型的 log-odds(``base_margin``,通常来自
|
|
1546
|
+
``init_model.get_base_margin(x)``)与本(增量)模型自身的 raw score
|
|
1547
|
+
在 log-odds 空间相加,再做 sigmoid。这种手动融合是唯一对 lgb 与 xgb
|
|
1548
|
+
行为一致的方式——LightGBM 在预测期并不支持注入 init_score。
|
|
1549
|
+
|
|
1550
|
+
Parameters
|
|
1551
|
+
----------
|
|
1552
|
+
x : array-like or pd.DataFrame
|
|
1553
|
+
待预测的特征
|
|
1554
|
+
base_margin : array-like
|
|
1555
|
+
基准模型的 log-odds 偏移,形状须与 ``x`` 的样本数一致
|
|
1556
|
+
return_prob : bool, default True
|
|
1557
|
+
``True`` 返回概率(sigmoid 后);``False`` 返回融合后的原始
|
|
1558
|
+
log-odds
|
|
1559
|
+
|
|
1560
|
+
Returns
|
|
1561
|
+
-------
|
|
1562
|
+
np.ndarray
|
|
1563
|
+
一维数组,``return_prob=True`` 时取值于 ``[0, 1]``
|
|
1564
|
+
"""
|
|
1565
|
+
combined = np.asarray(base_margin).ravel() + self.get_base_margin(x)
|
|
1566
|
+
return self._sigmoid(combined) if return_prob else combined
|
|
1567
|
+
|
|
1568
|
+
def predict(self, x):
|
|
1569
|
+
"""预测样本的概率。
|
|
1570
|
+
|
|
1571
|
+
Parameters
|
|
1572
|
+
----------
|
|
1573
|
+
x : array-like or pd.DataFrame
|
|
1574
|
+
|
|
1575
|
+
Returns
|
|
1576
|
+
-------
|
|
1577
|
+
np.ndarray
|
|
1578
|
+
"""
|
|
1579
|
+
return self._model.predict(x)
|
|
1580
|
+
|
|
1581
|
+
def get_feature_importance(self, importance_type='gain'):
|
|
1582
|
+
"""获取特征重要性。
|
|
1583
|
+
|
|
1584
|
+
Returns
|
|
1585
|
+
-------
|
|
1586
|
+
pd.DataFrame
|
|
1587
|
+
"""
|
|
1588
|
+
return self._model.get_feature_importance(importance_type=importance_type)
|
|
1589
|
+
|
|
1590
|
+
def save(self, path):
|
|
1591
|
+
"""保存模型。"""
|
|
1592
|
+
self._model.save(path)
|
|
1593
|
+
|
|
1594
|
+
def load(self, path):
|
|
1595
|
+
"""加载模型。"""
|
|
1596
|
+
self._model.load(path)
|
|
1597
|
+
return self
|
|
1598
|
+
|
|
1599
|
+
def calibrate(self, x, y, method='sigmoid', cv='prefit'):
|
|
1600
|
+
"""模型概率校准。"""
|
|
1601
|
+
self._model.calibrate(x, y, method=method, cv=cv)
|
|
1602
|
+
return self
|
|
1603
|
+
|
|
1604
|
+
def brier_score(self, x, y):
|
|
1605
|
+
"""计算Brier分数。"""
|
|
1606
|
+
return self._model.brier_score(x, y)
|
|
1607
|
+
|
|
1608
|
+
def roc_auc(self, x, y):
|
|
1609
|
+
"""计算ROC AUC。"""
|
|
1610
|
+
return self._model.roc_auc(x, y)
|