20201107 -
前端时间重新阅读了《Hands-On Machine Learning with Scikit-Learn and TensorFlow》的第三章分类过程,这里将重点内容记录一下。
与回归过程相比,分类过程重点将某条记录处理后输出一个类别,而常用的一些算法,例如SVM,逻辑回归等,都是为二分类而生,那么就需要通过一定的手段转化来实现多类别分类;同时,相对于回归过程,分类过程所使用的性能指标也有所不同,本篇文章将以此展开。
性能评估是机器学习过程中的重点过程,只有评估了算法的性能,才能知道这个算法或者模型能不能进行实际应用。在回归过程中,通常使用Mean Square Error(MSE)这种方式来评判误差。而在分类过程中,选取的是另一种指标。这里主要介绍二分类场景下的性能指标,但是多分类情况下类似,但是可能不适用。
这部分内容提供了一种给出训练数据集性能的方法,在以往我使用的方法中,都是直接训练了这部分数据,然后就直接全部在这个数据集上进行测试,输出一个相应的数值,得到结果,也就是训练集的测试效果。一般来说,这样整体上得到的效果,都是比较好的。甚至于都能达到100的效果,而测试机效果却特别差,这就是过拟合的现象。
书中这里并不是这样,对于训练集,他也采用了三折交叉的方式,每次测试其中一个部分,用剩下的部分来进行训练;这样通过两部分训练得到的模型,来测试剩下的这部分数据,通过三次交叉,就得到了全部的训练集样本的全部分类效果。
具体代码如下:
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
当然,书中这部分是为了介绍这部分的性能有所缺陷;我个人认为平时的时候应该不该这样来测试整个训练集的效果吧?!当然调整模型的参数的时候需要设置验证集,这个时候来收集模型的测试效果是可以的。而且,这种方式虽然得到了所有的测试效果,但是这是不同的数据所学习到的模型,自然是不一样的。个人理解,这里就是为了说明一种方式。
他所声明的意思,就是通过准确率的指标并不能准确反映这个分类器的效果,原因就是这个数据集是不均衡的。
混淆矩阵是指查看某个类别被误分类为其他类别的数目,或者比例。而这里,书中同样使用了前面交叉验证的方法,来获取训练集所有的样本预测结果。
from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
同时书中也说了, 这样的方式能够获取一个比较干净的结果,“干净”就是指通过这种方式得到的预测结果,每个模型在预测过程中都没有看到过这个数据。
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)
在混淆矩阵中,每行代表着真正的类别,而每列代表着预测的类别,通过这种方式,能够得到相应的真阳性和假阳性等内容。
首先来看看最基础的阳性这些指标。 TP(True Positives) \text {TP(True Positives)} TP(True Positives)是指真阳性,也就是被正确分类的正例样本; FP (False Positives) \text {FP (False Positives)} FP (False Positives)是指被误分类的正例样本; TN (True Negatives) \text {TN (True Negatives)} TN (True Negatives)是指被正确分类的负例样本, FN (True Negatives) \text {FN (True Negatives)} FN (True Negatives)是指被误分类的负例样本。然后先看精确度,也被查准率:
Precision = T P T P + F P \text {Precision}=\frac{TP}{TP+FP} Precision=TP+FPTP
精确度是指被预测为正例样本的数量中,被分类正确的正例样本的比例。召回率,又被称为查全率:
Recall = T P T P + F N \text {Recall} = \frac{TP}{TP+FN} Recall=TP+FNTP
而召回率是指真实样本是正例样中,被分类正确的正例样本的比例。
那么总体而言,就是说这两个指标都是针对正例样本被分类正确的时候,他们所占不同的总体的比例,利用书中的一个图来展示。
另外一种比较方便的方式,来统计精确率和召回率的方式就是 F 1 − S c o r e F1-Score F1−Score, F 1 F1 F1分数是精确率和召回率的谐波均值,计算方式如下:
F 1 = 2 1 precision + 1 recall = 2 × precision × r e c a l l precision + r e c a l l = T P T P + F N + F P 2 F_1 = \frac{2}{\frac{1}{\text {precision}}+\frac{1}{\text {recall}}}=2\times\frac{\text{precision}\times{recall}}{\text{precision}+{recall}}=\frac{TP}{TP+\frac{FN+FP}{2}} F1=precision1+recall12=2×precision+recallprecision×recall=TP+2FN+FPTP
当精确率和召回率都高得时候,那么 F 1 F1 F1分数也会很高。
下面是三种指标的计算方式:
from sklearn.metrics import precision_score, recall_score,f1_score
precision_score(y_train_5, y_pred)
recall_score(y_train_5, y_train_pred)
f1_score(y_train_5, y_pred)
F 1 F1 F1分数比较倾向于精确率和召回率类似的分类器,这句话不是很理解。
不过,我个人觉得,有时候不同的场景下,应该对不同的指标进行调优;有些场景需要高召回率。书中也说了,** 一般提高精确率会降低召回率,反之亦然**。一开始的时候,我举得这个说法不对,那种分类分类边界比较清晰的,选择好的阈值,是能偶完全分类正确的,那就是都是100%。当然我这是比较理想化的场景,书中应该是面向问题来说明的。那么下面就来说一说,精确率和召回率之间的调和。
在说明这部分内容的时候,是通过阈值的方式来说明,下面先来看图。
上图是一个分类数字5的性能指标,这个图的意义在于,他的分类边界并不是很清晰。也就是说,也就是说这种分类方法的分类边界处有些数据混杂在一起。从同种可以看出,通过分类器的决策函数,如果决策函数能够输出一个概率值,那么选择不同的阈值来进行分类的时候,就会造成精确率和召回率的不同。在某些分类器的实现中,可以通过调用decision_function
得到相应的决策值,然后自己来控制这个决策阈值。大致代码如下:
y_scores = sgd_clf.decision_function([some_digit])
threshold = 0
y_some_digit_pred = (y_scores > threshold)
array([ True], dtype=bool)
在书中的解释里,通过提高决策阈值,会降低召回率;那么如何选择比较好的决策阈值呢,依然可以通过交叉验证的方式,得到所有数据样本的决策值,然后来选择相应的决策值,具体的代码如下:
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")
而通过精确率和召回率的曲线函数,能够得到不同的一直情况下的性能。
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
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")
plt.xlabel("Threshold")
plt.legend(loc="upper left")
plt.ylim([0, 1])
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()
对这部分数据进行绘图,可以得到一下的图像。
从图中就可以选择合适的阈值;而另外一种方式就是绘制精确率和召回率两者的图像。
针对二分类场景下,ROC曲线是另外一种指标方式;与前面精确率和召回率的曲线不同,该方式直接绘制真阳性和假阳性的比例图。FPR(假阳性比例)是指负例样本被误分类为正例样本的比例,正好是 1 − TNR 1-\text{TNR} 1−TNR,也就是1减去真负例比例。为了绘制ROC曲线,需要fpr和tpr。
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)
def plot_roc_curve(fpr, tpr, label=None):
plt.plot(fpr, tpr, linewidth=2, label=label)
plt.plot([0, 1], [0, 1], 'k--')
plt.axis([0, 1, 0, 1])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plot_roc_curve(fpr, tpr)
plt.show()
当召回率(TRP)比较高的时候,那么假阳性也会增高,图中的虚线是指一个完全随机的二分类分类器的曲线,一个比较好的分类器,应该让ROC曲线远离这个虚线。而通过AUC(area under the curve)可以比较性能。最好的分类器是AUC为1。计算AUC的代码如下:
from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5, y_scores)
可以根据不同的场景来选取不同的曲线来调优。
Since the ROC curve is so similar to the precision/recall (or PR) curve, you may wonder how to decide which one to use. As a rule of thumb, you should prefer the PR curve whenever the positive class is rare or when you care more about the false positives than the false negatives, and the ROC curve otherwise. For example, looking at the previous ROC curve (and the ROC AUC score), you may think that the classifier is really good. But this is mostly because there are few positives (5s) compared to the negatives (non-5s). In contrast, the PR curve makes it clear that the classifier has room for improvement (the curve could be closer to the top- right corner).
注:有些分类函数可能不带有决策函数,例如随机森林,但是可以通过预测概率得到相应的结果。
(注:20211206增加)
在进行异常检测的时候,使用了Sklearn中的隔离森林这种方式以及一类SVM,采用score_samples或者decision_function
输出相应的异常概率的时候,发现auc数值居然还低于0.5,查了一些网上的代码之后[2],发现使用这个函数时,要在前面加上符号。
scoring = -model.decision_function(X_test) # the lower, the more abnormal
这里是和平时不一样的地方。
本节主要介绍了针对二分类的性能评估指标,重点应该理解精确率和召回率在不同阈值下的变化情况,同时学会使用决策函数或者预测概率来自己控制预测过程。
一些算法本身就是为了二分类而生的,也就是说它自身是不支持多分类的,这其实跟我们平时使用过程有些不一样,实际上是因为像sklearn这种库帮我们做了相应的多分类操作。不过也有些分类器是支持多分类的,例如决策树、朴素贝叶斯这种,但SVM及逻辑回归分类器是不支持的,需要一定的手段。
通过训练多个二分类的分类器,可以实现多分类,具体来说两种策略,一种是one-versus-all(OvA)
,另一种是one-versus-one(OvO)
。下面假设我有10个类别,来具体介绍。
通过训练10个分类器,每个分类器针对每个类别进行训练,其中正例样本就是所针对的类被,而负例样本就是剩下的所有样本,这也是为什么这种方法还被称为one-versus-the-rest
的原因。这种方法就得到了10个分类器。对于需要检测的样本,选取决策分数最高输出其类别。
但是这种方式是不是会收到不均衡样本的影响呢?
另一种方案就是每两个类别训练一个分类器,那么如果是10个类别,就需要训练45个( N × ( N − 1 ) / 2 ) N\times(N-1)/2) N×(N−1)/2)。在测试中,也是找到效果最优的类别。因为这种方法,每次训练的时候都是选取了两个类别的数据,所有单个分类器训练过程中比较快。
对于SVM这种对大数据训练比较慢的分类器,那么使用OvO的方式,这样每个分类器的训练时间就减小了。但对于大多数的二分类分类器来说,还是使用OvA的方式。sklearn会自动检测多分类情况,并且自动选择OvA的方式,当然SVM会选择OvO的方式。在预测过程中,可以通过决策分数来查看相应的预测值,但是需要注意的是,要使用分类器的class_
来进行获取类别,因为可能会乱序。
而如果想强制使用某种方式,可以通过代码实现。
from sklearn.multiclass import OneVsOneClassifier
>>> ovo_clf = OneVsOneClassifier(SGDClassifier(random_state=42))
>>> ovo_clf.fit(X_train, y_train)
>>> ovo_clf.predict([some_digit])
array([ 5.])
>>> len(ovo_clf.estimators_)
45
在书中第三章的最后,还包含了两种不同的手段,就是多标签和多输出分类,这部分内容比较少,不再展开。
本篇文章从性能评估和多分类两个角度讲解了分类过程。但其实这部分内容已经架设了知道分类过程是怎么实现的。我记得,当时学习《统计学习基础》的时候就看到过,逻辑回归的方式就是通过阈值的方式来划分类别。重点还是要关注如何通过阈值来进行调优,当然,最重要的还是超参的调整。
[1]ROC曲线与混淆矩阵的绘制
[2]bench_isolation_forest.py