摘要: 模型评估是机器学习工作流程中的核心环节,选择合适的评估指标直接影响模型优化的方向与最终效果。本文系统梳理了分类、回归两大任务场景下的核心评估指标,从混淆矩阵出发,深入讲解准确率、精确率、召回率、F1分数等分类指标的理论意义与代码实现;详细阐述ROC曲线与AUC的原理、绘制方法及阈值选择策略;对比PR曲线在不同数据分布下的适用性;并涵盖MAE、MSE、RMSE、R²、MAPE等回归指标的含义与Python实现。通过完整的Scikit-learn代码示例,帮助读者快速掌握模型评估的实战技巧。
关键词: 机器学习;模型评估;混淆矩阵;ROC曲线;AUC;F1分数;scikit-learn
1. 引言
机器学习的核心目标是构建能够泛化到新数据的模型,而模型评估正是检验这一目标是否达成的关键步骤。无论是经典的监督学习还是新兴的深度学习,模型评估指标都是连接训练过程与实际应用的桥梁。一个看似"准确率99%"的模型,可能因为数据不平衡而毫无价值;一个AUC为0.5的分类器,可能在特定阈值下成为业务利器。因此,深入理解各类评估指标的内涵与适用场景,是每一位机器学习从业者的必修课。
本文将按照分类任务与回归任务两大类别,系统讲解主流评估指标的数学原理,并通过scikit-learn给出完整代码示例。读者可根据自身业务场景,快速定位并上手适用的评估方法。
2. 分类评估指标
2.1 混淆矩阵:所有指标的基石
混淆矩阵(Confusion Matrix)是二分类问题评估的核心工具,以矩阵形式直观展示模型预测与真实标签的对照关系。矩阵的每一行代表模型预测的类别,每一列代表真实类别。
对于二分类问题,混淆矩阵的结构如下:
| 预测为正类 | 预测为负类 | |
|---|---|---|
| 实际为正类 | TP(真正例) | FN(假负例) |
| 实际为负类 | FP(假正例) | TN(真负例) |
四个基本概念定义如下:
-
TP(True Positive):实际为正,预测为正------预测正确
-
FP(False Positive):实际为负,预测为正------漏诊负类,误报
-
FN(False Negative):实际为正,预测为负------漏诊正类,漏报
-
TN(True Negative):实际为负,预测为负------预测正确
# Python代码示例:混淆矩阵的构建与可视化
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
# 1. 生成模拟的二分类数据集
# n_samples=5000:样本数量5000
# n_features=20:特征数量20
# n_informative=15:有信息量的特征数量15
# n_redundant=5:冗余特征数量5
# weights=[0.9, 0.1]:类别权重,负类占90%,正类占10%(模拟不平衡数据)
X, y = make_classification(
n_samples=5000,
n_features=20,
n_informative=15,
n_redundant=5,
weights=[0.9, 0.1], # 不平衡数据集,正类仅占10%
random_state=42
)
# 2. 划分训练集和测试集
# test_size=0.3:30%作为测试集
# random_state=42:保证结果可复现
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# 3. 训练逻辑回归模型
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
# 4. 在测试集上预测
y_pred = model.predict(X_test)
# 5. 计算混淆矩阵
# confusion_matrix 返回值顺序: [[TN, FP], [FN, TP]]
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(cm)
# 输出示例:
# [[1345 32]
# [ 98 25]]
# TN=1345, FP=32, FN=98, TP=25
# 6. 可视化混淆矩阵
# titles_options: 可选多种显示格式
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:带数值标签的混淆矩阵
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["负类(0)", "正类(1)"])
disp.plot(ax=axes[0], cmap="Blues")
axes[0].set_title("混淆矩阵(数值标签)", fontsize=14)
# 右图:归一化后的混淆矩阵(每个单元格表示占比)
cm_normalized = cm.astype("float") / cm.sum(axis=1)[:, np.newaxis]
disp_norm = ConfusionMatrixDisplay(confusion_matrix=cm_normalized, display_labels=["负类(0)", "正类(1)"])
disp_norm.plot(ax=axes[1], cmap="Blues")
axes[1].set_title("混淆矩阵(按行归一化)", fontsize=14)
plt.tight_layout()
plt.savefig("confusion_matrix.png", dpi=150, bbox_inches="tight")
plt.show()
# 7. 打印详细分类报告(包含各类指标的精确值)
# classification_report 输出每个类别的 Precision/Recall/F1-Score/Support
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=["负类(0)", "正类(1)"]))
运行上述代码后,将得到混淆矩阵的热力图与标准化矩阵,从图中可直观看出:虽然整体准确率较高(因负类样本占主导),但正类的召回率极低,说明模型对少数类的识别能力严重不足。
2.2 准确率(Accuracy)
公式:
Accuracy = \\frac{TP + TN}{TP + TN + FP + FN}
准确率是最直观、最常用的分类指标,表示所有预测中正确预测的比例。其取值范围为[0, 1],值越接近1表示模型越好。然而,准确率在数据不平衡场景下具有严重的欺骗性。例如,癌症筛查数据中阳性率仅1%,模型即使把所有样本都预测为阴性,准确率也能达到99%,但这毫无意义。
from sklearn.metrics import accuracy_score
# 计算准确率
acc = accuracy_score(y_test, y_pred)
print(f"准确率 (Accuracy): {acc:.4f}")
# 说明:在不平衡数据集下,99%的准确率可能掩盖正类召回率仅10%的问题
使用场景: 数据类别相对平衡(各类别样本比例相近)时,准确率是快速评估模型整体性能的不错选择。但面对不平衡数据,务必结合其他指标综合判断。
2.3 精确率(Precision)
公式:
Precision = \\frac{TP}{TP + FP} = \\frac{预测为正类中实际为正类的数量}{所有预测为正类的数量}
精确率衡量的是预测为正的样本中,有多少是真正的正类。它反映的是模型"不误报"的能力。在推荐系统中,精确率高意味着推荐的内容用户大多确实感兴趣;在垃圾邮件检测中,精确率高意味着被标记为垃圾邮件的邮件确实是垃圾邮件。
from sklearn.metrics import precision_score
# 计算精确率
# zero_division=0:防止分母为0时出现警告
prec = precision_score(y_test, y_pred, zero_division=0)
print(f"精确率 (Precision): {prec:.4f}")
使用场景: 当"误报代价高"时,优先关注精确率。例如:
-
股票推荐系统:误推荐一只会跌的股票给用户,会造成直接经济损失
-
医疗诊断:误诊一个健康人为患者,会导致不必要的进一步检查和患者焦虑
2.4 召回率(Recall / Sensitivity)
公式:
Recall = \\frac{TP}{TP + FN} = \\frac{预测为正类中实际为正类的数量}{所有实际为正类的数量}
召回率衡量的是所有正类样本中,模型成功识别出了多少。它反映的是模型"不漏报"的能力。召回率高意味着尽可能把所有正类样本都找出来。
from sklearn.metrics import recall_score
# 计算召回率
rec = recall_score(y_test, y_pred, zero_division=0)
print(f"召回率 (Recall): {rec:.4f}")
使用场景: 当"漏报代价高"时,优先关注召回率。例如:
-
癌症筛查:漏检一个真正的癌症患者可能导致延误治疗,代价极高
-
欺诈检测:漏掉一笔欺诈交易可能造成重大经济损失
-
地震预测:即使误报也不能漏报,召回率至关重要
2.5 F1分数(F1-Score)
精确率和召回率往往是一对矛盾体------追求高精确率意味着模型更保守(只预测非常有把握的正类),这会导致召回率下降;反之亦然。F1分数正是为调和这一矛盾而设计的。
公式:
F1 = \\frac{2 \\times Precision \\times Recall}{Precision + Recall}
F1是精确率和召回率的调和平均数,相比简单算术平均,它更重视两者的平衡。只有当精确率和召回率都较高时,F1才会高。
from sklearn.metrics import f1_score
# 计算F1分数
f1 = f1_score(y_test, y_pred, zero_division=0)
print(f"F1分数 (F1-Score): {f1:.4f}")
F1的更一般形式是Fβ分数,允许通过β调节对召回率的重视程度:
F_\\beta = (1 + \\beta\^2) \\times \\frac{Precision \\times Recall}{\\beta\^2 \\times Precision + Recall}
-
β < 1:更重视精确率(如F0.5)
-
β = 1:精确率和召回率同等重要(F1)
-
β > 1:更重视召回率(如F2)
from sklearn.metrics import fbeta_score
# 计算F2分数(召回率的权重更高)
# 在医疗检测等场景,F2分数比F1更常用
f2 = fbeta_score(y_test, y_pred, beta=2, zero_division=0)
print(f"F2分数 (F2-Score): {f2:.4f}")
2.6 特异性(Specificity)
公式:
Specificity = \\frac{TN}{TN + FP} = \\frac{预测为负类中实际为负类的数量}{所有实际为负类的数量}
特异性衡量的是模型正确识别负类的能力,与召回率(敏感度)相对应。在医学检测中,特异性高意味着健康人不会被误诊为患者。
# 手动计算特异性
# 从混淆矩阵中提取 TN, FP
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
specificity = tn / (tn + fp)
print(f"特异性 (Specificity): {specificity:.4f}")
# 或者用 sklearn 的 recall_score,pos_label 参数指定为负类
specificity_sklearn = recall_score(y_test, y_pred, pos_label=0)
print(f"特异性 (使用sklearn): {specificity_sklearn:.4f}")
2.7 敏感度(Sensitivity)
敏感度(Sensitivity)与召回率(Recall)是同一概念,都是指TPR(True Positive Rate):
Sensitivity = TPR = \\frac{TP}{TP + FN}
# 敏感度与召回率等价
sensitivity = recall_score(y_test, y_pred)
print(f"敏感度 (Sensitivity): {sensitivity:.4f}")
3. ROC曲线与AUC
3.1 TPR与FPR
在深入ROC曲线之前,需要先理解两个核心概念:
-
TPR(True Positive Rate)= 召回率 = 敏感度:所有正类中被正确识别的比例
-
FPR(False Positive Rate):所有负类中被错误识别为正类的比例
FPR = \\frac{FP}{FP + TN}
ROC曲线(Receiver Operating Characteristic Curve)正是以FPR为横轴,TPR为纵轴,展示模型在不同分类阈值下的表现。
# 概念说明:手动计算不同阈值下的 TPR 和 FPR
y_scores = model.predict_proba(X_test)[:, 1] # 获取正类的预测概率
# 手动计算 ROC 曲线上的一些点
thresholds = [0.1, 0.3, 0.5, 0.7, 0.9]
for thresh in thresholds:
# 大于阈值为正类
y_pred_thresh = (y_scores >= thresh).astype(int)
tp = np.sum((y_pred_thresh == 1) & (y_test == 1))
fp = np.sum((y_pred_thresh == 1) & (y_test == 0))
tn = np.sum((y_pred_thresh == 0) & (y_test == 0))
fn = np.sum((y_pred_thresh == 0) & (y_test == 1))
tpr = tp / (tp + fn) if (tp + fn) > 0 else 0
fpr = fp / (fp + tn) if (fp + tn) > 0 else 0
print(f"阈值={thresh:.1f}: TPR={tpr:.4f}, FPR={fpr:.4f}")
3.2 ROC曲线绘制
from sklearn.metrics import roc_curve, auc, roc_auc_score
import matplotlib.pyplot as plt
# 1. 获取预测概率(而非直接预测标签)
# predict_proba 返回每行样本属于各类别的概率,第2列是正类(1)的概率
y_prob = model.predict_proba(X_test)[:, 1]
# 2. 计算不同阈值下的 FPR 和 TPR
# fpr, tpr, thresholds:分别为假正率、真正率和对应阈值
fpr_arr, tpr_arr, thresholds = roc_curve(y_test, y_prob)
# 3. 计算AUC值
# AUC是ROC曲线下的面积,取值范围[0, 1]
auc_score = auc(fpr_arr, tpr_arr)
print(f"AUC值: {auc_score:.4f}")
# 也可以直接用 roc_auc_score(输入真实标签和预测概率)
auc_direct = roc_auc_score(y_test, y_prob)
print(f"AUC值 (直接计算): {auc_direct:.4f}")
# 4. 绘制ROC曲线
plt.figure(figsize=(8, 6))
plt.plot(fpr_arr, tpr_arr, color="darkorange", lw=2,
label=f"ROC曲线 (AUC = {auc_score:.4f})")
plt.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--",
label="随机猜测 (AUC = 0.5)")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel("假正率 (FPR)", fontsize=12)
plt.ylabel("真正率 (TPR)", fontsize=12)
plt.title("ROC曲线", fontsize=14)
plt.legend(loc="lower right", fontsize=11)
plt.grid(alpha=0.3)
# 标注一些关键阈值点
for i in range(0, len(thresholds), len(thresholds)//5):
if thresholds[i] < 1:
plt.annotate(f"阈值={thresholds[i]:.2f}",
(fpr_arr[i], tpr_arr[i]),
textcoords="offset points",
xytext=(5, -10),
fontsize=9,
arrowprops=dict(arrowstyle="->", color="gray"))
plt.tight_layout()
plt.savefig("roc_curve.png", dpi=150, bbox_inches="tight")
plt.show()
3.3 AUC的含义
AUC(Area Under the Curve)即ROC曲线下的面积,其核心含义是:随机从正负样本中各抽取一个,正类样本的预测分数高于负类样本预测分数的概率。
-
AUC = 1.0:完美分类器(理想情况,不存在)
-
AUC = 0.5:随机猜测,等同于无分辨能力
-
AUC < 0.5:比随机猜测还差,说明模型预测方向反了(可通过反转预测结果来改善)
AUC的特点:
-
不受阈值影响:AUC描述的是模型在所有阈值下的整体排序能力,而非某个特定阈值下的表现
-
对数据不平衡不敏感:由于只看相对排序,不受正负样本比例影响
-
衡量模型区分能力:本质上,AUC评估的是模型区分正负样本的能力
# AUC的另一个理解:正样本score > 负样本score 的概率
# 验证计算
pos_scores = y_prob[y_test == 1]
neg_scores = y_prob[y_test == 0]
# 计算正样本分数大于负样本分数的比例
count = 0
total = len(pos_scores) * len(neg_scores)
for p in pos_scores:
for n in neg_scores:
if p > n:
count += 1
auc_manual = count / total
print(f"AUC (手动验证): {auc_manual:.4f}")
3.4 如何选择最佳阈值
ROC曲线上的每个点对应一个分类阈值。选择最佳阈值需要权衡TPR和FPR,常用方法包括:
方法一:Youden's J统计量
J = TPR - FPR
选择使J值最大的阈值,即ROC曲线上距离对角线最远的点。
# 使用 Youden's J 统计量选择最佳阈值
j_scores = tpr_arr - fpr_arr
best_idx = np.argmax(j_scores)
best_threshold = thresholds[best_idx]
best_tpr = tpr_arr[best_idx]
best_fpr = fpr_arr[best_idx]
print(f"最佳阈值 (Youden's J): {best_threshold:.4f}")
print(f"对应 TPR: {best_tpr:.4f}, FPR: {best_fpr:.4f}")
方法二:几何平均(G-Mean)
G\\text{-}Mean = \\sqrt{TPR \\times Specificity}
G-Mean综合考虑正类和负类的识别率,在不平衡数据集上表现良好。
# 使用 G-Mean 选择最佳阈值
specificity_arr = 1 - fpr_arr
g_means = np.sqrt(tpr_arr * specificity_arr)
best_g_idx = np.argmax(g_means)
best_g_threshold = thresholds[best_g_idx]
print(f"最佳阈值 (G-Mean): {best_g_threshold:.4f}")
print(f"对应 TPR: {tpr_arr[best_g_idx]:.4f}, Specificity: {specificity_arr[best_g_idx]:.4f}")
4. PR曲线
4.1 什么是PR曲线
PR曲线(Precision-Recall Curve)以召回率为横轴,精确率为纵轴 ,展示不同阈值下两者之间的权衡关系。相比ROC曲线,PR曲线更能反映模型在不平衡数据集上的真实表现。
from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.metrics import auc
# 1. 计算PR曲线
# 返回不同阈值下的精确率、召回率和对应阈值
precision_arr, recall_arr, pr_thresholds = precision_recall_curve(y_test, y_prob)
# 2. 计算PR曲线下面积(Average Precision)
# AP是PR曲线下面积的近似,等效于AUC在PR空间中的值
ap_score = average_precision_score(y_test, y_prob)
print(f"平均精确率 (Average Precision): {ap_score:.4f}")
# 3. 绘制PR曲线
plt.figure(figsize=(8, 6))
plt.plot(recall_arr, precision_arr, color="green", lw=2,
label=f"PR曲线 (AP = {ap_score:.4f})")
# 标注基线(正类比例)
baseline = np.sum(y_test) / len(y_test)
plt.axhline(y=baseline, color="red", linestyle="--", lw=1.5,
label=f"基线 (正类比例 = {baseline:.4f})")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel("召回率 (Recall)", fontsize=12)
plt.ylabel("精确率 (Precision)", fontsize=12)
plt.title("精确率-召回率曲线 (PR Curve)", fontsize=14)
plt.legend(loc="upper right", fontsize=11)
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig("pr_curve.png", dpi=150, bbox_inches="tight")
plt.show()
4.2 为什么PR曲线在不平衡数据下更可靠
当数据集严重不平衡时,ROC曲线的FPR分母(TN+FP)中TN占主导,导致FPR变化不明显,ROC曲线的变化不足以反映模型对少数类的识别能力变化。而PR曲线直接以精确率和召回率为坐标,更敏感地反映少数类的表现。
# 对比:在极度不平衡数据下ROC和PR曲线的差异
# 生成极度不平衡数据(正类仅占1%)
X_imbalanced, y_imbalanced = make_classification(
n_samples=10000,
n_features=20,
n_informative=10,
weights=[0.99, 0.01], # 1%正类
random_state=42
)
X_train_imb, X_test_imb, y_train_imb, y_test_imb = train_test_split(
X_imbalanced, y_imbalanced, test_size=0.3, random_state=42, stratify=y_imbalanced
)
model_imb = LogisticRegression(max_iter=1000, random_state=42)
model_imb.fit(X_train_imb, y_train_imb)
y_prob_imb = model_imb.predict_proba(X_test_imb)[:, 1]
# 计算 ROC AUC 和 PR AUC
roc_auc_imb = roc_auc_score(y_test_imb, y_prob_imb)
ap_imb = average_precision_score(y_test_imb, y_prob_imb)
print(f"极度不平衡数据 - ROC AUC: {roc_auc_imb:.4f}")
print(f"极度不平衡数据 - Average Precision (PR AUC): {ap_imb:.4f}")
# PR曲线的AP很低说明模型在少数类上的精确率和召回率都很差
# 而ROC AUC看起来可能还不错(因为FPR变化不大)
5. 回归评估指标
回归任务评估的是模型预测的连续值与真实值之间的误差。以下是常用的回归评估指标。
5.1 MAE(平均绝对误差)
公式:
MAE = \\frac{1}{n} \\sum*{i=1}\^{n} \|y_i - \\hat{y}*i\|
MAE是预测值与真实值之差的绝对值的平均,对所有误差赋予相同权重,对异常值相对稳健。
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
# 生成回归数据集
from sklearn.datasets import make_regression
X_reg, y_reg = make_regression(
n_samples=1000,
n_features=10,
noise=20,
random_state=42
)
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(
X_reg, y_reg, test_size=0.3, random_state=42
)
# 训练一个简单的回归模型
from sklearn.linear_model import LinearRegression
reg_model = LinearRegression()
reg_model.fit(X_train_reg, y_train_reg)
y_pred_reg = reg_model.predict(X_test_reg)
# 1. MAE:平均绝对误差
mae = mean_absolute_error(y_test_reg, y_pred_reg)
print(f"MAE (平均绝对误差): {mae:.4f}")
# 手动验证
mae_manual = np.mean(np.abs(y_test_reg - y_pred_reg))
print(f"MAE (手动验证): {mae_manual:.4f}")
5.2 MSE(均方误差)
公式:
MSE = \\frac{1}{n} \\sum*{i=1}\^{n} (y_i - \\hat{y}*i)\^2
MSE对较大的误差给予更大的惩罚(因为平方项放大了大误差的影响),但对异常值敏感。
# 2. MSE:均方误差
mse = mean_squared_error(y_test_reg, y_pred_reg)
print(f"MSE (均方误差): {mse:.4f}")
# 手动验证
mse_manual = np.mean((y_test_reg - y_pred_reg) ** 2)
print(f"MSE (手动验证): {mse_manual:.4f}")
5.3 RMSE(均方根误差)
公式:
RMSE = \\sqrt{MSE} = \\sqrt{\\frac{1}{n} \\sum*{i=1}\^{n} (y_i - \\hat{y}*i)\^2}
RMSE是MSE的平方根,与目标变量具有相同的单位,更容易解释。在物理和工程领域,RMSE比MSE更常用。
# 3. RMSE:均方根误差
rmse = np.sqrt(mse)
print(f"RMSE (均方根误差): {rmse:.4f}")
# sklearn 直接计算
rmse_sklearn = mean_squared_error(y_test_reg, y_pred_reg, squared=False)
print(f"RMSE (sklearn): {rmse_sklearn:.4f}")
5.4 R²(决定系数)
公式:
R\^2 = 1 - \\frac{\\sum*{i=1}\^{n}(y_i - \\hat{y}* i)\^2}{\\sum*{i=1}\^{n}(y_i - \\bar{y})\^2} = 1 - \\frac{SS*{res}}{SS_{tot}}
R²表示模型对目标变量变异性的解释程度,取值范围通常为[0, 1](在OLS框架下),但也可能为负(当模型预测比简单均值还差时)。
-
R² = 1:模型完美预测
-
R² = 0:模型预测效果与简单均值预测相当
-
R² < 0:模型完全失效,预测不如均值
# 4. R²:决定系数
r2 = r2_score(y_test_reg, y_pred_reg)
print(f"R² (决定系数): {r2:.4f}")
# 手动验证
ss_res = np.sum((y_test_reg - y_pred_reg) ** 2) # 残差平方和
ss_tot = np.sum((y_test_reg - np.mean(y_test_reg)) ** 2) # 总平方和
r2_manual = 1 - (ss_res / ss_tot)
print(f"R² (手动验证): {r2_manual:.4f}")
# 说明:R²=0.85意味着模型解释了85%的目标变量方差
5.5 MAPE(平均绝对百分比误差)
公式:
MAPE = \\frac{100\\%}{n} \\sum*{i=1}\^{n} \\left\| \\frac{y_i - \\hat{y}*i}{y_i} \\right\|
MAPE以百分比形式表示预测误差,适合业务报告和跨场景比较。但当真实值接近0时,MAPE会趋向无穷大,需要特殊处理。
# 5. MAPE:平均绝对百分比误差
# 注意:MAPE在sklearn中需要手动计算
def mean_absolute_percentage_error(y_true, y_pred):
"""计算MAPE,处理y_true中含0的情况"""
y_true, y_pred = np.array(y_true), np.array(y_pred)
# 避免除以0:只计算非零样本的MAPE
non_zero_mask = y_true != 0
return np.mean(np.abs((y_true[non_zero_mask] - y_pred[non_zero_mask]) / y_true[non_zero_mask])) * 100
mape = mean_absolute_percentage_error(y_test_reg, y_pred_reg)
print(f"MAPE (平均绝对百分比误差): {mape:.4f}%")
# 注意:MAPE的缺点是当真实值接近0时会变得非常大
# 解决方法是使用对称MAPE(SMAPE)或在真实值极小时限制计算范围
5.6 回归指标对比与选择
# 综合对比所有回归指标
print("=" * 50)
print("回归指标综合对比")
print("=" * 50)
print(f"MAE (平均绝对误差): {mae:.4f}")
print(f"MSE (均方误差): {mse:.4f}")
print(f"RMSE (均方根误差): {rmse:.4f}")
print(f"R² (决定系数): {r2:.4f}")
print(f"MAPE (平均绝对百分比误差): {mape:.4f}%")
print("=" * 50)
# 指标解读
print("\n指标选择指南:")
print("- MAE:误差量纲相同,对异常值稳健,适合评估典型误差")
print("- MSE:惩罚大误差,适合"大错代价高"的场景(如金融风险)")
print("- RMSE:与MAE相同量纲,适合报告和业务沟通")
print("- R²:衡量模型解释力,0.7-0.9通常为较好水平")
print("- MAPE:适合跨尺度比较,但不适合同比接近0的数据")
6. 实战:综合评估示例
下面通过一个完整的实战案例,展示如何针对不同业务场景选择合适的评估指标。
# 综合实战:多模型对比与指标选择
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_predict
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import (
confusion_matrix, accuracy_score, precision_score, recall_score,
f1_score, roc_auc_score, average_precision_score,
classification_report, roc_curve, precision_recall_curve
)
import matplotlib.pyplot as plt
import numpy as np
# 1. 准备不平衡数据集(模拟实际业务场景)
X, y = make_classification(
n_samples=5000, n_features=20, n_informative=15,
n_redundant=5, weights=[0.85, 0.15], # 15%正类
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# 2. 定义三个不同类型的模型
models = {
"逻辑回归": LogisticRegression(max_iter=1000, random_state=42),
"随机森林": RandomForestClassifier(n_estimators=100, random_state=42),
"SVM": SVC(kernel="rbf", probability=True, random_state=42)
}
# 3. 存储评估结果
results = {}
# 4. 对每个模型进行评估
for name, model in models.items():
print(f"\n{'='*50}")
print(f"模型: {name}")
print('='*50)
# 训练模型
model.fit(X_train, y_train)
# 获取预测结果
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
# 计算各项指标
cm = confusion_matrix(y_test, y_pred)
tn, fp, fn, tp = cm.ravel()
accuracy = accuracy_score(y_test, y_pred)
precision = precision_score(y_test, y_pred, zero_division=0)
recall = recall_score(y_test, y_pred, zero_division=0)
f1 = f1_score(y_test, y_pred, zero_division=0)
specificity = tn / (tn + fp)
roc_auc = roc_auc_score(y_test, y_prob)
ap = average_precision_score(y_test, y_prob)
# 存储结果
results[name] = {
"Accuracy": accuracy,
"Precision": precision,
"Recall": recall,
"F1": f1,
"Specificity": specificity,
"ROC_AUC": roc_auc,
"AP": ap,
"y_prob": y_prob
}
# 打印详细报告
print(f"混淆矩阵: TN={tn}, FP={fp}, FN={fn}, TP={tp}")
print(f"准确率 (Accuracy): {accuracy:.4f}")
print(f"精确率 (Precision): {precision:.4f}")
print(f"召回率 (Recall): {recall:.4f}")
print(f"F1分数 (F1-Score): {f1:.4f}")
print(f"特异性 (Specificity): {specificity:.4f}")
print(f"ROC_AUC: {roc_auc:.4f}")
print(f"AP (PR_AUC): {ap:.4f}")
print(f"\n详细分类报告:")
print(classification_report(y_test, y_pred, target_names=["负类", "正类"], zero_division=0))
# 5. 汇总对比表格
print("\n" + "="*80)
print("模型综合对比汇总")
print("="*80)
print(f"{'模型':<15} {'Acc':>8} {'Prec':>8} {'Rec':>8} {'F1':>8} {'Spec':>8} {'AUC':>8} {'AP':>8}")
print("-"*80)
for name, metrics in results.items():
print(f"{name:<15} {metrics['Accuracy']:>8.4f} {metrics['Precision']:>8.4f} "
f"{metrics['Recall']:>8.4f} {metrics['F1']:>8.4f} {metrics['Specificity']:>8.4f} "
f"{metrics['ROC_AUC']:>8.4f} {metrics['AP']:>8.4f}")
print("="*80)
# 6. 绘制对比图
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 6.1 ROC曲线对比
ax1 = axes[0, 0]
colors = ["blue", "green", "red"]
for (name, metrics), color in zip(results.items(), colors):
fpr, tpr, _ = roc_curve(y_test, metrics["y_prob"])
ax1.plot(fpr, tpr, color=color, lw=2, label=f"{name} (AUC={metrics['ROC_AUC']:.4f})")
ax1.plot([0, 1], [0, 1], "k--", lw=1)
ax1.set_xlabel("FPR")
ax1.set_ylabel("TPR")
ax1.set_title("ROC曲线对比")
ax1.legend()
ax1.grid(alpha=0.3)
# 6.2 PR曲线对比
ax2 = axes[0, 1]
for (name, metrics), color in zip(results.items(), colors):
precision, recall, _ = precision_recall_curve(y_test, metrics["y_prob"])
ax2.plot(recall, precision, color=color, lw=2, label=f"{name} (AP={metrics['AP']:.4f})")
baseline = np.sum(y_test) / len(y_test)
ax2.axhline(y=baseline, color="gray", linestyle="--", label=f"基线={baseline:.4f}")
ax2.set_xlabel("Recall")
ax2.set_ylabel("Precision")
ax2.set_title("PR曲线对比")
ax2.legend()
ax2.grid(alpha=0.3)
# 6.3 指标柱状图
ax3 = axes[1, 0]
metrics_to_plot = ["Precision", "Recall", "F1", "AP"]
x = np.arange(len(metrics_to_plot))
width = 0.25
for i, (name, metrics) in enumerate(results.items()):
values = [metrics[m] for m in metrics_to_plot]
ax3.bar(x + i*width, values, width, label=name, color=colors[i], alpha=0.8)
ax3.set_xticks(x + width)
ax3.set_xticklabels(metrics_to_plot)
ax3.set_ylabel("分数")
ax3.set_title("分类指标对比")
ax3.legend()
ax3.grid(alpha=0.3, axis="y")
# 6.4 指标雷达图
ax4 = axes[1, 1]
ax4.axis("off")
# 用表格替代雷达图(雷达图在sklearn外需额外库)
cell_text = []
for name, metrics in results.items():
cell_text.append([
f"{metrics['Accuracy']:.3f}",
f"{metrics['Precision']:.3f}",
f"{metrics['Recall']:.3f}",
f"{metrics['F1']:.3f}",
f"{metrics['Specificity']:.3f}",
f"{metrics['ROC_AUC']:.3f}",
f"{metrics['AP']:.3f}"
])
table = ax4.table(
cellText=cell_text,
rowLabels=list(results.keys()),
colLabels=["Acc", "Prec", "Rec", "F1", "Spec", "AUC", "AP"],
cellLoc="center",
rowLoc="center",
loc="center",
bbox=[0.1, 0.3, 0.8, 0.5]
)
table.auto_set_font_size(False)
table.set_fontsize(10)
table.scale(1.2, 1.5)
ax4.set_title("指标汇总表", fontsize=12, pad=20)
plt.tight_layout()
plt.savefig("model_comparison.png", dpi=150, bbox_inches="tight")
plt.show()
7. 指标选择指南
不同业务场景下,评估指标的侧重点各有不同。以下是常见场景的指标推荐:
7.1 分类任务
| 场景 | 主要指标 | 次要指标 | 说明 |
|---|---|---|---|
| 类别平衡数据 | Accuracy | F1, ROC_AUC | 各类别样本比例相近时,准确率即可反映整体性能 |
| 不平衡数据(少数类重要) | Recall / F1 | ROC_AUC, AP | 如疾病筛查、欺诈检测,漏报代价高 |
| 不平衡数据(误报代价高) | Precision | F1, ROC_AUC | 如推荐系统、垃圾邮件过滤 |
| 排序任务 | ROC_AUC | AP | 只关心正类排在负类前面的概率 |
| 极度不平衡 | PR_AUC (AP) | Recall | ROC曲线在极度不平衡下可能过于乐观 |
| 需要综合权衡 | F1 / Fβ | - | 需要同时控制误报和漏报 |
7.2 回归任务
| 指标 | 特点 | 适用场景 |
|---|---|---|
| MAE | 对异常值稳健,量纲与目标变量一致 | 典型误差评估、成本预测 |
| MSE | 惩罚大误差,对异常值敏感 | 金融风险、重大偏差不可接受场景 |
| RMSE | 与MAE同量纲,易解释 | 物理工程领域、报告与沟通 |
| R² | 衡量模型解释力,与数据尺度无关 | 模型解释力评估、论文报告 |
| MAPE | 百分比形式,跨尺度可比 | 业务报告、需求预测 |
8. 总结
本文系统梳理了机器学习中分类与回归两大任务的核心评估指标:
-
分类任务:从混淆矩阵出发,理解TP/FP/TN/FN四个基本概念是掌握所有分类指标的关键。精确率与召回率的平衡是分类评估的核心挑战,F1分数提供了折中方案;ROC-AUC从全局排序能力角度评估模型,在数据不平衡时比准确率更可靠;PR曲线则在极度不平衡场景下提供更准确的性能画像。
-
回归任务:MAE、MSE、RMSE从不同角度刻画预测误差,R²反映模型的解释力,MAPE以百分比形式便于跨场景比较。
-
实战要点:没有"万能指标",必须结合业务场景和数据特点选择合适的评估体系。数据不平衡时尤其需要谨慎,避免被单一准确率误导。
理解每个指标的数学本质与适用边界,才能在模型开发中做出正确的优化方向决策,真正发挥机器学习模型的价值。