第 8 章 · 02 概率阈值搜索与 UMP 拦截核心


文档摘要

第 8 章 · 02 概率阈值搜索与 UMP 拦截核心 本节摘要:本节是第 8 章的灵魂——精读 AbuML 中专为"非均衡决策"设计的概率阈值搜索机制。核心函数 用 KFold 跑出每个样本的概率 ,再用 把概率二值化,过滤掉"全是 1 或全是 0"的无效投票行( ),只统计有效投票的准确率(accuracy)与生效率(effectrate),最后给出综合指标 ——准确率与生效率天然对立(阈值越高越准但覆盖越少),score 是两者的权衡。 / 用 生成阈值序列,找到第一个满足 双约束的阈值。 把这套机制落地成运行时拦截器:概率 ≥ threshold → 1,sum != 1 → (默认放行),这正是 UMP 裁判"高放行低拦截,只拦截把握大的交易"的工程实现。

第 8 章 · 02 概率阈值搜索与 UMP 拦截核心

本节摘要:本节是第 8 章的灵魂——精读 AbuML 中专为"非均衡决策"设计的概率阈值搜索机制。核心函数 cross_val_prob_accuracy_score 用 KFold 跑出每个样本的概率 y_prob,再用 binarize(threshold) 把概率二值化,过滤掉"全是 1 或全是 0"的无效投票行(vote_index),只统计有效投票的准确率(accuracy)与生效率(effect_rate),最后给出综合指标 score = effect_rate × accuracy——准确率与生效率天然对立(阈值越高越准但覆盖越少),score 是两者的权衡。search_match_pos_threshold / search_match_neg_thresholdlinspace 生成阈值序列,找到第一个满足 accuracy_match + effect_rate_match 双约束的阈值。predict_proba_threshold 把这套机制落地成运行时拦截器:概率 ≥ threshold → 1,sum != 1 → default_ret(默认放行),这正是 UMP 裁判"高放行低拦截,只拦截把握大的交易"的工程实现。本节还附带讲 importances_coef_pd(特征重要性)、feature_selection(RFE/VarianceThreshold)、train_test_split_xy(混淆矩阵)以及 AbuMLExecute 的四个可视化辅助。读完本节,你将理解 ABU 是怎么把"概率阈值"从一个超参数,升级成一个可搜索、可权衡、可解释的决策对象。

内容来源:原项目源码 abupy/MLBu/ABuML.py 的 cross_val_prob_accuracy_score / search_match_threshold / predict_proba_threshold / importances_coef_pd / feature_selection / train_test_split_xy 部分(L462-693, L758-965, L1102-1285)+ abupy/MLBu/ABuMLExecute.py(run_prob_cv_estimator / run_cv_estimator / plot)。

⚠️ 注意:本节是 UMP 拦截的 ML 基础。第 7 章讲 UMP 裁判用 GMM 聚类找"危险簇",本节讲的是另一条技术路线——用监督学习 + 概率阈值找"危险交易"。两者都服务于"高放行低拦截"的非均衡目标,但实现完全不同。务必把 score = effect_rate × accuracy 的权衡含义吃透,这是 ABU 整个非均衡决策体系的统计核心。

学习目标

阅读完本节,你应当能够:

  1. 逐行读懂 cross_val_prob_accuracy_score 的五步流程(run_prob_cv_estimator → binarize → vote_index 过滤 → accuracy/effect_rate/score 计算)。
  2. 解释 score = effect_rate × accuracy 为什么是准确率与生效率的权衡,以及它在 UMP 场景的对应含义。
  3. 说清 vote_index = (sum > 0) & (sum < 2) 过滤的两种"无效投票"场景(threshold 过低全 1 / 过高全 0)。
  4. 区分 search_match_pos_threshold(linspace 0.50→0.99)与 search_match_neg_threshold(linspace 0.50→0.01 倒序)的适用场景。
  5. 逐行读懂 predict_proba_threshold 的三步(predict_proba → np.where(≥threshold,1,0) → sum != 1 返回 default_ret)及其与 UMP 高放行低拦截的对应。
  6. 说清 importances_coef_pd 的 feature_importances_ / coef_ 分流与 clone(fiter) 的必要性。
  7. 区分 feature_selection 的有监督(RFE ranking)与无监督(VarianceThreshold)两条路径。
  8. 列举 AbuMLExecute 的四个核心函数(run_prob_cv_estimator / run_cv_estimator / plot_learning_curve / plot_roc_estimator)与它们用的 KFold 机制。

一、为什么需要概率阈值搜索:非均衡决策的统计基础

普通分类器的输出是"硬标签"——clf.predict(x) 返回 0 或 1。但 UMP 拦截交易时,这个硬标签太粗糙:一笔"特征略微像危险簇"的交易和"特征极度像危险簇"的交易都被判 1,精度不够。

ABU 的思路:用 predict_proba 拿到概率,然后自己设一个阈值。概率 ≥ 0.9 才算"真危险"(拦截),否则放行。这样能实现"高放行低拦截"——只拦截把握大的交易。

但阈值怎么定?设 0.6 太松(拦很多,但准确率低),设 0.95 太严(准确率高,但覆盖太少,大部分危险交易漏拦)。这就是"准确率 vs 生效率"的权衡。ABU 的解决方案:用 KFold 跑出每个样本的概率,扫描一系列阈值,算每个阈值下的 accuracy 和 effect_rate,找满足双约束的最优阈值

二、cross_val_prob_accuracy_score:概率阈值搜索核心

ABuML.py:462-693 是全节最关键的函数。它被 entry_wrapper(support=(EMLFitType.E_FIT_CLF,)) 装饰——只支持分类(因为 predict_proba 是分类器的特性,回归没有)。

2.1 第一步:KFold 跑 y_prob(L511)

507 x = kwargs.pop('x', self.x) 508 y = kwargs.pop('y', self.y) 509 fiter = self.get_fiter() 510 511 y_prob = ABuMLExecute.run_prob_cv_estimator(fiter, x, y, n_folds=cv)

run_prob_cv_estimator(ABuMLExecute.py:77-127)的实现:

77 def run_prob_cv_estimator(estimator, x, y, n_folds=10): 98 if not hasattr(estimator, 'predict_proba'): 99 print('estimator must has predict_proba') 100 return 102 # 所有执行fit的操作使用clone一个新的 103 estimator = clone(estimator) 104 kf = KFold(len(y), n_folds=n_folds, shuffle=True) 105 y_prob = np.zeros((len(y), len(np.unique(y)))) 118 for train_index, test_index in kf: 119 x_train, x_test = x[train_index], x[test_index] 120 y_train = y[train_index] 121 estimator.fit(x_train, y_train) 122 # 使用predict_proba将y_prob中的对应填数据 123 y_prob[test_index] = estimator.predict_proba(x_test) 124 return y_prob

关键四步:

  1. predict_proba 检查(98-100 行):学习器必须有 predict_proba 方法。SVC 默认没有(要 probability=True),所以 AbuMLCreater 默认初始化 self.clf = self.svc(probability=True) 专门带上。
  2. clone(103 行):clone(estimator) 复制一个干净的学习器,避免 KFold 多次 fit 污染原对象状态。ABuMLExecute 里所有 fit 操作都先 clone,这是个铁律。
  3. 预分配 y_prob(105 行):np.zeros((len(y), len(np.unique(y)))),行数=样本数,列数=标签种类数。比如 891 个样本二分类就是 (891, 2)。
  4. KFold 填充(118-123 行):每折用 train_index 训练,对 test_index 预测概率,填到 y_prob 对应行。shuffle=True 打乱顺序避免时序泄漏。循环结束 y_prob 每一行都是"用没见过该样本的模型预测的概率"——这是无偏估计。

2.2 第二步:binarize 二值化(L522)

522 y_prob_binarize = binarize(y_prob, threshold=pb_threshold)

binarize 是 sklearn.preprocessing 的函数:概率 ≥ threshold → 1,< threshold → 0。比如 y_prob = [[0.87, 0.13], [0.09, 0.91]],threshold=0.6,结果是 [[1, 0], [0, 1]]

阈值越极端(threshold 越接近 0 或 1),结果里"全 1 行"或"全 0 行"越多。这两类都是无效投票(下面解释)。

2.3 第三步:vote_index 过滤无效投票(L619)

617 # 即筛选出非均衡阀值情况下有效的投票行index 619 vote_index = (y_prob_df.sum(axis=1) > 0) & (y_prob_df.sum(axis=1) < 2)

y_prob_df.sum(axis=1) 是每行求和。对二分类,合法投票行的和应该是 1(一个 1 一个 0)。两种无效场景:

  • sum == 0(全 0 行):threshold 太高(如 0.9),所有概率都 < 0.9,binarize 全成 0。这种样本"模型完全没把握"。
  • sum == 2(全 1 行,二分类)或更一般地 sum == 列数:threshold 太低(如 0.1),所有概率都 ≥ 0.1,binarize 全成 1。这种样本"模型觉得所有类都成立",其实是没区分能力。

vote_index 把这两种行都过滤掉,只保留"有明确倾向"的有效投票。源码注释(L620-640)给了两个具体例子:

pb_threshold = 0.1 (太低) prob_0.0 prob_1.0 0 1.0 1.0 ← sum=2, 无效 1 1.0 1.0 ... pb_threshold = 0.9 (太高) prob_0.0 prob_1.0 0 0.0 0.0 ← sum=0, 无效 1 0.0 0.0 ...

2.4 第四步:accuracy / effect_rate / score(L676-693)

675 # 生效数量,投票不合格的不做准确率统计 676 effect_cnt = prob_df.shape[0] 677 # 生效率:effect_cnt / y.shape[0] 678 effect_rate = effect_cnt / y.shape[0] 679 # 生效的数据准确率 680 accuracy = 0.0 681 if effect_cnt > 0: 682 accuracy = metrics.accuracy_score(true_df, prob_df) 683 # 分数:生效比例 * 生效准确率(0-1) 684 score = effect_rate * accuracy 685 if show: 686 self.log_func( 687 'threshold={} prob accuracy={:.2f}, effect cnt={}, effect rate={:.2f}, score={:.2f}'.format(...)) 693 return accuracy, effect_cnt, effect_rate, score

四个返回值:

  • accuracy:metrics.accuracy_score(true_df, prob_df),只在有效投票行上算。这就是为什么 vote_index 重要——它把"模型没把握的样本"排除在准确率统计之外,让 accuracy 反映"模型有把握时的精度"。
  • effect_cnt:有效投票数。threshold 越极端,effect_cnt 越小。
  • effect_rate:effect_cnt / y.shape[0],生效率,即"多少比例的样本模型有把握"。
  • score:effect_rate × accuracy,综合指标。这是 ABU 的核心权衡——单纯追求 accuracy 阈值会很高(0.99),但 effect_rate 极低(0.05),score 只有 0.05;单纯追求 effect_rate 阈值会很低(0.5),accuracy 也会降,score 也不会太高。score 鼓励"既准又覆盖广"的阈值。

源码 docstring(L471-488)给了三个阈值的对比,直观展示 score 的权衡:

threshold=0.6 accuracy=0.83, effect rate=0.98, score=0.81 threshold=0.8 accuracy=0.87, effect rate=0.81, score=0.70 ← accuracy 涨但 score 降 threshold=0.85 accuracy=0.89, effect rate=0.38, score=0.34 ← accuracy 再涨但 score 暴跌

阈值从 0.6 涨到 0.85,accuracy 从 0.83 涨到 0.89(模型更准),但 effect_rate 从 0.98 跌到 0.38(覆盖大减),score 从 0.81 跌到 0.34。最高 score 在 0.6 附近,不是最高 accuracy 处——这就是 ABU 不盲目追求 accuracy 的统计基础。

💡 解剖要点:score = effect_rate × accuracy 的设计动机,完全对应 UMP 拦截场景。UMP 想"高放行低拦截,只拦截把握大的交易"。如果用 threshold=0.6(低阈值),accuracy 0.83(拦 10 个对 8 个),effect_rate 0.98(几乎每笔都拦)——拦得太狠,大量好交易被误杀。如果用 threshold=0.95(高阈值),accuracy 0.95(几乎拦一个准一个),effect_rate 0.1(只拦 10% 的交易)——拦得太保守,大部分坏交易漏掉。score 鼓励找到"accuracy 够高 + effect_rate 够广"的中间阈值,这正是 search_match_pos_threshold 要做的事。

三、search_match_pos_threshold / neg_threshold:linspace 找最优阈值

cross_val_prob_accuracy_score 只评估一个阈值,实战中要扫一系列。ABuML.py:1244-1285search_match_pos_threshold:

1244 def search_match_pos_threshold(self, accuracy_match=0, effect_rate_match=0, pos_num=50, **kwargs): 1264 pos_thresholds = np.linspace(0.50, 0.99, num=pos_num) 1273 with AbuProgress(len(pos_thresholds), 0, 'search pos threshold') as search_pos_progress: 1274 for neg in pos_thresholds: 1275 accuracy, _, effect_rate, _ = self.cross_val_prob_accuracy_score(neg, show=False, **kwargs) 1276 search_pos_progress.show(ext='threshold:{:.2f} accuracy:{:.2f}, effect_rate:{:.2f}'.format( 1277 neg, accuracy, effect_rate)) 1278 if accuracy >= accuracy_match and effect_rate >= effect_rate_match: 1279 # eg: 0.500 satisfy require, accuracy:0.940, effect_rate:1.000 1280 self.log_func('{:.3f} satisfy require, accuracy:{:.3f}, effect_rate:{:.3f}'.format( 1281 neg, accuracy, effect_rate)) 1282 # 返回寻找到的满足条件的阀值 1283 return neg 1284 # 迭代完成所有pos_thresholds,没有找到符合参数需求的二分阀值 1285 self.log_func('pos_thresholds no satisfy require, search failed!')

四步:

  1. linspace 生成阈值序列(1264 行):np.linspace(0.50, 0.99, num=50),从 0.50 到 0.99 均匀取 50 个点,正序(从低到高)。pos 代表"正方向",即阈值 ≥ 0.5 的区间——对应"把握大才投票"的高精度场景。
  2. 逐个评估(1274-1275 行):每个阈值调 cross_val_prob_accuracy_score(show=False 静默),拿 accuracy 和 effect_rate。AbuProgress 显示进度。
  3. 双约束命中即返回(1278-1283 行):accuracy >= accuracy_match AND effect_rate >= effect_rate_match 同时满足,返回当前阈值。注意是"第一个满足的"——因为正序遍历,低阈值先满足(覆盖广但精度低),所以 accuracy_match 要设高一点(如 0.85)才能逼出"够准"的阈值。
  4. 找不到打 log(1285 行):所有阈值都不满足,优雅返回 None。

search_match_neg_threshold(ABuML.py:1200-1242)对称:阈值范围 np.linspace(0.01, 0.50, num=neg_num)[::-1](0.01→0.50 然后倒序,即从 0.50 往 0.01 扫),对应"把握小就投票"的低阈值场景——这种场景下,低 threshold 会让大量样本投票成 1,适合"广撒网"的负向筛选。

典型用法(docstring L1252-1253):

ttn_abu.search_match_pos_threshold(0.85, 0.80, fiter_type=ml.EMLFitType.E_FIT_CLF) # out: 0.770 satisfy require, accuracy:0.850, effect_rate:0.854

返回 threshold=0.77——这是后续 predict_proba_threshold 用的阈值,意味着"predict_proba ≥ 0.77 才判 1"。

四、predict_proba_threshold:UMP 拦截的运行时落地

ABuML.py:1102-1158 是把搜索到的阈值用于运行时拦截的核心函数:

1102 def predict_proba_threshold(self, x, threshold, default_ret, pre_fit=True, **kwargs): 1149 # 套接self.predict_proba对x所描述的特征进行概率proba 1150 proba = self.predict_proba(x, pre_fit=pre_fit, **kwargs) 1151 # eg:array([[ 0.1063, 0.8937]]) -> array([[0, 1]]) 1152 # noinspection PyTypeChecker 1153 proba = np.where(proba >= threshold, 1, 0) 1154 if proba.sum() != 1: 1155 # eg: proba = array([[ 0.2328, 0.7672]])->array([[0, 0]]) 1156 return default_ret 1157 # 唯一最大值就是序列值为1的,通过argmax获取index,即y label 1158 return proba.argmax()

三步逻辑:

  1. predict_proba(1150 行):拿到概率向量,如 [[0.106, 0.894]]
  2. np.where 二值化(1153 行):np.where(proba >= threshold, 1, 0)。threshold=0.77,0.106<0.77→0,0.894≥0.77→1,结果是 [[0, 1]]
  3. sum != 1 返回 default_ret(1154-1156 行):如果 binarize 后行和不是 1(全 0 或全 1),返回 default_ret。这是 vote_index 过滤的运行时版本——"模型没把握"就返回默认值。

最后 proba.argmax() 返回 1 所在的位置(标签 index)。

default_ret:高放行低拦截的物化

default_ret 是这个函数的精髓。docstring(L1131-1133)说得很直白:

应用场景:比如对交易进行拦截,实行高放行率,低拦截率,0代表放行,1代表拦截, 上述predict_proba_threshold(test2, threshold=0.77, default_ret=0) 即可实行对较大概率的交易进行拦截,即把握大的进行拦截,把握不大的默认选择放行

UMP 拦截场景:default_ret=0(放行),threshold=0.77。新交易来了,模型预测概率 [0.23, 0.77]——binarize 后 [0, 1],sum=1,argmax=1(拦截)。如果概率 [0.4, 0.6]——binarize 后 [0, 0](0.6 < 0.77),sum=0,返回 default_ret=0(放行)。

效果:只有模型非常有把握(prob ≥ 0.77)才拦截,否则一律放行。这正是 UMP 裁判"宁纵毋枉"的工程实现。注意第 7 章的 GMM 路线用 need_hit_cnt 多裁判合议实现非均衡,本节用 threshold + default_ret 实现非均衡——两条技术路线,同一个目标。

完整调用链示例

docstring(L1105-1129)给了一个完整例子,展示从 search_match 到 predict 的全流程:

# 1. 训练 + 找最优阈值 ttn_abu = AbuML.create_test_more_fiter() ttn_abu.estimator.svc(probability=True) ttn_abu.search_match_pos_threshold(0.85, 0.80, fiter_type=ml.EMLFitType.E_FIT_CLF) # out: 0.770 satisfy require, accuracy:0.850, effect_rate:0.854 # 2. 新样本预测 test = np.array([1., 0., 0., 1., 1., 0., 0., 1., 0., 1., 0., 0., 0.8132, 0.5868]) ttn_abu.predict_proba(test) # out: array([[0.106, 0.894]]) ← 概率 ttn_abu.predict_proba_threshold(test, threshold=0.77, default_ret=0) # out: 1 ← 0.894 > 0.77, 拦截 test2 = np.array([0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 0., 1., 0.7832, 0.2868]) ttn_abu.predict_proba(test2) # out: array([[0.2372, 0.7628]]) ttn_abu.predict_proba_threshold(test2, threshold=0.77, default_ret=0) # out: 0 ← 0.7628 < 0.77, 放行(default_ret)

五、importances_coef_pd:特征重要性

ABuML.py:830-893importances_coef_pd 用来评估特征贡献度,被 entry_wrapper(support=(E_FIT_CLF, E_FIT_REG)) 装饰(只支持监督学习):

830 @entry_wrapper(support=(EMLFitType.E_FIT_CLF, EMLFitType.E_FIT_REG)) 831 def importances_coef_pd(self, **kwargs): 874 x = kwargs.pop('x', self.x) 875 y = kwargs.pop('y', self.y) 876 fiter = self.get_fiter() 877 # 训练前进行clone(fiter) 878 fiter = clone(fiter) 879 fiter.fit(x, y) 881 if hasattr(fiter, 'feature_importances_'): 882 return pd.DataFrame( 883 {'feature': list(self.df.columns)[1:], 'importance': fiter.feature_importances_}).sort_values( 884 'importance') 885 elif hasattr(fiter, 'coef_'): 886 return pd.DataFrame({"columns": list(self.df.columns)[1:], "coef": list(fiter.coef_.T)}) 887 else: 888 self.log_func('fiter not hasattr feature_importances_ or coef_!')

两个分流:

  • 树模型(随机森林/决策树/AdaBoost/GBDT)有 feature_importances_ 属性,返回每个特征的重要性评分(0-1,和为 1),组成 DataFrame 按 importance 排序。
  • 线性模型(LogisticRegression/SVC-linear/LinearRegression)有 coef_ 属性,返回每个特征的权重系数(可正可负),组成 DataFrame。

878 行 clone(fiter) 必不可少——这个函数会 fit,如果不 clone 会污染 self.estimator.clf/reg 的状态。这是 AbuML 中所有"为了评估而 fit"的函数的统一规范(importances_coef_pd / feature_selection / plot_decision_function 都先 clone)。

list(self.df.columns)[1:] 跳过第一列——ABU 约定 df 第一列是 y,后面才是 x 特征,所以 [1:] 取特征名。这个约定贯穿整个 MLBu 模块(make_xy 里 y = matrix[:, 0]x = matrix[:, 1:])。

六、feature_selection:监督 RFE / 无监督 VarianceThreshold

ABuML.py:758-828@entry_wrapper() 全支持装饰,但内部按 is_supervised_learning 分流:

808 fiter = self.get_fiter() 809 if self.is_supervised_learning(): 810 selector = RFE(fiter) 811 selector.fit(x, y) 812 feature_df = pd.DataFrame({ 813 'support': selector.support_, 'ranking': selector.ranking_}, index=self.df.columns[1:]) 814 if show: 815 self.log_func('RFE selection') 816 self.log_func(feature_df) 817 else: 818 selector = VarianceThreshold() 819 selector.fit(x) 820 feature_df = pd.DataFrame({ 821 'support': selector.get_support()}, index=self.df.columns[1:]) 822 if show: 823 self.log_func('unsupervised VarianceThreshold') 824 self.log_func(feature_df) 825 return feature_df

两条路径:

  • 监督学习 → RFE(Recursive Feature Elimination,递归特征消除):用学习器反复 fit,每次去掉最不重要的特征,最终给每个特征 ranking(1 最重要,数字越大越不重要)和 support(True 保留 / False 剔除)。RFE 需要 fiter 有 coef_feature_importances_
  • 无监督学习 → VarianceThreshold:按方差筛选,方差低于阈值的特征剔除。无需 y,纯看特征自身波动。

RFE 的输出示例(docstring L766-779):

RFE selection ranking support SibSp 1 True ← ranking=1, 保留 Parch 1 True Cabin_No 1 True Cabin_Yes 7 False ← ranking=7, 剔除 Embarked_C 2 False ...

七、train_test_split_xy:切分 + 混淆矩阵 + classification_report

ABuML.py:895-965 提供标准的分类评估流水线:

895 @entry_wrapper(support=(EMLFitType.E_FIT_CLF,)) 896 def train_test_split_xy(self, test_size=0.1, random_state=0, **kwargs): 935 x = kwargs.pop('x', self.x) 936 y = kwargs.pop('y', self.y) 937 train_x, test_x, train_y, test_y = train_test_split(x, y, test_size=test_size, 938 random_state=random_state) 944 fiter = self.get_fiter() 945 fiter = clone(fiter) 946 clf = fiter.fit(train_x, train_y) 947 y_predict = clf.predict(test_x) 948 949 # 度量分类准确率 950 self.log_func("accuracy = %.2f" % (metrics.accuracy_score(test_y, y_predict))) 951 # precision_score和predictions在二分类的情况下使用binary 952 average = 'binary' 953 if len(np.unique(y)) != 2: 954 average = 'macro' 955 self.log_func("precision_score = %.2f" % (metrics.precision_score(test_y, y_predict, average=average))) 956 self.log_func("recall_score = %.2f" % (metrics.recall_score(test_y, y_predict, average=average))) 957 self._confusion_matrix_with_report(test_y, y_predict, labels=np.unique(y))

四步:train_test_split 切分 → clone+fit 训练 → predict → 三度量(accuracy / precision / recall)。注意 952-954 行:二分类用 average='binary',多分类自动切到 average='macro'(各类平均)。

最后调 _confusion_matrix_with_report(ABuML.py:1725-1749),对二分类打印漂亮的 Actual/Predicted 表格,对多分类直接打印 matrix,并附 metrics.classification_report(每个类的 precision/recall/f1/support)。

八、AbuMLExecute 辅助函数族

除了核心的 run_prob_cv_estimator,AbuMLExecute 还提供三个可视化辅助和 run_cv_estimator:

run_cv_estimator(ABuMLExecute.py:130-168)

run_prob_cv_estimator 对称,但用 predict(硬标签)而非 predict_proba(概率):

150 y_pred = y.copy() # ← 用 y.copy() 预填,而不是 zeros 162 for train_index, test_index in kf: 165 estimator.fit(x_train, y_train) 166 # 通过 estimator.predict(x_test)将y_pred中的值逐步替换 167 y_pred[test_index] = estimator.predict(x_test) 168 return y_pred

注意 150 行用 y.copy() 预分配(不是 zeros)——这只是个优化,反正会被每折的 predict 覆盖。返回的 y_pred 是每个样本的硬标签预测,供 plot_confusion_matrices 画混淆矩阵用。

plot_learning_curve(L173-256)

套接 sklearn 的 learning_curve,train_sizes 用 np.linspace(.05, 1., 20) 从 5% 到 100% 递进。绘制训练集/测试集 score 的均值±方差区域,用来判断模型是否过拟合(训练分高、测试分低)或欠拟合(两者都低)。

plot_roc_estimator(L414-518)

固定 10 折 KFold,每折用 predict_proba 算 fpr/tpr,画 10 条 ROC 曲线 + 一条均值 ROC(mean_tpr 用 scipy.interp 线性插值对齐)。这是分类器评估的金标准。

plot_confusion_matrices(L521-572)

run_cv_estimator 拿 y_pred,用 metrics.confusion_matrix 算混淆矩阵,ax.matshow 可视化。颜色数量 len(y_unique) * len(y_unique)(3 类 → 9 色)。

💡 解剖要点:AbuMLExecute 全部函数遵循"先 clone 再 fit"的铁律。这是 sklearn 多线程/多次 fit 的安全规范——sklearn 的学习器 fit 后会改变内部状态(coef_、support_ 等),不 clone 直接 fit 会污染原对象。ABU 把这个规范固化在每个辅助函数里,让上层不用操心。run_prob_cv_estimator 和 run_cv_executor 的区别只在一行(predict_proba vs predict),但语义完全不同——前者返回概率矩阵供阈值搜索,后者返回硬标签供混淆矩阵。

本节要点回顾

  1. cross_val_prob_accuracy_score 五步:run_prob_cv_estimator(KFold + predict_proba 填 y_prob)→ binarize(threshold)→ vote_index 过滤(sum>0 & sum<2)→ accuracy(有效投票行)/effect_rate(有效/总)→ score = effect_rate × accuracy。
  2. score 的权衡含义:accuracy 与 effect_rate 天然对立(阈值越高越准但覆盖越少),score 鼓励"既准又广"。最高 score 不在最高 accuracy 处。UMP 场景对应"高放行低拦截"——只拦把握大的。
  3. vote_index 过滤:threshold 过低→binarize 全 1(sum=列数),threshold 过高→全 0(sum=0),都是无效投票,排除在准确率统计外。
  4. search_match_pos_threshold:linspace(0.50, 0.99, 50) 正序扫,找第一个满足 accuracy_match + effect_rate_match 双约束的阈值;neg_threshold 对称扫 0.01-0.50。
  5. predict_proba_threshold 三步:predict_proba → np.where(≥threshold,1,0) → sum != 1 返回 default_ret(高放行低拦截),sum == 1 返回 argmax。default_ret 是 UMP"宁纵毋枉"的物化。
  6. importances_coef_pd:树模型 feature_importances_(0-1 评分)/ 线性模型 coef_(权重)。必须 clone(fiter) 避免 fit 污染状态。list(self.df.columns)[1:] 跳过第一列 y。
  7. feature_selection:监督 RFE(ranking + support,递归消除)/ 无监督 VarianceThreshold(方差筛选)。按 is_supervised_learning 内部分流。
  8. train_test_split_xy:train_test_split 切分 → clone+fit → predict → accuracy/precision/recall(二分类 binary,多分类 macro)+ 混淆矩阵 + classification_report。
  9. AbuMLExecute 铁律:所有 fit 前先 clone。run_prob_cv_estimator 返回概率矩阵(供阈值搜索),run_cv_estimator 返回硬标签(供混淆矩阵)。plot_roc_estimator 用 10 折 KFold + scipy.interp 画均值 ROC。

下一节,我们用一个完整案例把第 1 节的三层架构和本节的概率阈值搜索串起来——BtcBigWaveClf 比特币大波动分类。重点讲它怎么用三倍数据增强(raw / raw[1:] / raw[2:])把样本量翻三倍,以及 btc_siblings_df 怎么把"3 天 K 线"压缩成"1 条特征",最后由 AbuBTCDayBuy.fit_daybtc_ml.predict 作为买入信号,实现 ML 与回测的衔接。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U