• 分类问题(三)混淆矩阵,Precision与Recall


    混淆矩阵

    衡量一个分类器性能的更好的办法是混淆矩阵。它基于的思想是:计算类别A被分类为类别B的次数。例如在查看分类器将图片5分类成图片3时,我们会看混淆矩阵的第5行以及第3列。

    为了计算一个混淆矩阵,我们首先需要有一组预测值,之后再可以将它们与标注值(label)进行对比。我们也可以在测试集上做预测,但是最好是先不要动测试集(测试集仅需要在最后的阶段使用,在我们有了一个准备上线的分类器后,最后再用测试集测试性能)。接下来,我们可以使用cross_val_predict() 方法:

    from sklearn.model_selection import cross_val_predict
    
    y_train_pred
    = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3) y_train_pred.shape >(60000,)

    与cross_val_score() 方法一样,cross_val_predict() 会执行K-折交叉验证,但是不会返回评估分数,而是返回在每个测试折上的预测值,加起来就是整个训练数据集的预测值。现在我们可以使用confusion_matricx() 方法获取混淆矩阵。直接传入label数据(y_train_5)以及预测数据(y_train_pred)即可:

    from sklearn.metrics import confusion_matrix
    
    confusion_matrix(y_train_5, y_train_pred) >array([[
    53892, 687], [ 1891, 3530]])

    在这个混淆矩阵中,每一行代表一个真实类别,每一列代表一个预测类别。第一行代表的是“非5”(亦称为negative class)图片:53892张图片被分类为“非5“类别(它们亦称为true negatives)。剩下的687 张图片被错误的分类为”非5“(亦称为false positives)。第二行代表的是”真5“(亦称为 positive class):1891张图片被错误地分类为”非5“类别(亦称为false negatives),剩下的3530 张图片被正确地分类为”真5“(亦称为true positives)。一个完美的分类器应该仅包含true positives 以及true negatives,所以它的混淆矩阵应该仅有主对角线上有非0数值,其他值应都为0。例如,假设我们有了以下一个完美的预测:

    y_train_perfect_predictions = y_train_5
    confusion_matrix(y_train_5, y_train_perfect_predictions)
    >array([[54579,     0],
           [    0,  5421]])
    

    混淆矩阵可以给我们提供很多信息,但是有时候我们可能需要一个更精准的指标。一个比较好的方式是:查看positive predictions的精准度。它也称为分类器的精度(precision),它的公式为:

    Precision

    Precision=TP / (TP + FP)

    这里TP 是true positives 的数量,FP 是false positive 的数量。

    对于精度,我们仍有办法去构造一个完整精度。比如假设测试集里全部是数字5,然后模型的逻辑是仅输出True。这样就可以构造一个 100% 精度的模型。所以精度(precision)一般与另一个指标一起用,这另一个指标称为回调(recall),也称为sensitivity或true positive rate(TPR):它是分类器正确分类positive 条目的比率,公式为:

    Recall

    TP / (TP+FN)

    这里FN是false negatives的数目。

    如果对混淆矩阵的这些概念比较模糊的话,可以看看下图:

     

    PrecisionRecall

    Sk-learn提供了一些方法用于计算分类器的各个指标,包括精准率(precision)与回调率(recall):

    from sklearn.metrics import precision_score, recall_score
    
    print(precision_score(y_train_5, y_train_pred))  #3530/(3530+687)
    print(recall_score(y_train_5, y_train_pred))  #3530/(3530+1891)
    >0.8370879772350012
     0.6511713705958311

    从precision与recall来看,这个分类器的表现并不像之前准确度(accuracy)那样亮眼了。当这个分类器认为某张图片是数字5时,它仅有83.7% 的概率是正确的。并且它仅识别出了65.1%的数字5图片。

    一般我们还会将precision和recall结合成一个指标:F1分数。特别是在需要使用一个简单的办法对比两个分类器时。F1分数是precision与recall的调和平均数(harmonic mean):

    如果一个分类器的recall与precision分数都比较高的话,则最终才会得到一个较高的F1分数。其中任意一个recall或是precision比较低的话,F1分数都不会太高。

    在sk-learn中,直接调用f1_score() 方法即可计算F1分数:

    from sklearn.metrics import f1_score
    
    f1_score(y_train_5, y_train_pred)
    >0.7325171197343846

    F1分数会比较倾向于那些precision值与recall值接近的分类器。不过这个需求并不是在任何场景下都是必须的,在一些场景下,我们可能更关心精准率,而在另一些场景下更关注回调率。例如,如果我们训练一个分类器,用于为孩子们检测一些健康的视频。在这个场景下,我们可能会更倾向于使用:一个可以更精准的判断视频是否为健康的视频(高精准),但是可能会误杀掉一些健康的视频(低回调)的分类器。而不是一个有着高回调,但是会让小部分不健康的视频通过的分类器。另一方面,假设我们训练一个判断监控里小偷的分类器,即使这个分类器只有30%的精准率(precision)也是可以的,只要是它有99%左右的回调率(recall),依然可以达到我们的需求(即使警报可能会响很多次,但是基本都会抓到小偷)。

    不过可惜的是,precision与recall无法二者兼得:增加precision会降低recall,反之亦然。这个被称为精准/回调折中(precision/recall tradeoff)。

    精准/回调折中(Precision/Recall Tradeoff

    为了理解这种折中,我们看一下SGDClassifier是如何做分类决策的。对每条数据,它首先根据决策方法,计算出一个分数,如果此分数大于某个阈值,则将这条数据分类为正类(positive class),反之则分类为负类(negative class)。

    下图是一个例子,低分在左边,被分为负类,高分在右边,被分为正类。假设决策阈值的位置在正中间(下图中间的两个5之间):我们可以看到阈值右边有4个true positives(真正为数字5),以及1个false positive (真正为数字6)。所以,在这个阈值下,精准率precision是80%(4/5)。这个集合中一共有6个数字5,但是在这个阈值下,只检测出了4个,所以召回率recall是67%(4/6)。

    现在假设我们升高这个阈值(将箭头向右移),则之前的那个数字6从false positive 变成了true negative,所以此时false positive 现在是0,precision是100%(3/3)。而之前的数字5 由true positive 变成了false negative,所以召回率recall现在是50%(3/6)。同样,减少阈值后,召回率会上升,但是精准率precision会下降。

     

    Sk-learn并不允许用户直接设置阈值,但是可以指定一个决策分数,用于做预测。之前我们是调用predict() 方法做预测,现在我们可以先使用 decision_function() 方法,它会返回每条数据的分数。然后我们可以根据这些分数,提供的阈值进行预测:

    y_scores = sgd_clf.decision_function([X_test[0], X_test[1], X_test[2], X_train[0]])
    y_scores
    >array([-8542.1753957 , -4410.49112461, -3416.59592945,  2164.22030239])
    
    threshold = 0
    y_demo_digit_pred = (y_scores > threshold)
    y_demo_digit_pred
    >array([False, False, False,  True])

    SGDClassifier 使用的是0作为阈值,所以上面的方法返回的结果与直接调用 predict() 的结果是一致的。下面我们可以试着改一下这个阈值:

    threshold = 8000
    y_demo_digit_pred = (y_scores > threshold) y_demo_digit_pred >array([False, False, False, False])

    这个也证明了:提高阈值后,recall会下降。最后这张图片本来是数字5,并且在阈值为0的情况下,分类器可以将它正确识别。但是在阈值升高到8000后,此图片便被识别为“非数字5”。

    现在,我们如何决定使用哪个阈值呢?首先我们需要获取训练数据中所有数据的得分。再次使用cross_val_predict() 方法即可,但是这次我们要指定它返回决策分数,而不是做预测:

    y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method='decision_function')
    y_scores
    >array([  1200.93051237, -26883.79202424, -33072.03475406, ...,
    
            13272.12718981,  -7258.47203373, -16877.50840447])

    现在有了这些分数,我们可以计算在各种可能的阈值下,precision与recall的值,使用precision_recall_curve()方法:

    from sklearn.metrics import precision_recall_curve
    precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

    最后我们可以画出precision与recall的函数图,以threshold为因变量,使用matplotlib:

    def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
        plt.plot(thresholds, precisions[:-1], 'b--', label="Precision")
        plt.plot(thresholds, recalls[:-1], 'g--', label="Recall")
    
    plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
    plt.show()

    大家可能会好奇,为什么precision的图相较于recall的图抖动的更剧烈。这是因为:在阈值上升后,precision可能偶尔会下降(虽然一般它会上升)。为了便于理解,大家可以回看一下之前“精准/回调折中”里的那张图。假设我们的阈值设在最中间,然后下一个阈值只向右移动了一个单位,则precision从80%(4/5)下降到了75%(3/4)。而另一边,recall仅会在阈值增加的时候才会下降,所以它的曲线看起来更平滑。

    另一个比较好的选择“精准/回调折中”的办法是直接画出precision对应于recall的图,如:

     

    可以看到在大约recall在80% 左右的样子,precision开始急速下降。所以我们一般会选择一个它下降前的一个“精准/回调折中“(precision/recall tradeoff),例如在60%的回调左右。不过这个最终的决定取决于我们的项目需求。

    下面假设我们定的目标是90%的precision。从第一副图我们可以得知,阈值大约为8000 左右。为了更精确地获取这个阈值,我们可以搜索满足90%精准率的最小阈值(np.argmax() 可以返回第一个最大值,在这个例子中就是第一个True值):

    threshold_90_precision = thresholds[np.argmax(precisions >=0.90)]
    threshold_90_precision
    >3370

    下一步,做决策。这次我们不再用predict() 方法,而是使用:

    y_train_pred_90 = (y_scores >= threshold_90_precision)
    y_train_pred_90
    >array([False, False, False, ...,  True, False, False])

    然后我们检查一下这些预测的precision与recall:

    precision_score(y_train_5, y_train_pred_90)
    >0.9000345901072293
    
    recall_score(y_train_5, y_train_pred_90)
    >0.4799852425751706

    现在我们就有了一个90%精准率的分类器!正如你所见,创建一个高精准率的分类器其实很简单,只需要设置更高的阈值即可。但是,如果一个分类器即使precision很高,而recall很低的话,这个分类器基本没太大用处。所以如果有人说,他的分类器达到了99%的精准率,那我们可以继续问问他“recall是多少?”

    接下来我们还会继续介绍另一种性能衡量的办法: ROC曲线。它是另一种与Precision/Recall 曲线类似的曲线。

  • 相关阅读:
    关于python做人工智能的一个网页(很牛逼)
    python操作Excel模块openpyxl
    linux环境下安装mysql
    爬虫框架scrapy
    爬虫
    2017.2.6 开工大吉
    C# List集合Group by查询
    Datatable 列查询,统计值
    调用WCF Client客户端测试
    Oracle数据库导入导出命令
  • 原文地址:https://www.cnblogs.com/zackstang/p/12325718.html
Copyright © 2020-2023  润新知