前面章节说道,改变模型中用于做出分类决策的阈值,是一种调节给定分类器的准确率和召回率之间的这种方法。我们可能希望仅遗漏不到10%的正类样本(即希望召回率能达到90%,该点也可以称作设置工作点),这就是我们设定的目标,这是我们就为了实现这个目标调整相应的阈值,使其满足要求,同时也要求准确率尽可能的高,这样难点就在于如何创建一个符合预期的模型。
在新开发模型时,我们并不知道工作点是在哪里,因此,为了更好地理解建模问题,很有启发性的做法是——同时查看所有可能的阈值或准确率和召回率的所有可能,也就是利用准确率-召回率曲线(precious recall curve)的工具来做到这一点。可以通过该曲线找到计算准确率-召回率曲线函数,最终达到我们的目的,对应代码如下:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.svm import SVC
import mglearn
from sklearn.metrics import precision_recall_curve
from sklearn.metrics import confusion_matrix
from sklearn.metrics import f1_score
from sklearn.metrics import classification_report
from mglearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
#precision_recall_curve函数返回一个列表,包含顺序排序的所有可能阈值(在决策树中出现的所有值)对应
#的准确率和召回率,这样我们就可以绘制对应的曲线
x,y=make_blobs(n_samples=(400,50), centers=2, cluster_std=(7,2), random_state=0)
x_train, x_test, y_train, y_test=train_test_split(x, y, random_state=0)
svc=SVC(gamma=0.5).fit(x_train, y_train)
precision, recall, thresholds = precision_recall_curve(y_test, svc.decision_function(x_test))
#找到最接近于0的阈值
close_zero = np.argmin(np.abs(thresholds))
plt.plot(precision[close_zero], recall[close_zero], 'o', markersize=10, label="threshold zero",
fillstyle="none", c='k', mew=2)
plt.plot(precision, recall, label="precious recall curve")
plt.ylabel("Recall")
plt.xlabel("Precision")
plt.legend(loc="best")
运行后结果如下:
由上述运行结果可以看出,曲线上的每一个点都对应decision_function的一个可能阈值,在准确率约为0.7对应的召回率约为0.45,这个点刚好是黑圈所在的点,也就是decision_function默认的阈值。该点即是调用predict方法时所用的折中点。
曲线越靠近右上角,则分类器越好,右上角的点表示对于同一阈值,准确率和召回率都很高。从左上角开始,这里对应于非常低的阈值,将所有样本都划分为正类,提高阈值可以让曲线向准确率更高的方向移动,但是召回率会更低。如果随着准确率的提高,模型也能够保持较高的召回率,则模型越好。
不同的分类器在曲线上不同的位置(即不同的工作点)表现很好,下面我们来比较在同一数据集上的SVM、随机森林的准确率-召回率曲线(RandomForestClassifier没有decision_function,只有predict_proba);precision_recall_curve函数的第二个参数应该是正类(类别1)的确定性度量,所以我们传入样本属于类别1的概率(即rf.predict_proba(x_test)[:,1])。
二分类问题的predict_proba的默认阈值是0.5,对应的比较代码如下:
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, random_state=0, max_features=2)
rf.fit(x_train, y_train)
#RandomForestClassifier有predic_proba,但是没有decision_function
precision_rf, recall_rf, threshold_rf = precision_recall_curve(y_test,
rf.predict_proba(x_test)[:,1])
plt.plot(precision, recall, label="svc")
plt.plot(precision[close_zero], recall[close_zero], 'o', markersize=10, label="threshold zero",
fillstyle="none", c='k', mew=2)
plt.plot(precision_rf, recall_rf, label="RandomForestClassifier")
close_default_rf = np.argmin(np.abs(threshold_rf - 0.5))
plt.plot(precision_rf[close_default_rf], recall_rf[close_default_rf], 'o', markersize=10, label="threshold 0.5 rf",
fillstyle="none", c='k', mew=2)
plt.ylabel("Recall")
plt.xlabel("Precision")
plt.legend(loc="best")
运行后其结果如下:
从运行结果可以看出,随机森林的极值比SVM表现更好(同时具备更高的准确率和召回率),不过其他店则各具优势(如果我们只看f-分数来比较二者的总体性能,很可能会遗漏这些细节)。下面我们将两者默认阈值对应的那个点分值打印出来:
print("F1 score of random forest: {:.3f}".format(f1_score(y_test, rf.predict(x_test))))
print("F1 score of SVC: {:.3f}".format(f1_score(y_test, svc.predict(x_test))))
运行后其打印结果为:
F1 score of random forest: 0.593
F1 score of SVC: 0.552
平均正确率(average precision):比较准确率-召回率曲线,可以为我们提供详细的情况,但是这对于自动化模型而言,我们更需要的是总结曲线中包含的信息,而不是特定的阈值或工作点,所以引入了平均准确率的概念。