机器学习模型常用性能指标和Python代码实现

问题描述

上半年开始做一个需要机器学习算法模型的项目,但是之前并没有研发过这类模型。起初的时候遇到了一件略显尴尬的事情:我基于sklearn包快速得到了一个机器学习模型和性能指标,领导想知道我给的性能指标具体是哪个指标,我回复并不清楚。领导无奈地说,你可以先把各种模型的性能指标都弄清楚,这样咱们才能站在同一维度讨论问题。我深感愧疚,所以就有了这篇文章的源动力。

最近又联想到了组内一些小的模型开发需求,非机器学习方向,勉强算运筹优化方向吧。这些模型完成后,也需要一些指标去衡量模型的能力,比如准确性或者最优性,只有在这些量化指标达到算法、产品和业务的预期后,才能交付使用。然后逐渐意识到,作为一名算法工程师,如何为自己负责的算法模型,定义一些合乎业务逻辑的性能指标,是尤为重要的。虽然不同模型的性能指标的含义应该需要根据业务的实际需求做个性化调整,但是如果能理解清楚机器学习中的性能指标簇,对未来其他模型性能指标的设计,应该是极具意义的。所以就有了这篇文章的直接动力。

在机器学习领域,可以基本分为分类问题和回归问题,不同的问题类型有各自常用的性能指标。以下指标不算全面,但是是个人认为其中最常用的几个。

性能指标

分类问题

分类问题的性能指标最常用的指标包括:准确率(Accuracy)、精准率(Precision)、召回率(Recall)和F1-Score(F1)。这些指标的含义可以通过混淆矩阵来理解
在这里插入图片描述
其中,T (True) 和 F(False) 评价模型的预测结果是否正确;P (Positive) 和 N(Negative) 代表模型的预测数值为1或者0。即无论是T和F,还是P和N,评价的都是模型本身。

准确率指的是,预测结果为T的数量/所有数量: A c c u r a c y = T P + T N T P + F N + T N + F P Accuracy= \frac{TP+TN}{TP+FN+TN+FP} Accuracy=TP+FN+TN+FPTP+TN
精准率指的是,预测结果为T预测数值也为1的数量/预测数值为1的数量: P r e c i s i o n = T P T P + F P Precision= \frac{TP}{TP+FP} Precision=TP+FPTP
召回率指的是,预测结果为T预测数值也为1的数量/真实值为1的数量: R e c a l l = T P T P + F N Recall= \frac{TP}{TP+FN} Recall=TP+FNTP
F1-Score指的是,精准率和召回率的调和值: F 1 = 2 ∗ P r e c i s i o n ∗ R e c a l l P r e c i s i o n + R e c a l l F1=\frac{2*Precision*Recall}{Precision+Recall} F1=Precision+Recall2PrecisionRecall
显然,以上4个指标的值越大,分类模型的效果越好。

举个例子,有一个分类问题,真实标签值和三个模型的预测值分别如下表所示,基于此,可以得到各模型的预测结果和预测数值,也展示在了表中。
在这里插入图片描述
因此三个模型的性能指标分别为
在这里插入图片描述
从预测结果来看,模型一过于激进,所有预测值均为1;模型二偏向保守,只有一个预测值为1;模型三则相对平衡。直观感觉告诉我们,模型三的综合性能更好,从指标上看,使用F1-Score更能支持我们选择模型三。

事实上,准确率是最直观的指标,但其问题在于:如果标签真实值的分布差异大,对于以上实例,大部分真实值都是0,那么偏保守的模型二就会有很高的准确率;如果大部分真实值都是1,那么可以想象激进的模型一会有更高的准确率指标。精准率又称查准率,衡量的是被预测为1的样本中有多少是真正的1,在模型落地试点时,一个高的精准率指标有助于增强业务对模型的认可。召回率又称查全率,衡量的是所有为1的样本中有多少是被正确分类,对于那些更关注1的问题来说,比如判断病人是否患有癌症,召回率指标就非常重要(可以先初步诊断,然后复查确认)。而F1-Score从定义上就可以看出,是精准率和召回率的调和,是模型优化时,经常选用的一个指标。

回归问题

回归问题中最常用的指标包括:均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)、平均绝对百分比误差(MAPE)和可决系数(r2)。这些指标的含义可以直接通过公式来理解:
MSE = 1 n ∑ i = 1 n ( y i − y ~ i ) 2 \text{MSE}=\frac{1}{n}\sum_{i=1}^{n}{{(y_i-\tilde{y}_i)}^2} MSE=n1i=1n(yiy~i)2 MAE = 1 n ∑ i = 1 n ∣ y i − y ~ i ∣ \text{MAE}=\frac{1}{n}\sum_{i=1}^{n}{\left| y_i-\tilde{y}_i\right|} MAE=n1i=1nyiy~i
MAPE = 1 n ∑ i = 1 n ∣ y i − y ~ i ∣ ∣ y i ∣ \text{MAPE}=\frac{1}{n}\sum_{i=1}^{n}{\frac{\left| y_i-\tilde{y}_i\right|}{\left| y_i \right|}} MAPE=n1i=1nyiyiy~i r 2 = 1 − ∑ i = 1 n ( y i − y ~ i ) 2 ∑ i = 1 n ( y i − y ˉ ) 2 r2=1-\frac{\sum_{i=1}^{n}{{(y_i-\tilde{y}_i)}^2}}{\sum_{i=1}^{n}{{(y_i-\bar{y})}^2}} r2=1i=1n(yiyˉ)2i=1n(yiy~i)2$
其中, n n n 为样本个数, y i y_i yi y ~ i \tilde{y}_i y~i分别为第 i i i个样本的真值和模型预测值, y ˉ \bar{y} yˉ表示所有样本真值的平均值。

MSE、MAE、MAPE表征的均为预测值和真值之间的误差,所以越小越好,但是很容易异常值的影响,所以可以为模型的改进做参考,但不建议直接用于评估模型的优劣; r 2 r2 r2的后一项中,分母 ( y i − y ˉ ) 2 {(y_i-\bar{y})}^2 (yiyˉ)2可以理解为用平均值作为预估值的误差, ( y i − y ~ i ) 2 {(y_i-\tilde{y}_i)}^2 (yiy~i)2为用模型值作为预估值的误差,如果该比例项的值为5%,即采用模型后,误差已经降为平均值误差的5%,此时 r 2 r2 r2为95%,说明模型很好;如果该比例项的值为95%,即采用模型后,误差仅降为平均值误差的95%,此时 r 2 r2 r2为5%,说明模型很差。所以r2越大越好,也更适合用来评估回归模型的好坏。

代码实现

分类问题

以下假定真值和预测值均为列表格式,基于Python代码实现了准确率、精准率、召回率和F1-Score的计算。

def classifier_metrics_by_self(y_true, y_pred):

    TP, FP, FN, TN = 0, 0, 0, 0
    for i in range(len(y_true)):
        if y_true[i] == 1 and y_pred[i] == 1:
            TP += 1
        if y_true[i] == 0 and y_pred[i] == 1:
            FP += 1
        if y_true[i] == 1 and y_pred[i] == 0:
            FN += 1
        if y_true[i] == 0 and y_pred[i] == 0:
            TN += 1

    accuracy = (TP + TN) / (TP + FP + FN + TN)
    precision = TP / (TP + FP)
    recall = TP / (TP + FN)
    F1 = 2 * precision * recall / (precision + recall)

    print('accuracy_score: {:.2f}'.format(accuracy))
    print('precision_score: {:.2f}'.format(precision))
    print('recall_score: {:.2f}'.format(recall))
    print('f1_score: {:.2f}'.format(F1))

在sklearn包中,这4种指标的计算方式如下

from sklearn.metrics import f1_score, precision_score, recall_score, accuracy_score


def classifier_metrics_by_sklearn(y_true, y_pred):
    print('accuracy_score: {:.2f}'.format(accuracy_score(y_true, y_pred)))
    print('precision_score: {:.2f}'.format(precision_score(y_true, y_pred)))
    print('recall_score: {:.2f}'.format(recall_score(y_true, y_pred)))
    print('f1_score: {:.2f}'.format(f1_score(y_true, y_pred)))

回归问题

以下假定真值和预测值均为N*1的ndarray数据格式,MSE、MAE、MAPE和r2的代码实现如下

import numpy as np


def regression_metrics_by_self(y_true, y_pred):

    n = len(y_true)
    y_mean = y_true.mean()
    MSE = sum((y_pred - y_true) ** 2) / n
    MAE = sum(abs(y_pred - y_true)) / n
    MAPE = sum(abs(y_pred - y_true) / np.maximum(abs(y_true), np.finfo(np.float64).eps)) / n
    r2 = 1 - sum((y_pred - y_true) ** 2) / sum((y_true - y_mean) ** 2)
    
    print('========calculate regression metrics by self========')
    print('MSE: {:.4f}'.format(float(MSE)))
    print('MAE: {:.4f}'.format(float(MAE)))
    print('MAPE: {:.4f}'.format(float(MAPE)))
    print('r2: {:.4f}'.format(float(r2)))

sklearn包中,计算这4种指标的方式为

from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error, r2_score


def regression_metrics_by_sklearn(y_true, y_pred):
    print('========calculate regression metrics by sklearn========')
    print('MSE: {:.2f}'.format(mean_squared_error(y_true, y_pred)))
    print('MAE: {:.2f}'.format(mean_absolute_error(y_true, y_pred)))
    print('MAPE: {:.2f}'.format(mean_absolute_percentage_error(y_true, y_pred)))
    print('r2: {:.2f}'.format(r2_score(y_true, y_pred)))

代码测试

分类问题

使用第二节“性能指标-分类问题”中的实例,对比手动计算、自编Python程序和sklearn的计算结果。

if __name__ == '__main__':
    y_true = [1, 1, 0, 1, 0, 0, 0, 0, 0, 0]
    y_pred_1 = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    y_pred_2 = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
    y_pred_3 = [0, 1, 0, 1, 0, 0, 0, 0, 0, 1]
    print('======================sklearn======================')
    classifier_metrics_by_sklearn(y_true, y_pred_1)
    print('-------------------')
    classifier_metrics_by_sklearn(y_true, y_pred_2)
    print('-------------------')
    classifier_metrics_by_sklearn(y_true, y_pred_3)

    print('======================self======================')
    classifier_metrics_by_self(y_true, y_pred_1)
    print('-------------------')
    classifier_metrics_by_self(y_true, y_pred_2)
    print('-------------------')
    classifier_metrics_by_self(y_true, y_pred_3)

运行之后,可以发现,三组计算结果完全相同(手动计算方式的结果,可以参见之前的表)。
在这里插入图片描述
在这里插入图片描述

回归问题

数据集真值为正弦函数,模型预估值为在真值增加了小的随机值。代码实现和可视化图如下

import math
import numpy as np


def sin():
    t = np.linspace(0, math.pi, 20)

    np.random.seed(10)
    rm = np.random.uniform(-1, 1, (len(t), 1)) / 10

    y_true = []
    y_pred = []
    for i in range(len(t)):
        y_true.append(math.sin(t[i]))
        y_pred.append(math.sin(t[i]) + rm[i, 0])

    y_true = np.array(y_true).reshape((len(y_true), 1))
    y_pred = np.array(y_pred).reshape((len(y_pred), 1))

    return t, y_true, y_pred

在这里插入图片描述
对比sklearn的计算结果和自编函数的计算结果,可以发现,两者也是完全相同的。

if __name__ == '__main__':
    t, y_true, y_pred = sin()
    regression_metrics_by_sklearn(y_true, y_pred)
    regression_metrics_by_self(y_true, y_pred)

在这里插入图片描述

  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值