《西瓜书》模型评估与选择之性能度量(含代码)

本文详细介绍了模型评估中的关键指标,包括回归任务的均方误差、分类任务的错误率和精度,以及查准率、查全率、F1分数、P-R曲线、ROC曲线和AUC。通过这些指标,可以全面评估模型的泛化能力,适用于不同的任务需求。同时,文章提供了相应的代码示例,便于理解与实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所谓”性能度量“(performance measure)就是衡量模型泛化能力的评价标准。它反映了任务需求,对于不同的任务需求往往使用不同的性能度量。比如,回归任务中常用的“均方误差”(mean squared error),分类任务中常用的“错误率”(error rate)与“精度”(accuracy)。

均方误差

均方误差是回归任务中最常用的性能度量,对于给定数据集D={(x1, y1), (x2, y2), ……, (xm, ym)},其中yi是样本xi对应的真实值。对于学习器f,评估其性能就是把预测值f(xi)与真实值yi进行比较,定义为:
E ( f ; D ) = 1 m ∑ i = 1 m ( f ( x i − y i ) ) 2 E(f;D)=\frac{1}{m} \sum_{i=1}^m(f(x_i-y_i))^2 E(f;D)=m1i=1m(f(xiyi))2
代码如下

"""
    均方误差
"""
import numpy as np

# 构造数据
test_example_number = 100
predict_label = np.random.normal(0, 1, test_example_number)
label = np.random.normal(0, 1, test_example_number)

# 计算均方误差MSE
def mse(predict, label):
    """
    - predict : 预测值
    - label : 真实值
    """
    total_error = 0
    for i in range(len(label)):
        total_error += (predict_label[i] - label[i]) ** 2
        
    return total_error / len(label)

mse(predict_label, label)

错误率与精度

错误率与精度是分类任务中最常见的两种评价标准,包括二分类和多分类任务。

错误率是分类错误的样本数占样本总数的比例,精度则是分类正确的样本数占样本总数的比例。对数据集D,错误率定义为:
E ( f ; D ) = 1 m ∑ i = 0 m ( Ⅱ ( f ( x i ) ≠ y i ) ) E(f;D)=\frac{1}{m} \sum_{i=0}^m(Ⅱ(f(x_i)≠y_i)) E(f;D)=m1i=0m((f(xi)=yi))
精度定义为:
a c c ( f ; D ) = 1 m ∑ i = 0 m ( Ⅱ ( f ( x i ) = y i ) ) = 1 − E ( f ; D ) acc(f;D)=\frac{1}{m} \sum_{i=0}^m(Ⅱ(f(x_i)=y_i))\\= 1-E(f;D) acc(f;D)=m1i=0m((f(xi)yi))=1E(f;D)
代码如下

"""
    精度
"""
import numpy as np

# 构造数据
classes = 10
test_example_number = 100
predict = np.random.randint(0, classes, test_example_number)
label = np.random.randint(0, classes, test_example_number)
predict = np.eye(classes)[predict]
label = np.eye(classes)[label]

# 计算精度
def acc(predict, label):
    """
    - predict : 预测值one-hot
    - label : 真实值one-hot
    """
    right = len(label) - np.sum((predict - label) ** 2) // 2
    return right / len(label)

acc(predict, label)

查准率、查全率与F1

考虑以下问题:农夫手里有一批西瓜,我们用训练好的模型对这些西瓜进行判定,显然,错误率可以衡量出有多少比例的西瓜被判定错误。但是如果我们更关心“挑出来的西瓜中有多少是好的”,或者“所有的好瓜中有多少比例被挑了出来”,那么错误率就不再适用,此时“查准率”(precision)“查全率”(recall)更适合作为性能度量。

以二分类为例,根据分类结果,构造如下“混淆矩阵”(confusion matrix):

真实情况预测结果
正例反例
正例TP(真正例)FN(假反例)
反例FP(假正例)TN(真反例)

代码如下

"""
    混淆矩阵
"""
import itertools
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# 构造数据
classes_num = 5
classes = ['A', 'S', 'D', 'F', 'G']
test_example_number = 100
predict = np.random.randint(0, classes_num, test_example_number)
label = np.random.randint(0, classes_num, test_example_number)
predict = np.eye(classes_num, dtype=np.int32)[predict]
label = np.eye(classes_num, dtype=np.int32)[label]

# 计算混淆矩阵
def confusion_matrix(predict, label):
    """
    - predict : 预测值one-hot
    - label : 真实值one-hot
    """
    num, classes_num = label.shape
    con_matrix = np.zeros((classes_num, classes_num), dtype=np.int32)
    for i in range(num):
        con_matrix[np.argmax(label[i]), np.argmax(predict[i])] += 1
    return con_matrix

# 绘制混淆矩阵
def plot_confusion_matrix(con_matrix, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Reds):
    """
    - con_matrix : 计算出的混淆矩阵的值
    - classes : 混淆矩阵中每一行每一列对应的列
    - normalize : True:显示百分比, False:显示个数
    """
    if normalize:
        con_matrix = con_matrix.astype('float') / con_matrix.sum(axis=1)[:, np.newaxis]
        np.set_printoptions(formatter={'float': '{: 0.2f}'.format})
    
    plt.imshow(con_matrix, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    # plt.xticks(tick_marks, classes, rotation=45)
    plt.xticks(tick_marks, classes)
    plt.yticks(tick_marks, classes)
    fmt = '.2f' if normalize else 'd'
    thresh = con_matrix.max() / 2.
    for i, j in itertools.product(range(con_matrix.shape[0]), range(con_matrix.shape[1])):
        plt.text(j, i, con_matrix[i, j],
                 horizontalalignment="center",
                 color="white" if con_matrix[i, j] > thresh else "black")
    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
    plt.show()
    
confusion_matrix = confusion_matrix(predict, label)
plot_confusion_matrix(confusion_matrix, classes)

查准率P与查全率R分别定义为:
P = T P T P + F P R = T P T P + F N P = \frac{TP}{TP+FP}\\R = \frac{TP}{TP+FN} P=TP+FPTPR=TP+FNTP
从定义中可以看出,查准率是预测正例中预测结果正确的样本所占的比例,查全率是真实正例中预测正确的样本所占的比例。分别对应于“挑出来的西瓜中有多少是好的”和“所有的好瓜中有多少比例被挑了出来”。

一般来说,查准率较高时,查全率往往偏低;而查全率较高时,查准率往往偏低。只有在一些简单任务中,才可能使两者都很高。

把测试样本为正例的可能性按照从高到低排序,并按此顺序逐个把样本作为正例进行预测,则可以得到一系列的查准率和查全率。以查准率为纵轴、查全率为横轴进行作图,可以得到一系列的点,将这些点连成线就得到查准率-查全率曲线,简称“P-R”曲线,显示该曲线的图称为“P-R”图。示意图如下:

img

在对多个模型进行性能评估时,如果一个模型的P-R曲线被另一个模型的P-R曲线完全“包住”,则说明后者优于前者,例如上图中的A和C。如果两个模型的P-R曲线发生交叉,例如上图中的A和B,此时较为合理的判断依据是“平衡点”。“平衡点”(Break-Even Point, 简称BEP),是“查准率=查全率”时的取值。上图中,模型A的平衡点大于模型B的平衡点,因此可以认为模型A优于模型B。

然而BEP还是过于简化,更常用的是F1度量:
F 1 = 2 × P × R P + R = 2 × T P 样 本 总 数 + T P − T N F1=\frac{2×P×R}{P+R}=\frac{2×TP}{样本总数+TP-TN} F1=P+R2×P×R=+TPTN2×TP
是基于查准率与查全率的调和平均定义的:
1 F 1 = 1 2 ⋅ ( 1 P + 1 R ) \frac{1}{F1} = \frac{1}{2}·(\frac{1}{P}+\frac{1}{R}) F11=21(P1+R1)
以上性能度量都是基于二分类为例进行分析,对于多分类问题,在对不同的类别进行分析时,分别把该类别作为正例其他类别作为反例进行分析,这样便得可到每个类别的性能度量。如果先计算每个类别的查准率和查全率,再计算平均值(宏平均),可得宏-P、宏-R和宏-F1;如果先平均TP、TN、FP、FN,再计算P、R和F1(微平均),可得微-P、微-R和微F1。

宏平均和微平均的对比如下:

  • 如果每个类别的样本数量相差不大, 那么宏平均和微平均差异也不大

  • 如果每个类别的样本数量相差较大,且:

    • 更注重样本量多的类别,使用宏平均
    • 更注重样本量少的类别,使用微平均
  • 如果微平均远低于宏平均,则要检查样本量多的类别

  • 如果宏平均远低于微平均,则要检查样本量少的类别

代码如下

"""
    P-R曲线
"""
import numpy as np
import matplotlib.pyplot as plt

# 构造数据
test_example_number = 100
predict = np.random.random(test_example_number)
label = np.random.randint(0, 2, test_example_number)

# 计算混淆矩阵
def confusion_matrix(predict, label):
    """
    - predict : 样本为正类的概率
    - label : 样本的真实标签值
    """
    con_matrix = np.zeros((2, 2), dtype=np.int32)
    for i in range(len(label)):
        con_matrix[label[i], predict[i]] += 1
    return con_matrix

# 绘制P-R图
def P_R(predict, label):
    """
    - predict : 样本为正类的概率
    - label : 样本的真实标签值
    """
    index = np.argsort(-predict)
    label = label[index]
    n = len(predict)
    pre, p, r = np.zeros(n, dtype=np.int32), [], []
    for i in range(1, n):
        pre[:i] = 1
        con_mat = confusion_matrix(pre, label)
        p.append(con_mat[1, 1] / (con_mat[1, 1] + con_mat[0, 1]))
        r.append(con_mat[1, 1] / (con_mat[1, 1] + con_mat[1, 0]))
    
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.plot(r, p, c="blue", label=r'P - R', alpha=1, lw=1, zorder=1)
    plt.title("P - R图")
    plt.legend()
    plt.show()

P_R(predict, label)

ROC与AUC

很多模型是为测试样本产生一个实值或概率预测,然后将这个预测结果与一个分类阈值(threshold)进行比较,若大于阈值为正类,小于阈值为反类。这个实值或概率预测结果的好坏,直接决定了模型的泛化能力。根据这个实值或预测概率结果,可按照从大到小的顺序将测试样本进行排序,即“最可能”为正例的样本排在前面,“最不可能”为正例的样本排在后面。这样,分类过程相当于在这个排序中以某个“截断点”(cut point)将样本分类两部分,前一部分预测为正例,后一部分预测为反例。

如果更重视“查准率”,则可选择排序中靠前的位置进行截断;如果更重视“查全率”,则可选择靠后的位置进行截断。

ROC全称是“受试者工作特征(Receiver Operating Characteristic)”曲线,源于“二战”中用于敌机检测的雷达信号分析技术,二十世纪六七十年代开始用于一些心理学、医学检测应用中,此后被引入机器学习领域。根据模型的预测结果对样本进行排序,按此顺序逐个把样本作为正例进行预测,每次计算出“真正例率”和“假正例率”,并以前者为纵轴,后者为横轴,就可得到“ROC曲线”

基于上面的混淆矩阵,“真正例率”(True Positive Rate, 简称TPR)“假正例率”(False Positive Rate, 简称FPR)的定义为:
T P R = T P T P + F N F P R = F P T N + F P TPR = \frac{TP}{TP+FN}\\FPR = \frac{FP}{TN+FP} TPR=TP+FNTPFPR=TN+FPFP
显示ROC曲线的图称为“ROC图”,示意图如下:

img

与P-R图相似,在对多个模型进行性能评估时,若一个模型的ROC曲线被另一个模型的ROC曲线完全“包住”,则说明后者的性能优于前者。若两个模型的ROC曲线发生交叉,较为合理的判断依据是比较ROC曲线下的面积,即AUC(Area Under ROC Curve)。如上图所示。其估算为:
A U C = 1 2 ∑ i = 1 m − 1 ( x i + 1 − x i ) ⋅ ( y i + y i + 1 ) AUC=\frac{1}{2} \sum_{i=1}^{m-1}(x_{i+1}-x_i)·(y_i+y_{i+1}) AUC=21i=1m1(xi+1xi)(yi+yi+1)
形式化地看,AUC考虑的是样本预测的排序质量,因此与排序误差有紧密联系。给定m+个正例和m-个反例,令D+和D-分别表示正、反例集合,则排序“损失”(loss)定义为
l r a n k = 1 m + m − ∑ x + ∈ D + ∑ x − ∈ D − ( Ⅱ ( f ( x + ) < f ( x − ) ) + 1 2 Ⅱ ( f ( m + ) = f ( m − ) ) ) l_{rank}=\frac{1}{m^+m^-} \sum_{x^+∈D^+} \sum_{x^-∈D^-}(Ⅱ(f(x^+)<f(x^-))+\frac{1}{2}Ⅱ(f(m^+)=f(m^-))) lrank=m+m1x+D+xD((f(x+)<f(x))+21(f(m+)=f(m)))
即考虑每一对正、反例,若正例的预测值小于反例,则记一个“罚分”,若相等,则记0.5个“罚分”。因此,lrank对应的是ROC曲线上面的面积。因此有
A U C = 1 − l r a n k AUC=1-l_{rank} AUC=1lrank
代码如下

"""
    ROC曲线
"""
import numpy as np
import matplotlib.pyplot as plt

# 构造数据
test_example_number = 100
predict = np.random.random(test_example_number)
label = np.random.randint(0, 2, test_example_number)

# 计算混淆矩阵
def confusion_matrix(predict, label):
    """
    - predict : 样本为正类的概率
    - label : 样本的真实标签值
    """
    con_matrix = np.zeros((2, 2), dtype=np.int32)
    for i in range(len(label)):
        con_matrix[label[i], predict[i]] += 1
    return con_matrix

# 绘制ROC图
def ROC(predict, label):
    """
    - predict : 样本为正类的概率
    - label : 样本的真实标签值
    """
    index = np.argsort(-predict)
    label = label[index]
    n = len(predict)
    pre, tpr, fpr = np.zeros(n, dtype=np.int32), [], []
    for i in range(n):
        pre[:i] = 1
        con_mat = confusion_matrix(pre, label)
        tpr.append(con_mat[1, 1] / (con_mat[1, 1] + con_mat[1, 0]))
        fpr.append(con_mat[0, 1] / (con_mat[0, 0] + con_mat[0, 1]))
    
    auc = 0
    for i in range(n-1):
        auc += 0.5 * (fpr[i+1] - fpr[i]) * (tpr[i] + tpr[i+1])
    
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    plt.plot(fpr, tpr, c="blue", label=r'ROC图', alpha=1, lw=1, zorder=1)
    plt.title("ROC图")
    plt.legend()
    plt.show()
    
    return auc
    
ROC(predict, label)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值