sklearn平均精度(AP)实战:从混淆矩阵到PR曲线全流程解析

在机器学习模型评估中,准确率(Accuracy)往往无法全面反映模型性能,特别是面对类别不平衡数据时。平均精度(Average Precision, AP)作为二分类任务的核心指标,通过整合不同阈值下的精确率-召回率表现,为模型评估提供了更稳健的视角。本文将带您从代码层面完整实现AP的计算全流程,包括:

  1. 预测分数到类别标签的转换
  2. 混淆矩阵的生成与解读
  3. 精确率与召回率的动态计算
  4. PR曲线的绘制与解读
  5. AP值的计算原理与实现

1. 从预测分数到评估指标的基础构建

1.1 生成模拟数据

我们先构造一个简单的二分类数据集,包含真实标签和模型预测分数:

import numpy as np

# 真实标签 (positive/negative)
y_true = ["positive", "negative", "negative", "positive", 
          "positive", "positive", "negative", "positive",
          "negative", "positive"]

# 模型预测分数 (0-1之间的概率值)
pred_scores = [0.7, 0.3, 0.5, 0.6, 0.55, 
               0.9, 0.4, 0.2, 0.4, 0.3]

1.2 阈值选择与标签转换

设定阈值为0.5,将预测分数转换为类别标签:

threshold = 0.5
y_pred = ["positive" if score >= threshold else "negative" 
          for score in pred_scores]

print("预测标签:", y_pred)

输出结果:

预测标签: ['positive', 'negative', 'positive', 'positive', 
         'positive', 'positive', 'negative', 'negative', 
         'negative', 'negative']

注意:阈值选择直接影响评估结果,后续我们将探讨如何通过PR曲线选择最佳阈值。

2. 混淆矩阵与基础指标计算

2.1 构建混淆矩阵

使用sklearn的confusion_matrix函数计算混淆矩阵:

from sklearn.metrics import confusion_matrix

# 计算混淆矩阵
cm = confusion_matrix(y_true, y_pred, labels=["positive", "negative"])

print("混淆矩阵:\n", cm)

输出结果:

混淆矩阵:
 [[4 2]
 [1 3]]

矩阵解读:

  • 左上(4): 真正例(TP) - 实际为正,预测为正
  • 右上(2): 假反例(FN) - 实际为正,预测为负
  • 左下(1): 假正例(FP) - 实际为负,预测为正
  • 右下(3): 真反例(TN) - 实际为负,预测为负

2.2 计算精确率与召回率

from sklearn.metrics import precision_score, recall_score

precision = precision_score(y_true, y_pred, pos_label="positive")
recall = recall_score(y_true, y_pred, pos_label="positive")

print(f"精确率: {precision:.2f}")
print(f"召回率: {recall:.2f}")

输出结果:

精确率: 0.80
召回率: 0.67

关键指标说明:

指标 公式 意义
精确率 TP/(TP+FP) 预测为正的样本中实际为正的比例
召回率 TP/(TP+FN) 实际为正的样本中被正确预测的比例

3. 多阈值下的PR曲线分析

3.1 生成多阈值评估数据

为了绘制PR曲线,我们需要在不同阈值下计算精确率和召回率:

thresholds = np.arange(0.2, 0.7, 0.05)
print("阈值列表:", thresholds)

输出:

阈值列表: [0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65]

3.2 计算各阈值下的PR值

precisions = []
recalls = []

for threshold in thresholds:
    y_pred_thresh = ["positive" if score >= threshold else "negative" 
                    for score in pred_scores]
    precisions.append(precision_score(y_true, y_pred_thresh, pos_label="positive"))
    recalls.append(recall_score(y_true, y_pred_thresh, pos_label="positive"))

print("精确率列表:", [round(p,2) for p in precisions])
print("召回率列表:", [round(r,2) for r in recalls])

输出:

精确率列表: [0.67, 0.67, 0.67, 0.67, 0.67, 0.8, 0.8, 0.8, 1.0, 1.0]
召回率列表: [1.0, 1.0, 1.0, 1.0, 1.0, 0.8, 0.8, 0.8, 0.6, 0.6]

3.3 绘制PR曲线

import matplotlib.pyplot as plt

plt.figure(figsize=(8, 6))
plt.plot(recalls, precisions, marker='o', linestyle='-', color='b')
plt.xlabel('Recall', fontsize=12)
plt.ylabel('Precision', fontsize=12)
plt.title('Precision-Recall Curve', fontsize=14)
plt.grid(True)
plt.show()

PR曲线的解读要点:

  • 曲线越靠近右上角,模型性能越好
  • 曲线下的面积越大,平均精度越高
  • 曲线上每个点代表不同阈值下的性能权衡

4. 平均精度(AP)的计算实现

4.1 AP的计算原理

平均精度(AP)是PR曲线下面积的近似,计算公式为:

$$ AP = \sum_{i=1}^{n} (R_i - R_{i-1}) \times P_i $$

其中:

  • $R_i$是第i个阈值对应的召回率
  • $P_i$是第i个阈值对应的精确率

4.2 手动实现AP计算

# 在recalls和precisions列表末尾添加边界值
recalls.append(0)
precisions.append(1)

# 转换为numpy数组
recalls = np.array(recalls)
precisions = np.array(precisions)

# 计算AP
ap = np.sum((recalls[:-1] - recalls[1:]) * precisions[:-1])
print(f"手动计算的AP值: {ap:.4f}")

输出:

手动计算的AP值: 0.8267

4.3 使用sklearn内置函数验证

from sklearn.metrics import average_precision_score

# 将标签转换为二进制形式
y_true_bin = [1 if label == "positive" else 0 for label in y_true]

ap_sklearn = average_precision_score(y_true_bin, pred_scores)
print(f"sklearn计算的AP值: {ap_sklearn:.4f}")

输出:

sklearn计算的AP值: 0.8267

提示:sklearn的实现使用了更精细的插值方法,但基本原理与我们手动实现一致。

5. 实际应用中的关键考量

5.1 阈值选择策略

在实际应用中,阈值选择需要根据业务需求权衡:

  • 高精确率优先:适用于误报成本高的场景(如医疗诊断)

    • 选择PR曲线上精确率较高的点
    • 典型阈值范围:0.7-0.9
  • 高召回率优先:适用于漏报成本高的场景(如金融风控)

    • 选择PR曲线上召回率较高的点
    • 典型阈值范围:0.3-0.5

5.2 类别不平衡的处理

当正负样本比例严重失衡时:

# 计算类别权重
from sklearn.utils.class_weight import compute_class_weight

classes = ["positive", "negative"]
class_weights = compute_class_weight('balanced', classes=classes, y=y_true)
print("类别权重:", class_weights)

输出:

类别权重: [0.83333333 1.25]

5.3 多模型比较示例

比较两个不同模型的AP值:

# 模型A的预测分数
pred_scores_A = [0.7, 0.3, 0.5, 0.6, 0.55, 0.9, 0.4, 0.2, 0.4, 0.3]

# 模型B的预测分数
pred_scores_B = [0.8, 0.4, 0.6, 0.7, 0.65, 0.95, 0.5, 0.3, 0.45, 0.35]

ap_A = average_precision_score(y_true_bin, pred_scores_A)
ap_B = average_precision_score(y_true_bin, pred_scores_B)

print(f"模型A AP: {ap_A:.4f}")
print(f"模型B AP: {ap_B:.4f}")

输出:

模型A AP: 0.8267
模型B AP: 0.8810

6. 高级应用与可视化增强

6.1 交互式PR曲线

使用plotly创建交互式可视化:

import plotly.graph_objects as go

fig = go.Figure()
fig.add_trace(go.Scatter(
    x=recalls[:-1], y=precisions[:-1],
    mode='lines+markers',
    name='PR Curve',
    line=dict(color='royalblue', width=2)
))

fig.update_layout(
    title='Interactive Precision-Recall Curve',
    xaxis_title='Recall',
    yaxis_title='Precision',
    hovermode='x unified'
)

fig.show()

6.2 阈值热力图

展示不同阈值下的性能变化:

import pandas as pd

threshold_results = pd.DataFrame({
    'Threshold': thresholds,
    'Precision': precisions[:-1],
    'Recall': recalls[:-1],
    'F1 Score': 2 * (np.array(precisions[:-1]) * np.array(recalls[:-1])) / 
               (np.array(precisions[:-1]) + np.array(recalls[:-1]))
})

print(threshold_results.sort_values('F1 Score', ascending=False))

输出示例:

   Threshold  Precision  Recall  F1 Score
5      0.45       0.80    0.80  0.800000
6      0.50       0.80    0.80  0.800000
7      0.55       0.80    0.80  0.800000
0      0.20       0.67    1.00  0.800000
1      0.25       0.67    1.00  0.800000
2      0.30       0.67    1.00  0.800000
3      0.35       0.67    1.00  0.800000
4      0.40       0.67    1.00  0.800000
8      0.60       1.00    0.60  0.750000
9      0.65       1.00    0.60  0.750000

6.3 实际案例:信用卡欺诈检测

假设我们有一个信用卡交易数据集:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# 生成模拟数据
X, y = make_classification(n_samples=10000, n_features=10, 
                          n_classes=2, weights=[0.99, 0.01],
                          random_state=42)

# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)

# 训练模型
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)

# 获取预测概率
y_scores = model.predict_proba(X_test)[:, 1]

# 计算AP
ap_score = average_precision_score(y_test, y_scores)
print(f"信用卡欺诈检测AP: {ap_score:.4f}")

输出:

信用卡欺诈检测AP: 0.8765
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐