欢迎光临
我们一直在努力

金融 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_cost=500, # 客户投诉处理成本
fn_cost=50000, # 平均欺诈损失
tp_benefit=50000,
),
"credit_scoring": CostMatrix(
fp_cost=2000, # 失去一个客户的利息收入
fn_cost=80000, # 平均坏账金额
tp_benefit=2000,
),
"compliance": CostMatrix(
fp_cost=100000, # 监管处罚
fn_cost=20000, # 漏过的可疑交易
tp_benefit=0,
),
}

def __init__(self, scenario: str = "fraud_detection"):
self.scenario = scenario
self.cost_matrix = self.DEFAULT_COST_MATRICES.get(
scenario,
CostMatrix(fp_cost=1, fn_cost=1),
)

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, key=lambda r: r["total_cost"])
# 找到 F1 最大的阈值(对比用)
best_f1 = max(results, key=lambda 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 最优阈值的差距作为模型分析报告的必选项。代价估算不必精确到个位数,但需要给出合理的波动范围,并验证在这个范围内最优阈值的稳定性。

    赞(0)
    未经允许不得转载:171主机测评 » 金融 AI 评测:召回率和精确率的博弈比通用场景更残酷
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址