跳转至

模型训练

SuperModelingFactory 在 Model 子包封装了逻辑回归(评分卡首选)、LightGBM、XGBoost、CatBoost 四大类模型,并提供后向变量消元辅助工具。

样本权重

训练与评估均支持可选的样本权重(weight_col / sample_weight)。 不传权重时行为与历史版本完全一致(向后兼容)。自主仓 PR #25 起为原生 API。 评估侧语义见 模型评估 — 样本权重

1. 逻辑回归 —— LRMaster

from Modeling_Tool import LRMaster

lr = LRMaster(params={"C": 1.0, "max_iter": 1000, "solver": "lbfgs"})
# fit 接收 (data, varlist, tgt_name),而非 (X, y)
lr.fit(train_woe, woe_features, "bad_flag")

# 统计摘要:系数、标准误、z、p-value、置信区间
summary = lr.get_statsmodel_summary()
print(summary)

样本权重

fit 支持从 DataFrame 列名或显式数组传入权重(二选一,也接受 wgt / wgt_col 别名):

# 方式 1:列名(推荐,与评估侧 weight_col 命名一致)
lr.fit(train_woe, woe_features, "bad_flag", weight_col="sample_wgt")

# 方式 2:显式数组
lr.fit(train_woe, woe_features, "bad_flag", sample_weight=train_woe["sample_wgt"].values)

stepwise_selectioncalibrate_modelget_aic / get_bic 同样透传权重。 权重须为非负有限值。

关键参数(透传 sklearn)

参数 默认值 说明
C 1.0 正则化强度倒数,越小越强正则
penalty "l2" l1 / l2 / elasticnet
solver "lbfgs" 优化算法
max_iter 100 最大迭代次数

变量重要性

# LR 系数(按绝对值排序);列为 varlist / coef / importance
varimp = lr.get_variable_importance()
print(varimp[["varlist", "coef", "importance"]])

逐步变量选择

stepwise_selection(data, varlist, tgt_name, ...) 基于 AIC/BIC 做前向 / 后向 / 双向选择:

from Modeling_Tool import LRMaster

lr = LRMaster(params={"C": 1.0})
selected = lr.stepwise_selection(
    train_woe, woe_features, "bad_flag",
    criterion="aic",        # 'aic' 或 'bic'
    direction="both",       # 'forward' / 'backward' / 'both'
    weight_col="sample_wgt",  # 可选:加权 AIC/BIC
)
print(f"逐步选择保留 {len(selected)} 个变量: {selected}")

标准化(可选)

默认情况下 LRMaster 不做特征标准化(与历史行为一致)。如需在入模前对特征做标准化, 构造时打开 standardize=True 即可,默认使用 StandardScaler

from Modeling_Tool import LRMaster

# 开启标准化(默认 StandardScaler)
lr = LRMaster(params={"C": 1.0, "max_iter": 1000}, standardize=True)
lr.fit(train_woe, woe_features, "bad_flag")

# 预测时自动用 fit 阶段拟合好的 scaler 变换入参,无需手动标准化
proba = lr.predict_proba(test_woe)

也可以传入自定义 scaler(需同时 standardize=True),例如 MinMaxScaler

from sklearn.preprocessing import MinMaxScaler
from Modeling_Tool import LRMaster

lr = LRMaster(
    params={"C": 1.0},
    standardize=True,
    scaler=MinMaxScaler(),   # 传入的实例会被克隆,原对象不会被修改
)
lr.fit(train_woe, woe_features, "bad_flag")

行为说明

方面 说明
默认值 standardize=False,完全不标准化(向后兼容)
默认 scaler StandardScaler;可通过 scaler= 传自定义(如 MinMaxScaler()
拟合时机 scaler 在 fit / stepwise_selection只在训练特征上拟合一次,存于 lr.standardizer
推理一致性 predict / predict_proba / calibrate_model / get_statsmodel_summary / get_aic / get_bic 都用同一个 scaler 变换入参,避免训练 / 推理空间不一致
stepwise_selection 在标准化空间进行选择;结束后按最终入选变量重新拟合 scaler
clone() 只复制 standardize 开关与 scaler 原型,复制已拟合的 scaler / model

系数解读

开启标准化后,get_variable_importance()get_statsmodel_summary() 返回的系数是 标准化空间下的系数——好处是不同量纲特征的系数大小可直接横向比较;但不再等同于 原始单位下「自变量变动 1 个单位」的对数几率变化。

自定义 scaler 需显式开启标准化

仅传 scaler=... 而不设 standardize=True 不会启用标准化;自定义 scaler 必须与 standardize=True 一起使用。

超参网格搜索(holdout)

grid_search_params(...)INS / OOS / OOT holdout 上做超参网格搜索(而非 k-fold 交叉验证), 专为评分卡常见的「样本内 / 样本外 / 跨时间」场景设计:对 param_grid 的笛卡尔积逐个训练候选模型, 在每个 eval set 上算 AUC,再按 objective 选出最优组合。

import numpy as np
from Modeling_Tool import LRMaster

tuner = LRMaster(params={"solver": "lbfgs", "max_iter": 1000})
results = tuner.grid_search_params(
    data=ins_fit,                  # 训练候选模型的数据(通常是 INS)
    varlist=woe_features,
    tgt_name="bad_flag",
    eval_sets={"ins": ins_woe, "oos": oos_woe, "oot": oot_woe},  # 有序,按 AUC 评分
    param_grid={"C": np.logspace(-3, 2, 31)},   # 多个键按笛卡尔积组合
    objective="oot_gap_penalized",  # 默认:最大化主集 AUC 同时惩罚过拟合 gap
    primary_set="oot",              # 缺省为 eval_sets 的最后一个键
    gap_ref_sets=["ins", "oos"],    # 缺省为除 primary_set 外的所有集
    refit=True,                     # 搜完用最优参数在 data 上重训 self
    weight_col="sample_wgt",        # 训练集权重列
    eval_weight_col="sample_wgt",   # 各 eval set 上的加权 AUC 评分
)

print(tuner.best_params_)     # 最优参数 dict
print(tuner.search_results_)  # 完整结果表(= 返回值)

三种 objective

objective 选择标准
'oot_gap_penalized'(默认) AUC[primary] - |mean(AUC[gap_refs]) - AUC[primary]|,即在拉高主集 AUC 的同时惩罚训练/holdout 的 AUC 差(过拟合)
'max_primary' 直接最大化 AUC[primary]
callable 自定义 f(auc_dict) -> floatauc_dict{集名: AUC}

返回值与副作用

  • 返回:按 score 降序的结果表,列为参数列 + 每个 eval set 的 AUC_<名> +(gap objective 下的)gap + score
  • 副作用:写入 self.best_params_self.search_results_,并把最优组合合并进 self.paramsrefit=True 时还会用最优参数在 data 上重训 self.model

holdout 而非 CV;目前仅支持 AUC

这是基于你显式提供的 eval_sets 的 holdout 搜索(不是 k-fold 交叉验证),更贴合风控 INS/OOS/OOT 实践。metric 目前仅支持 'auc'

标准化配置自动继承

若该 LRMaster 开了 standardize=True,每个候选会继承相同配置(各自在 data 上拟合 scaler), 保证搜索与最终模型处于同一特征空间。

2. 梯度提升模型 —— GradientBoostingModel

LightGBM / XGBoost / CatBoost 的统一接口。

from Modeling_Tool import GradientBoostingModel

gbm = GradientBoostingModel(
    model_type="lgb",       # 'lgb' / 'xgb' / 'cat'
    params={
        "n_estimators": 500,
        "learning_rate": 0.05,
        "max_depth": 4,
        "num_leaves": 15,
        "min_child_samples": 100,
        "subsample": 0.8,
        "colsample_bytree": 0.8,
        "early_stopping_rounds": 30,
        "eval_metric": "auc",
    },
)
gbm.fit(
    train_X, train_y,
    val_X,   val_y,
)

# 变量重要性
varimp = gbm.get_feature_importance()
print(varimp.head(15))

# 预测(返回正类概率,一维)
proba = gbm.predict(test_X)

# 校准(可选)
gbm.calibrate(val_X, val_y, method="isotonic")

样本权重

GradientBoostingModel.fit 及底层 LightGBMModel / XGBoostModel / CatBoostModel 支持训练集 sample_weight(别名 wgt)和验证集 eval_sample_weight

gbm.fit(
    train_woe[woe_features], train_woe["bad_flag"],
    test_woe[woe_features],  test_woe["bad_flag"],
    sample_weight=train_woe["sample_wgt"],
    eval_sample_weight=test_woe["sample_wgt"],
)

CatBoost 通过 Pool(weight=...) 注入权重。calibrate 与内部 roc_auc / brier_score 评估同样接受 sample_weight

lgbm_quick_train / xgbm_quick_train 可用 val_wgt_col 指定验证集权重列:

from Modeling_Tool import lgbm_quick_train

model = lgbm_quick_train(
    train_X, train_y, val_X, val_y,
    params={"n_estimators": 200},
    val_wgt_col="sample_wgt",
)

CatBoost 示例(model_type="cat"

CatBoost 走同一套接口,只需把 model_type 换成 "cat"。统一参数名(n_estimators / max_depth)会自动映射到 CatBoost 原生参数(iterations / depth),无需改动其余调用代码; 若数据里有原始类别列,可直接用 cat_features 交给 CatBoost 原生处理(无需先做 WOE / one-hot)。

from Modeling_Tool import GradientBoostingModel

cat = GradientBoostingModel(
    model_type="cat",       # CatBoost
    params={
        "n_estimators": 500,        # 等价于 CatBoost 的 iterations
        "learning_rate": 0.05,
        "max_depth": 6,             # 等价于 CatBoost 的 depth
        "l2_leaf_reg": 3.0,
        "subsample": 0.8,
        "early_stopping_rounds": 30,
        "eval_metric": "AUC",
        "cat_features": ["city_grade"],   # 可选:直接传原始类别列
    },
)
cat.fit(train_X, train_y, val_X, val_y)

# 与 lgb / xgb 完全一致的下游用法
varimp = cat.get_feature_importance()
proba = cat.predict(test_X)

关键参数

参数 默认值 说明
model_type "lgb" "lgb" / "xgb" / "cat"
n_estimators 100 树数量
learning_rate 0.1 学习率
max_depth -1 最大深度(-1=不限)
early_stopping_rounds None 早停轮数
eval_metric "auc" 评估指标

单独使用 LightGBMModel / XGBoostModel / CatBoostModel

from Modeling_Tool import LightGBMModel, XGBoostModel, CatBoostModel

lgb = LightGBMModel(params={"n_estimators": 200, "learning_rate": 0.05})
lgb.fit(train_X, train_y, val_X, val_y)

xgb = XGBoostModel(params={"n_estimators": 200, "max_depth": 6})
xgb.fit(train_X, train_y, val_X, val_y)

cat = CatBoostModel(params={"n_estimators": 200, "max_depth": 6})
cat.fit(train_X, train_y, val_X, val_y)

CatBoost 标准建模 —— CatBoostModel

CatBoostModel 既可单独使用,也可通过 GradientBoostingModel("cat", ...) 以统一接口调用。 它最大的特点是原生处理类别特征:通过 cat_features 指定原始类别列(列名或列索引), CatBoost 会用 ordered target statistics 内部编码,无需事先做 WOE / one-hot。

from Modeling_Tool import CatBoostModel

cat = CatBoostModel(
    params={
        "n_estimators": 500,        # -> iterations
        "learning_rate": 0.05,
        "max_depth": 6,             # -> depth
        "l2_leaf_reg": 3.0,
        "early_stopping_rounds": 30,
        "eval_metric": "AUC",
        "cat_features": ["city_grade"],   # 原始类别列,免编码
    },
)
cat.fit(train_X, train_y, val_X, val_y)

varimp = cat.get_feature_importance()
proba = cat.predict(test_X)

WOE 流水线里通常不需要 cat_features

评分卡标准流程会先把所有特征 WOE 编码成数值列,此时入模特征已无原始类别列, 可不传 cat_features。仅当你直接把原始类别列喂给 CatBoost 时才需要它。

快速训练函数

from Modeling_Tool import lgbm_quick_train, xgbm_quick_train

model = lgbm_quick_train(train_X, train_y, val_X, val_y,
                         params={"n_estimators": 200})

增量学习(Warm-start)

在已有模型基础上、用新数据继续训练,而非从头开始。GradientBoostingModel 提供一套同时兼容 lgb 和 xgb 的接口:用旧模型的 log-odds 输出作为 init_score 在新数据上继续学习,打分时再把「旧模型 margin + 新模型贡献」融合成最终概率。

lgb / xgb 的差异

环节 XGBoost LightGBM
取原始 margin(log-odds) predict(X, output_margin=True) predict(X, raw_score=True)
训练时传偏移 fit(X, y, base_margin=...) fit(X, y, init_score=...)
预测时直接加偏移 原生支持 不支持

GradientBoostingModel 把这些差异封装在内部:对外统一用 init_score,预测融合统一走 sigmoid(base_margin + 新模型 raw score)——这是唯一对两种框架行为一致的做法(LightGBM 在预测期并不支持注入 init_score)。

三步用法

from Modeling_Tool import GradientBoostingModel

# 1) 用旧模型取 base margin(log-odds),作为增量训练的起点
base_margin_train = init_model.get_base_margin(train_X)

# 2) 增量训练:把 base margin 当作 init_score 传入(lgb / xgb 统一用 init_score)
new_model = GradientBoostingModel("xgb", params)   # "lgb" 同理
new_model.fit(train_X, train_y, val_X, val_y, init_score=base_margin_train)

# 3) 融合预测:sigmoid(base_margin + 新模型 raw score)
base_margin_score = init_model.get_base_margin(score_X)
proba = new_model.predict_with_base_margin(score_X, base_margin_score, return_prob=True)
# return_prob=False 则返回融合后的原始 log-odds

偏移只作用于训练集

与常见生产实现一致,init_score 偏移只注入训练集;验证集未加偏移,因此早停的 eval 指标是在「未加偏移」的空间上评估的。如需严格一致,可后续透传 lgb 的 eval_init_score / xgb 的 base_margin_eval_set

init_model 应是 GradientBoostingModel

get_base_margin / predict_with_base_marginGradientBoostingModel 的实例 方法,因此基准模型 init_model 也应是 GradientBoostingModel(而非裸 LGBMClassifier / XGBClassifier)。

3. 后向变量消元 —— BackwardVariableEliminator

基于累计重要性阈值逐步剔除变量,常用于轻量级变量筛选

from Modeling_Tool import BackwardVariableEliminator

eliminator = BackwardVariableEliminator(
    model_type="lgb",
    train_data=train_woe,
    validation_data=test_woe,
    oot_data=oot_woe,
    params={"n_estimators": 100, "learning_rate": 0.1},
    y="bad_flag",
    weight_col="sample_wgt",              # 训练集权重列
    validation_weight_col="sample_wgt",   # 验证集权重列
    results_output_dir="./output/",   # 构造器参数,非 fit 参数
    modelsave_dir="./models/",
)

eliminator.fit(x=woe_features)
result = eliminator.analyze()
print(result)   # 后向消元每轮的变量数与性能

底层 backward_lgbm / backward_xgbm 同样接受 weight_colvalidation_weight_col, 训练与性能汇总(get_perf_summary)均按权重计算。

工作原理

  1. 用全部特征训练一轮 LGB,记录每个特征的 gain 重要性
  2. 剔除累计重要性 < 阈值的变量(如 < 0.001
  3. 重复 1–2 直至剩余变量数达下限或 AUC 不再提升

4. 模型持久化

from Modeling_Tool import save_model, load_model

save_model(gbm._model.model, "./models/gbm_v1.pkl")

# 加载
loaded = load_model("./models/gbm_v1.pkl")

5. 评分函数

from Modeling_Tool import scoring

scores = scoring(
    data=new_df,
    model=gbm._model.model,
    varlist=woe_features,
    scr_name="prob",
)

模型对比实践

from Modeling_Tool import (
    LRMaster, GradientBoostingModel, PerformanceEvaluator,
)

models = {
    "LR":   LRMaster({"C": 1.0}),
    "LGB":  GradientBoostingModel("lgb", {"n_estimators": 200}),
    "XGB":  GradientBoostingModel("xgb", {"n_estimators": 200}),
    "CAT":  GradientBoostingModel("cat", {"n_estimators": 200}),
}

results = {}
for name, model in models.items():
    if name == "LR":
        model.fit(train_woe, woe_features, "bad_flag", weight_col="sample_wgt")
    else:
        model.fit(train_woe[woe_features], train_woe["bad_flag"],
                  test_woe[woe_features],  test_woe["bad_flag"],
                  sample_weight=train_woe["sample_wgt"],
                  eval_sample_weight=test_woe["sample_wgt"])

    raw_model = model._model.model if name != "LR" else model.model
    evaluator = PerformanceEvaluator(
        tgt_name="bad_flag",
        model=raw_model,
        feature_cols=woe_features,
        weight_col="sample_wgt",
    )
    perf = evaluator.add_dataset("train", train_woe) \
                    .add_dataset("test",  test_woe).evaluate()
    results[name] = perf

# 对比 KS / AUC
for name, perf in results.items():
    print(f"{name}: AUC={perf['AUC'].mean():.4f}  KS={perf['KS'].mean():.4f}")

常见问题

LightGBM 训练报 categorical_feature 错误

确保类别列在 DataFrame 中是 category dtype,或在 params 中设置:

params["categorical_feature"] = ["city_grade"]
CatBoost 的参数别名:n_estimators / max_depth 还是 iterations / depth

GradientBoostingModel("cat", ...)CatBoostModel 接受统一参数名,让三种 GBM 共用同一套配置:n_estimators 会映射到 CatBoost 原生的 iterationsmax_depth 映射到 depth。你也可以直接写 CatBoost 原生名(iterations / depth),二者择一即可。 建议不要同时传别名与原生名,以免产生歧义;其余参数(learning_ratel2_leaf_regearly_stopping_roundseval_metric 等)按 CatBoost 原生名透传。

CatBoost 如何处理类别特征?

CatBoost 原生支持类别特征:在 params 里用 cat_features 指定原始类别列(列名或列索引), CatBoost 会用 ordered target statistics 内部编码,无需 WOE / one-hot 预处理:

cat = GradientBoostingModel("cat", {
    "n_estimators": 300,
    "cat_features": ["city_grade", "channel"],   # 原始类别列
})
cat.fit(train_X, train_y, val_X, val_y)

注意:训练集与验证 / 打分数据必须含有相同的列;若已走 WOE 流水线(特征均为数值), 则通常不需要 cat_features

变量重要性总和不为 1

get_feature_importance(importance_type='gain') 返回归一化的相对值, sum 应为 1.0;若返回 split,则按分裂次数加权。

开启标准化后系数变了很多 / 解读不一样

这是预期行为。standardize=True 后模型在标准化空间训练,get_variable_importance()get_statsmodel_summary() 给出的是标准化系数;如需原始单位下的系数,请关闭标准化 (standardize=False,默认)后重新训练。

如何做超参搜索 / 交叉验证

LRMaster 内置了基于 holdout 的网格搜索 grid_search_params(...)(见上文「超参网格搜索」)。 若想用 k-fold 交叉验证,或为 GradientBoostingModel 做超参搜索,可用 sklearn 的 cross_val_score 自行包装(注意 GBM 取底层估计器用 model._model.model,LR 用 model.model):

from sklearn.model_selection import cross_val_score
scores = cross_val_score(model._model.model, X, y, cv=5, scoring="roc_auc")

GBM 超参搜索见 GBM 超参搜索

何时使用样本权重?weight_colsample_weight 有何区别?

典型场景:抽样偏差校正(如过采样后给原始样本更高权重)、按金额/余额加权、 按时间衰减加权等。weight_col 从 DataFrame 列解析(推荐,与评估侧一致); sample_weight 直接传 numpy 数组。二者不可同时传入。 评估侧统一用 weight_col;底层绘图函数用 sample_weight 键(见 模型评估)。