金融 AI 评测:召回率和精确率的博弈比通用场景更残酷

发布时间:2026/7/23 8:18:30
金融 AI 评测:召回率和精确率的博弈比通用场景更残酷 金融 AI 评测召回率和精确率的博弈比通用场景更残酷一、个性化深度引言在通用 NLP 任务中精确率和召回率的取舍通常遵循 F1 最大化。但在金融场景这个取舍要重新算账。一个例子反欺诈模型漏掉一次欺诈交易低召回率损失可能是 50 万。但如果误拦了 100 次正常交易低精确率客户投诉和监管问询的代价远超 50 万。另一面信用风险评估中错过一个坏客户低召回率可能损失 100 万而拒绝一个好客户低精确率只损失一笔利息收入。同样的指标不同的代价函数。见证奇迹的时刻在于当我们把“误判的经济成本”直接作为损失权重后同一个模型在两个业务场景下的最优阈值差了 0.3。二、个性化原理剖析代价敏感评测框架不同金融场景的代价比场景FP 代价误报FN 代价漏报代价比FN/FP优化方向反欺诈检测中等客户投诉高资金损失5:1 - 10:1偏向高召回信用评估低失去一笔生意高坏账损失3:1 - 8:1偏向高召回合规监控极高监管处罚中等漏过可疑交易0.2:1 - 0.5:1偏向高精确量化信号过滤低错过机会低小损失~1:1平衡高风险客户识别中等极高合规风险10:1 - 50:1强偏向召回代价敏感阈值选择传统方法选择使 F1 最大的阈值。但在金融场景应该选择使总经济代价最小的阈值。假设每个 FP 的代价是 CF每个 FN 的代价是 CFN则总代价为$$TotalCost(\tau) FP(\tau) \times C_F FN(\tau) \times C_{FN}$$最优阈值 $\tau^*$ 满足总代价最小而非 F1 最大。三、个性化代码实践import numpy as np from typing import List, Tuple, Dict, Optional from dataclasses import dataclass dataclass class CostMatrix: 代价矩阵 fp_cost: float # 误报代价元 fn_cost: float # 漏报代价元 tp_benefit: float 0 # TP 带来的收益 tn_benefit: float 0 # TN 带来的收益 property def cost_ratio(self) - float: 误报 vs 漏报的代价比 return self.fn_cost / max(self.fp_cost, 1e-8) class FinancialModelEvaluator: 金融模型评测器代价敏感版本 # 设计原因各金融场景的默认代价矩阵 # 基于行业经验值可通过实际业务数据进行校准 DEFAULT_COST_MATRICES { fraud_detection: CostMatrix( fp_cost500, # 客户投诉处理成本 fn_cost50000, # 平均欺诈损失 tp_benefit50000, ), credit_scoring: CostMatrix( fp_cost2000, # 失去一个客户的利息收入 fn_cost80000, # 平均坏账金额 tp_benefit2000, ), compliance: CostMatrix( fp_cost100000, # 监管处罚 fn_cost20000, # 漏过的可疑交易 tp_benefit0, ), } def __init__(self, scenario: str fraud_detection): self.scenario scenario self.cost_matrix self.DEFAULT_COST_MATRICES.get( scenario, CostMatrix(fp_cost1, fn_cost1), ) def compute_confusion_matrix( self, y_true: np.ndarray, y_score: np.ndarray, threshold: float, ) - Tuple[int, int, int, int]: 计算混淆矩阵 y_pred (y_score threshold).astype(int) tp int(np.sum((y_true 1) (y_pred 1))) fp int(np.sum((y_true 0) (y_pred 1))) fn int(np.sum((y_true 1) (y_pred 0))) tn int(np.sum((y_true 0) (y_pred 0))) return tp, fp, fn, tn def compute_total_cost( self, fp: int, fn: int, tp: int, tn: int ) - float: 计算总经济代价 # 设计原因代价 误判损失 - 正确收益 loss ( fp * self.cost_matrix.fp_cost fn * self.cost_matrix.fn_cost ) benefit ( tp * self.cost_matrix.tp_benefit tn * self.cost_matrix.tn_benefit ) return loss - benefit def find_optimal_threshold( self, y_true: np.ndarray, y_score: np.ndarray, n_thresholds: int 100, ) - Dict: 寻找最优阈值代价最小化 设计原因金融场景下最小化总代价而非最大化 F1 thresholds np.linspace(0, 1, n_thresholds) results [] for tau in thresholds: tp, fp, fn, tn self.compute_confusion_matrix( y_true, y_score, tau ) total_cost self.compute_total_cost(tp, fp, fn, tn) # 计算传统指标 precision tp / max(tp fp, 1) recall tp / max(tp fn, 1) f1 ( 2 * precision * recall / max(precision recall, 1e-8) ) results.append({ threshold: tau, tp: tp, fp: fp, fn: fn, tn: tn, precision: precision, recall: recall, f1: f1, total_cost: total_cost, }) # 找到代价最小的阈值 best_cost min(results, keylambda r: r[total_cost]) # 找到 F1 最大的阈值对比用 best_f1 max(results, keylambda r: r[f1]) return { optimal_threshold: best_cost[threshold], min_cost: best_cost[total_cost], f1_at_optimal: best_cost[f1], precision_at_optimal: best_cost[precision], recall_at_optimal: best_cost[recall], f1_optimal_threshold: best_f1[threshold], max_f1: best_f1[f1], cost_at_f1_optimal: best_f1[total_cost], threshold_gap: abs( best_cost[threshold] - best_f1[threshold] ), } def compare_scenarios( self, y_true: np.ndarray, y_score: np.ndarray, ) - Dict: 对比不同业务场景的最优阈值 comparisons {} for scenario_name in self.DEFAULT_COST_MATRICES: # 临时切换场景 original_scenario self.scenario self.scenario scenario_name self.cost_matrix self.DEFAULT_COST_MATRICES[scenario_name] result self.find_optimal_threshold(y_true, y_score) comparisons[scenario_name] { cost_ratio: self.cost_matrix.cost_ratio, optimal_threshold: result[optimal_threshold], precision: result[precision_at_optimal], recall: result[recall_at_optimal], } # 恢复原场景 self.scenario original_scenario self.cost_matrix self.DEFAULT_COST_MATRICES[original_scenario] return comparisons # 使用示例 np.random.seed(42) # 模拟数据1000 个样本5% 异常率 n_samples 1000 y_true np.random.choice([0, 1], n_samples, p[0.95, 0.05]) # 模拟模型分数正样本分数偏高 y_score np.zeros(n_samples) for i in range(n_samples): if y_true[i] 1: y_score[i] np.random.beta(5, 2) # 偏高分 else: y_score[i] np.random.beta(2, 5) # 偏低分 # 评测不同场景的最优阈值 evaluator FinancialModelEvaluator(fraud_detection) comparison evaluator.compare_scenarios(y_true, y_score) print(各场景最优阈值对比) print(f{场景:15} {代价比:10} {最优阈值:10} {精确率:8} {召回率:8}) print(- * 55) for scenario, metrics in comparison.items(): print( f{scenario:15} f{metrics[cost_ratio]:10.1f} f{metrics[optimal_threshold]:10.4f} f{metrics[precision]:8.4f} f{metrics[recall]:8.4f} )四、个性化边界权衡评测指标适用场景不足F1 Score通用场景、代价对称忽略业务代价的不对称性AUC-ROC模型排序能力不关心最优阈值PR-AUC不平衡数据仍然是统计指标总经济代价金融业务决策需要准确的代价估算净收益高层决策更依赖代价估算关键权衡统计指标 vs 业务指标AUC 0.95 的模型可能在业务上不如 AUC 0.85 但阈值优化更好的模型。评测需要在统计指标和业务指标之间建立映射而不是只看统计指标。代价估算的准确性FP 和 FN 的真实代价很难精确计算。建议给出一个代价范围如 FP 500-2000 元并检查最优阈值在这个范围内是否稳定。多目标优化单一场景可能有多个目标如反欺诈既要低漏报又要低误报。可以使用多目标优化Pareto 前沿方法找到折中方案。五、总结金融 AI 的评测必须从“统计指标最优”转向“经济代价最小”。不同金融场景的 FP/FN 代价比差异巨大——反欺诈和信用评估偏重召回率代价比 5:1 到 50:1合规监控偏重精确率代价比 0.2:1 到 0.5:1。工程上建议建立代价矩阵字典按场景划分 FP 和 FN 的经济成本在模型上线前进行代价敏感阈值搜索将最优阈值与 F1 最优阈值的差距作为模型分析报告的必选项。代价估算不必精确到个位数但需要给出合理的波动范围并验证在这个范围内最优阈值的稳定性。