1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 机器学习实战 基于_基于ScikitLearn Keras和TensorFlow的机器学习实战:分类

机器学习实战 基于_基于ScikitLearn Keras和TensorFlow的机器学习实战:分类

时间:2021-03-04 02:22:35

相关推荐

机器学习实战 基于_基于ScikitLearn Keras和TensorFlow的机器学习实战:分类

本文讲解Aurélien Géron所著的《Hands-On Machine Learning withScikit-Learn, Keras, andTensorFlow》的第一部分(机器学习基础)的第三章《分类》。

该书的总目录如下,分为两部分:第一部分介绍机器学习的基础,第二部分介绍神经网络及深度学习。

第一部分第三章《分类》的内容如下:

在第一章我们提到过最常用的监督学习任务是回归(用于预测某个值)和分类(预测某个类别)。在第二章我们探索了一个回归任务:预测房价。我们使用了多种算法,诸如线性回归、决策树和随机森林。现在我们将我们的注意力转到分类任务上。

1)MNIST数据集

在本章中,我们将会使用MNIST 这个数据集,它有着 70000 张规格较小的手写数字图片,由美国的高中生和美国人口调查局的职员手写而成。这个数据集相当于机器学习当中的“Hello World”,人们无论什么时候提出一个新的分类算法,都想知道该算法在这个数据集上的表现如何。Scikit-Learn 提供了许多辅助函数,以便于下载流行的数据集。MNIST 是其中一个。下面的代码可以获取 MNIST:

>>> from sklearn.datasets import fetch_openml>>> mnist = fetch_openml('mnist_784', version=1)>>> mnist.keys()dict_keys(['data','target','feature_names','DESCR','details','categories','url'])

一般而言,由 sklearn 加载的数据集有着相似的字典结构,这包括:i) DESCR描述数据集ii) data存放一个数组,数组的一行表示一个样本,一列表示一个特征iii)target存放一个标签数组

让我们看一下这些数组:

>>> X, y = mnist["data"], mnist["target"]>>> X.shape(70000, 784)>>> y.shape(70000,)

MNIST 有 70000 张图片,每张图片有 784 个特征。这是因为每个图片都是28*28像素的,并且每个像素的值介于 0~255 之间。让我们看一看数据集的某一个数字。你只需要将某个样本的特征向量,reshape为28*28的数组,然后使用 Matplotlib 的imshow函数展示出来。

import matplotlib as mplimport matplotlib.pyplot as pltsome_digit = X[0]some_digit_image = some_digit.reshape(28, 28)plt.imshow(some_digit_image, cmap="binary")plt.axis("off")plt.show()

你总是应该先创建测试集,并且在进一步探索数据之前先把测试集晾到一边。MNIST 数据集已经事先被分成了一个训练集(前 60000 张图片)和一个测试集(最后 10000 张图片)。

X_train,X_test,y_train,y_test=X[:60000],X[60000:],y[:60000],y[60000:]

打乱训练集,这可以保证交叉验证的每一折都是相似的。而且,一些机器学习算法对训练样本的顺序是比较敏感的,当它们连续得到许多相似的样本时,这些算法将会表现得非常差。打乱数据集将保证这种情况不会发生。

2)Training a BinaryClassifier——训练二分类器

现在我们简化一下问题,只尝试去识别一个数字,比如说,数字 5。这个“数字 5 检测器”就是一个二分类器,能够识别两类别,“是 5”和“非 5”。让我们为这个分类任务创建目标向量:

y_train_5 = (y_train == 5) # True for all 5s, False for all other digitsy_test_5 = (y_test == 5)

现在让我们挑选一个分类器去训练它。用随机梯度下降分类器 SGD是一个不错的开始。使用 Scikit-Learn 的SGDClassifier类。这个分类器有一个好处是能够高效地处理非常大的数据集。部分原因在于SGD一次只处理一条数据,这也使得 SGD 适合在线学习(online learning)。现在让我们创建一个SGDClassifier,并且在整个数据集上训练它。

from sklearn.linear_model import SGDClassifiersgd_clf = SGDClassifier(random_state=42)sgd_clf.fit(X_train, y_train_5)

现在你可以用它来检测数字 5 的图片。

>>> sgd_clf.predict([some_digit])array([ True])

分类器猜测这个数字代表 5(True)。看起来在这个例子当中,它猜对了。现在让我们评估这个模型的性能。

3)PerformanceMeasures——性能指标

评估一个分类器,通常比评估一个回归器更加玄学,所以这章我们将会花大量的篇幅在这个话题上。有许多可用的性能指标,所以准备好学习许多新概念和术语吧。3.1Measuring Accuracy Using Cross-Validation——利用交叉验证来测量精度

评估一个模型的好方法是使用交叉验证,就像第二章所做的那样。

有时候你想要更好地控制交叉验证的过程。这种情况下,你可以实现你自己版本的交叉验证。事实上它相当简单。以下代码粗略地做了和cross_val_score()相同的事情,并且输出相同的结果。

from sklearn.model_selection import StratifiedKFoldfrom sklearn.base import cloneskfolds = StratifiedKFold(n_splits=3, random_state=42)for train_index, test_index in skfolds.split(X_train, y_train_5): clone_clf = clone(sgd_clf) X_train_folds = X_train[train_index] y_train_folds = y_train_5[train_index]X_test_fold=X_train[test_index] y_test_fold = y_train_5[test_index] clone_clf.fit(X_train_folds, y_train_folds) y_pred = clone_clf.predict(X_test_fold) n_correct = sum(y_pred == y_test_fold) print(n_correct/len(y_pred))#prints0.9502,0.96565,and0.96495

让我们使用cross_val_score()函数来评估SGDClassifier模型,同时使用 K 折交叉验证,此处让k=3。记住:K 折交叉验证意味着把训练集分成 K 折(此处 3 折),然后使用模型对其中一折进行预测,用其他折进行训练。

>>> from sklearn.model_selection import cross_val_score>>> cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")array([0.96355, 0.93795, 0.95615])

哇!在交叉验证上有大于 95% 的精度(accuracy)?这看起来很令人吃惊。先别高兴,让我们来看一个非常笨的分类器去,看看其在“非 5”这个类上的表现。

from sklearn.base import BaseEstimatorclass Never5Classifier(BaseEstimator): def fit(self, X, y=None): pass def predict(self, X): return np.zeros((len(X), 1), dtype=bool)

你能猜到这个模型的精度吗?揭晓谜底:

>>> never_5_clf = Never5Classifier()>>> cross_val_score(never_5_clf, X_train, y_train_5, cv=3,scoring="accuracy")array([0.91125, 0.90855, 0.90915])

没错,这个笨的分类器也有 90% 的精度。这是因为只有 10% 的图片是数字 5,所以你总是猜测某张图片不是 5,你也会有90%的可能性是对的。

这证明了为什么精度通常来说不是一个好的性能度量指标,特别是当你处理不平衡的数据集,比方说其中一些类比其他类频繁得多。

3.2Confusion Matrix —— 混淆矩阵

对分类器来说,一个好得多的性能评估指标是混淆矩阵。大体思路是:统计类别A被分类成类别 B 的次数。举个例子,为了知道分类器将 5 误分为 3 的次数,你需要查看混淆矩阵的第五行第三列。为了计算混淆矩阵,首先你需要有一系列的预测值,这样才能将预测值与真实值做比较。你或许想在测试集上做预测。但是我们现在先不碰它。(记住,只有当你处于项目的尾声,当你准备上线一个分类器的时候,你才应该使用测试集)。相反,你应该使用cross_val_predict()函数:

from sklearn.model_selection import cross_val_predicty_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)

就像 cross_val_score()函数,cross_val_predict()也使用 K 折交叉验证。它不是返回一个评估分数,而是返回基于每一个测试折做出的预测结果。这意味着,对于每一个训练集中的样本,你会得到一个干净的预测(“干净”是说是一个模型在训练过程中没有用到这个数据)。现在使用 confusion_matrix()函数,你将会得到一个混淆矩阵。只需要传入目标类别(y_train_5)和预测类别(y_train_pred)。

>>> from sklearn.metrics import confusion_matrix>>> confusion_matrix(y_train_5, y_train_pred)array([[53057, 1522], [ 1325, 4096]])

混淆矩阵中的每一行表示一个实际的类别, 而每一列表示一个预测的类别。该矩阵的第一行认为“非 5”(反例)中的 53057 张被正确归类为 “非 5”(他们被称为真反例,true negatives), 而其余 15227 被错误归类为"是 5" (假正例,false positives)。第二行认为“是 5” (正例)中的 1325 被错误地归类为“非 5”(假反例,false negatives),其余 4096被正确分类为 “是 5”类(真正例,true positives)。一个完美的分类器将只有真反例和真正例,所以混淆矩阵的非零值仅在其主对角线(左上至右下)。

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

混淆矩阵可以提供很多信息。有时候你会想要更加简明的指标。一个有趣的指标是正例预测的精度,也叫做分类器的准确率(precision)。其中,TP 是真正例的数目,FP 是假正例的数目。

想要一个完美的准确率,一个简单的方法是做一次单一正例的预测并确保这个预测是正确的(precision = 1/1 = 100%)。但是这没什么用,因为分类器会忽略所有样例,除了那一个正例。所以准确率一般会伴随另一个指标一起使用,这个指标叫做召回率(recall),也叫做敏感度(sensitivity)或者真正例率(true positive rate, TPR)。这是正例被分类器正确探测出的比率。

FN 是假反例的数目。

如果你对于混淆矩阵感到困惑,下图将对你有帮助。

3.3Precision and Recall——准确率与召回率Scikit-Learn 提供了一些函数去计算分类器的指标,包括准确率和召回率。

>>> from sklearn.metrics import precision_score, recall_score>>> precision_score(y_train_5, y_train_pred) # == 4096 / (4096 + 1522)0.7290850836596654>>> recall_score(y_train_5, y_train_pred) # == 4096 / (4096 + 1325)0.7555801512636044

当你去观察精确度的时候,你的“数字 5 探测器”看起来还不够好。当它判断某张图片是 5 的时候,它只有 72.9% 的可能性是正确的。而且,它也只检测出“是 5”类图片当中的 75.6%。通常结合准确率和召回率会更加方便,这个指标叫做“F1 值”,特别是当你需要一个简单的方法去比较两个分类器的优劣的时候。F1 值是准确率和召回率的调和平均。普通的平均平等地看待所有的值,而调和平均会给小的值更大的权重。所以,要想分类器得到一个高的 F1 值,需要召回率和准确率同时高。为了计算 F1 值,简单调用f1_score()即可:

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

F1 支持那些有着相近准确率和召回率的分类器。这不总是你想要的:有的场景你会绝大程度地关心准确率,而另外一些场景你会更关心召回率。例如,如果你训练一个分类器去检测视频是否适合儿童观看,你会倾向选择那种即便拒绝了很多好视频(低召回率)、但保证所保留的视频都是好(高准确率)的分类器,而不是那种高召回率、但让坏视频混入的分类器(这种情况下你或许想增加人工去检测分类器选择出来的视频)。另一方面,加入你训练一个分类器去检测监控图像当中的窃贼,有着 30% 准确率、99% 召回率的分类器或许是合适的(当然,警卫会得到一些错误的报警,但是几乎所有的窃贼都会被抓到)。

不幸的是,你不能同时拥有两者。增加准确率会降低召回率,反之亦然。这叫做准确率与召回率之间的折衷。

3.4Precision/Recall Trade-off——准确率与召回率之间的折衷为了弄懂这个折衷,我们看一下SGDClassifier是如何做分类决策的。对于每个样例,它根据决策函数计算分数,如果这个分数大于一个阈值,它会将样例分配给正类,否则它将分配给反类。下图显示了从左边的最低分数排到右边的最高分。假设决策阈值位于中间的箭头(介于两个 5 之间):您将发现4个真正例(数字 5)和一个假正例(数字 6)在该阈值的右侧。因此,使用该阈值,准确率为 80%(4/5)。但实际有 6 个数字 5,分类器只检测 4 个, 所以召回是 67% (4/6)。现在,如果你提高阈值(移动到右侧的箭头),假正例(数字 6)成为一个真反例,从而提高准确率(在这种情况下高达 100%),但一个真正例变成假反例,召回率降低到 50%。相反,降低阈值可提高召回率、降低准确率。Scikit-Learn 不让你直接设置阈值,但是它给你提供了获取决策分数的方法,这个决策分数可以用来进行预测。它不是调用分类器的predict()方法,而是调用decision_function()方法。这个方法返回每一个样例的分数值,然后基于这个分数值,使用你想要的任何阈值做出预测。

>>> y_scores = sgd_clf.decision_function([some_digit])>>> y_scoresarray([2412.53175101])>>> threshold = 0>>> y_some_digit_pred = (y_scores > threshold)array([ True])

SGDClassifier用了一个等于 0 的阈值,所以前面的代码返回了跟predict()方法一样的结果(都返回了true)。让我们提高这个阈值:

>>> threshold = 8000>>> y_some_digit_pred = (y_scores > threshold)>>> y_some_digit_predarray([False])

这证明了提高阈值会降低召回率。这个图片实际是数字 5,当阈值等于 0 的时候,分类器可以探测到这是一个 5,当阈值提高到 8000 的时候,分类器将不能探测到这是数字 5。那么,你应该使用哪个阈值呢?首先,你需要再次使用cross_val_predict()得到每一个样例的分数值,但是这一次指定返回一个决策分数,而不是预测结果。

y_scores=cross_val_predict(sgd_clf,X_train,y_train_5,cv=3,method="decision_function")

现在有了这些分数值。对于任何可能的阈值,使用precision_recall_curve(),你都可以计算准确率和召回率:

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

最后,你可以使用 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")[...] #highlightthethresholdandaddthelegend,axislabel,andgridplot_precision_recall_vs_threshold(precisions, recalls, thresholds)plt.show()

你也许会好奇为什么准确率曲线比召回率曲线更加起伏不平。原因是准确率有时候会降低,尽管当你提高阈值的时候,通常来说准确率会随之提高。另一方面,当阈值提高时候,召回率只会降低。这也就说明了为什么召回率的曲线更加平滑。另一个选出好的准确率/召回率折衷的方法是直接画出准确率对召回率的曲线。可以看到,在召回率在 80% 左右的时候,准确率急剧下降。你可能会想选择在急剧下降之前选择出一个准确率/召回率折衷点。比如说,在召回率 60% 左右的点。当然,这取决于你的项目需求。我们假设你决定达到 90% 的准确率。你查阅第一幅图,在 8000 附近找到一个阈值。为了更精确一点,您可以搜索至少能提供90%准确率的最低阈值。

threshold_90_precision = thresholds[np.argmax(precisions >= 0.90)] # ~7816

要进行预测(在训练集上),不需要调用分类器的predict()方法,您可以运行以下代码:

y_train_pred_90 = (y_scores >= threshold_90_precision)

让我们检查一下预测的准确率和召回率:

>>> precision_score(y_train_5, y_train_pred_90)0.9000380083618396>>> recall_score(y_train_5, y_train_pred_90)0.4368197749492714

很棒!你拥有了一个 90% 准确率的分类器。很容易去创建一个任意准确率的分类器,只要将阈值设置得足够高。但是,一个高准确率的分类器不是非常有用,如果它的召回率太低!3.5 The ROC Curve ——ROC 曲线

受试者工作特征(ROC)曲线是另一个二分类器常用的工具。它非常类似于准确率/召回率曲线,但不是画出准确率对召回率的曲线,ROC 曲线是真正例率(true positive rate,召回率的另一个名字)对假正例率(false positive rate, FPR)的曲线。FPR 是反例被错误分成正例的比率。它等于 1 减去真反例率(true negative rate, TNR)。TNR是反例被正确分类的比率。TNR也叫做特异性。所以, ROC 曲线画出召回率对(1 减去特异性)的曲线。

为了画出 ROC 曲线,你首先需要计算各种不同阈值下的 TPR、FPR,使用roc_curve()函数:

from sklearn.metrics import roc_curvefpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

然后你可以使用 matplotlib,画出 FPR 对 TPR 的曲线。

def plot_roc_curve(fpr, tpr, label=None): plt.plot(fpr, tpr, linewidth=2, label=label) plt.plot([0, 1], [0, 1], 'k--') # Dashed diagonal [...] # Add axis labels and grid plot_roc_curve(fpr, tpr)plt.show()

这里同样存在折衷的问题:召回率(TPR)越高,分类器就会产生越多的假正例(FPR)。图中的点线是一个完全随机的分类器生成的 ROC 曲线;一个好的分类器的 ROC 曲线应该尽可能远离这条线(即向左上角方向靠拢)。一个比较分类器之间优劣的方法是:测量ROC曲线下的面积(AUC)。一个完美的分类器的AUC 等于 1,而一个纯随机分类器的AUC 等于 0.5。Scikit-Learn 提供了一个函数来计算AUC:

>>> from sklearn.metrics import roc_auc_score>>> roc_auc_score(y_train_5, y_scores)0.9611778893101814

因为 ROC 曲线跟准确率/召回率曲线(或者叫 PR)很类似,你或许会好奇如何决定使用哪一个曲线呢?一个笨拙的规则是,优先使用 PR 曲线:当正例很少,或者当你关注假正例多于假反例的时候。其他情况使用 ROC 曲线。例如,回顾前面的 ROC 曲线和AUC 数值,你或许认为这个分类器很棒。但是这几乎全是因为只有少数正例(“是 5”),而大部分是反例(“非 5”)。相反,PR 曲线清楚显示出这个分类器还有很大的改善空间(PR 曲线应该尽可能地靠近右上角)。让我们训练一个RandomForestClassifier,然后拿它的的ROC曲线和AUC数值去跟SGDClassifier的比较。首先你需要得到训练集每个样例的分数。但是由于随机森林分类器的工作方式,RandomForestClassifier不提供decision_function()方法。相反,它提供了predict_proba()方法。Skikit-Learn分类器通常有二者中的一个,或者都有。predict_proba()方法返回一个数组,数组的每一行代表一个样例,每一列代表一个类。数组当中的值的意思是:给定一个样例属于给定类的概率。比如,70%的概率这幅图是数字 5。

from sklearn.ensemble import RandomForestClassifierforest_clf = RandomForestClassifier(random_state=42)y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3,method="predict_proba")

但是要画 ROC 曲线,你需要的是样例的分数,而不是概率。一个简单的解决方法是使用正例的概率当作样例的分数。

y_scores_forest=y_probas_forest[:,1]#score=probaofpositiveclassfpr_forest,tpr_forest,thresholds_forest=roc_curve(y_train_5,y_scores_forest)

现在你即将得到 ROC 曲线。将前面一个分类器的 ROC 曲线一并画出来是很有用的,可以清楚地进行比较。

plt.plot(fpr, tpr, "b:", label="SGD")plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")plt.legend(loc="lower right")plt.show()

如你所见,RandomForestClassifier的 ROC 曲线比SGDClassifier的好得多:它更靠近左上角。所以,它的AUC 也会更大。

>>> roc_auc_score(y_train_5, y_scores_forest)0.9983436731328145

计算一下准确率和召回率:99% 的准确率,86.6% 的召回率。还不错。

现在你知道如何训练一个二分类器,选择合适的性能指标,使用交叉验证去评估你的分类器,选择满足你需要的准确率/召回率折衷方案,和比较不同模型的 ROC 曲线和 AUC 数值。现在让我们检测更多的数字,而不仅仅是一个数字 5。

4)MulticlassClassification——多类分类

二分类器只能区分两个类,而多类分类器(也被叫做多项式分类器)可以区分多于两个类。

一些算法(比如随机森林分类器或者朴素贝叶斯分类器)可以直接处理多类分类问题。其他一些算法(比如 SVM 分类器或者逻辑回归)则是严格的二分类器。但是,有许多策略可以让你用二分类器去执行多类分类。

举个例子,创建一个可以将图片分成 10 类(从 0 到 9)的系统的一个方法是:训练10个二分类器,每一个对应一个数字(数字0检测器,数字1检测器,以此类推)。然后当你想对某张图片进行分类的时候,让每一个分类器对这个图片进行分类,选出决策分数最高的那个分类器。这叫做“一对所有"(OvA)策略(也被叫做“一对其他”)。

另一个策略是对每一对数字都训练一个二分类器:一个分类器用来处理数字 0 和数字 1,一个用来处理数字 0 和数字 2,一个用来处理数字 1 和 2,以此类推。这叫做“一对一”(OvO)策略。如果有 N 个类。你需要训练N*(N-1)/2个分类器。对于 MNIST 问题,需要训练 45 个二分类器!当你想对一张图片进行分类,你必须将这张图片跑在全部45个二分类器上,然后看哪个类胜出。OvO 策略的主要优点是:每个分类器只需要在训练集的部分数据上面进行训练。这部分数据是它所需要区分的那两个类对应的数据。

一些算法(比如 SVM 分类器)很难随着训练集的大小进行扩展,所以对于这些算法,OvO 是比较好的,因为它可以在小的数据集上面训练多个分类器,而不适合在大的数据集上训练少量的分类器。但是,对于大部分的二分类器来说,OvA 是更好的选择。

Scikit-Learn 可以探测出你想使用一个二分类器去完成多分类的任务,它会自动地执行 OvA或者 OvO策略,这取决于所用的算法。让我们用sklearn.svm.SVC 类试一下支持向量机分类器:

>>> from sklearn.svm import SVC>>> svm_clf = SVC()>>> svm_clf.fit(X_train, y_train) # y_train, not y_train_5>>> svm_clf.predict([some_digit])array([5], dtype=uint8)

很容易。上面的代码在训练集上训练了一个支持向量机分类器。这个分类器处理原始的类别标签,从 0 到 9(y_train),而不是仅仅探测是否为 5 (y_train_5),然后它做出一个预测。在底层,Scikit-Learn 实际上使用了OvO策略,训练了45 个二分类器,每个分类器都产生一张图片的决策数值,选择数值最高的那个类。

如果你调用decision_function()方法,它会为每个样例返回 10 个数值,而不只是返回一个数值。每个数值对应于一个类。

>>> some_digit_scores = svm_clf.decision_function([some_digit])>>> some_digit_scoresarray([[2.92492871,7.02307409,3.93648529,0.90117363,5.96945908, 9.5 , 1.90718593, 8.02755089, -0.13202708, 4.94216947]])

最高数值是对应于类别 5 :

>>> np.argmax(some_digit_scores)5 >>>svm_clf.classes_array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)>>> svm_clf.classes_[5]5

如果你想强制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,你可以使用OneVsOneClassifier类或者OneVsRestClassifier类。创建一个样例,传递一个分类器给它的构造函数。下面的代码会创建一个多类分类器,使用 OvR 策略,基于SVC。

>>> from sklearn.multiclass import OneVsRestClassifier>>> ovr_clf = OneVsRestClassifier(SVC())>>> ovr_clf.fit(X_train, y_train)>>> ovr_clf.predict([some_digit])array([5], dtype=uint8)>>> len(ovr_clf.estimators_)10

训练一个SGDClassifier 或者 RandomForestClassifier同样简单:

>>> sgd_clf.fit(X_train, y_train)>>> sgd_clf.predict([some_digit])array([5], dtype=uint8)

这次 Scikit-Learn 没有必要去运行 OvO 或者 OvA,因为SGD分类器能够直接将一个样例分到多个类别。现在,decision_function()方法为每个类返回一个值。让我们看看SGD分类器分配给每个类的分数:

>>> sgd_clf.decision_function([some_digit])array([[-15955.22628,-38080.96296,-13326.66695,573.52692,-17680.68466,2412.53175,-25526.86498,-12290.15705,-7946.05205,-10631.35889]])

你可以看到这个分类器相当确信它的预测:几乎所有的分数都是很大的负数,而第5类的分数是2412.5。该分类器对类别3有一点怀疑,因为类别3的得分为573.5。当然,现在你需要评估这个分类器。通常,您可以使用交叉验证。使用cross_val_score()函数来评估SGDClassifier的准确率:

>>> cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")array([0.8489802 , 0.87129356, 0.86988048])

在所有测试折(test fold)上,它有 84% 的精度。如果你用一个随机的分类器,你将会得到 10% 的正确率。所以这不是一个坏的分数,但是你可以做的更好。举个例子,简单将输入归一化,将会提高精度到89% 以上。

>>> from sklearn.preprocessing import StandardScaler>>> scaler = StandardScaler()>>> X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))>>> cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3,scoring="accuracy")array([0.89707059, 0.8960948 , 0.90693604])

5)ErrorAnalysis——误差分析

当然,如果这是一个实际的项目,你会在你的机器学习项目当中,跟随以下步骤(见附录 B)。探索数据准备的候选方案,尝试多种模型,把最好的几个模型列为入围名单,用GridSearchCV调试超参数,尽可能地自动化。在这里,我们假设你已经找到一个不错的模型,你试图找到方法去改善它。一个方式是分析模型产生的误差类型。

首先,你可以检查混淆矩阵。你需要使用cross_val_predict()做出预测,然后调用confusion_matrix()函数。

>>> y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train,cv=3)>>> conf_mx = confusion_matrix(y_train, y_train_pred)>>> conf_mxarray([[5578, 0, 22, 7,8, 45, 35, 5, 222, 1], [ 0, 6410, 35, 26, 4, 44, 4, 8, 198, 13],[28,27,5232, 100, 74, 27, 68, 37, 354, 11],[23,18,115,5254, 2, 209, 26, 38, 373, 73],[11,14,45, 12,5219, 11, 33, 26, 299, 172],[26,16,31,173,54,4484, 76, 14, 482, 65], [ 31, 17, 45, 2, 42, 98, 5556, 3, 123, 1], [ 20, 10, 53, 27, 50, 13, 3, 5696, 173, 220],[17,64,47,91,3,125, 24, 11,5421, 48],[24,18,29,67,116,39,1,174, 329,5152]])

这是一大堆数字。使用 Matplotlib 的matshow()函数,将混淆矩阵以图像的方式呈现,将会更加方便。

plt.matshow(conf_mx, cmap=plt.cm.gray)plt.show()

这个混淆矩阵看起来相当好,因为大多数的图片在主对角线上,意味着被分类正确。数字 5 对应的格子看起来比其他数字要暗淡许多。这可能是数据集当中数字 5 的图片比较少,又或者是分类器对于数字 5 的表现不如其他数字那么好。你可以验证两种情况。让我们关注仅包含误差数据的图像。首先你需要将混淆矩阵的每一个值除以相应类别的图片的总数目。这样子,你可以比较错误率,而不是绝对的错误数(这对数量较大的类别不公平)。

row_sums = conf_mx.sum(axis=1, keepdims=True)norm_conf_mx = conf_mx / row_sums

现在让我们用 0 来填充对角线。这样子就只保留了被错误分类的数据。让我们画出这个结果。

np.fill_diagonal(norm_conf_mx, 0)plt.matshow(norm_conf_mx, cmap=plt.cm.gray)plt.show()

现在你可以清楚看出分类器的各类误差。记住:行代表实际类别,列代表预测的类别。第8类的列非常明亮,这告诉您许多图像被错误地分类为第8类。然而,类8的行并没有那么糟糕,它告诉您实际的第8类通常被正确地分类为了第8类。正如您所看到的,混淆矩阵不一定是对称的。你还可以看到第3类和第5类经常被互相混淆。

分析混淆矩阵通常可以给你提供深刻的见解去改善你的分类器。回顾这幅图,看样子你应该努力改善分类器在数字 8的表现。例如,你可以尝试去收集更多的看起来像8的数据,或者你可以构造新的、有助于分类器的特征。例如,写一个算法去统计闭合的环(比如,数字 8 有两个环,数字 6 有一个, 5没有)。又或者你可以预处理图片(比如,使用 Scikit-Learn,Pillow或 OpenCV)去使得一些特征更加突出。

分析单个类型的误差也是一种很好的方法,可以帮助您了解您的分类器在做什么以及为什么失败,但是这样做更困难,也更费时。例如,我们画出第3类和第5类的样本。

cl_a, cl_b = 3, 5X_aa = X_train[(y_train == cl_a) & (y_train_pred == cl_a)]X_ab = X_train[(y_train == cl_a) & (y_train_pred == cl_b)]X_ba = X_train[(y_train == cl_b) & (y_train_pred == cl_a)]X_bb = X_train[(y_train == cl_b) & (y_train_pred == cl_b)]plt.figure(figsize=(8,8))plt.subplot(221); plot_digits(X_aa[:25], images_per_row=5)plt.subplot(222); plot_digits(X_ab[:25], images_per_row=5)plt.subplot(223); plot_digits(X_ba[:25], images_per_row=5)plt.subplot(224); plot_digits(X_bb[:25], images_per_row=5)plt.show()

左边两个5*5的块将数字识别为 3,右边的将数字识别为 5。一些被分类器错误分类的数字(比如左下角和右上角的块)是书写地相当差,甚至让人类分类都会觉得很困难。但是,大部分被误分类的数字,在我们看来都是显而易见的错误,很难明白为什么分类器会分错。原因是我们使用的简单的SGDClassifier,这是一个线性模型。它所做的全部工作就是分配一个类权重给每一个像素,然后当它看到一张新的图片,它就将加权的像素强度相加,每个类得到一个新的值。所以,因为 3 和 5 只有一小部分的像素有差异,这个模型很容易混淆它们。

3 和 5 之间的主要差异是连接顶部的线和底部的线的细线位置。如果你画一个 3,连接处稍微向左偏移,分类器很可能将它分类成 5。反之亦然。换一个说法,这个分类器对于图片的位移和旋转相当敏感。所以,减轻 3/5 混淆的一个方法是对图片进行预处理,确保它们都很好地中心化和不过度旋转。这同样很可能帮助减轻其他类型的错误。

6)MultilabelClassification——多标签分类

到目前为止,所有的样例都总是被分配到仅一个类。有些情况下,你也许想让你的分类器给一个样例输出多个类别。比如说,考虑一个人脸识别器:如果对于同一张图片,它识别出多个人,它应该做什么?当然它应该给每一个它识别出的人贴上一个标签。比方说,这个分类器被训练成识别三个人脸,Alice,Bob,Charlie;然后当它被输入一张含有 Alice 和 Bob 的图片,它应该输出[1, 0, 1](意思是:Alice 是,Bob 不是,Charlie 是)。这种输出多个二值标签的分类系统被叫做多标签分类系统。目前我们不打算深入脸部识别。我们可以先看一个简单点的例子,仅仅是为了阐明的目的。

from sklearn.neighbors import KNeighborsClassifiery_train_large = (y_train >= 7)y_train_odd = (y_train % 2 == 1)y_multilabel = np.c_[y_train_large, y_train_odd]knn_clf = KNeighborsClassifier()knn_clf.fit(X_train, y_multilabel)

这段代码创造了一个y_multilabel数组,里面包含两个目标标签。第一个标签指出这个数字是否为大数字(7,8 或者 9),第二个标签指出这个数字是否是奇数。接下来几行代码创建了一个KNeighborsClassifier(它支持多标签分类,但不是所有分类器都可以),然后我们使用多目标数组来训练它。现在你可以生成一个预测,然后它输出两个标签:

>>> knn_clf.predict([some_digit])array([[False, True]])

它工作正确。数字 5 不是大数(False),同时是一个奇数(True)。

有许多方法去评估一个多标签分类器,选择正确的量度标准取决于你的项目。其中一种方法是对每个标签计算F1 值(或者前面讨论过的其他任意的二分类器的量度标准),然后计算平均值。下面的代码计算全部标签的平均 F1 值:

>>> y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel,cv=3)>>> f1_score(y_multilabel, y_train_knn_pred, average="macro")0.976410265560605

这里假设所有标签有着同等的重要性,但可能不是这样。特别是,如果你的 Alice 的照片比 Bob 或者 Charlie 更多的时候,也许你想让分类器在 Alice 的照片上具有更大的权重。一个简单的选项是:给每一个标签的权重等于它的支持度(比如,那个标签的样例的数目)。为了做到这点,简单地在上面代码中设置average="weighted"。

7)Multioutput Classification——多输出分类

我们即将讨论的最后一种分类任务被叫做“多输出-多类分类”(或者简称为多输出分类)。它是多标签分类的简单泛化,在这里每一个标签可以是多类别的(比如说,它可以有多于两个可能值)。

为了说明这点,我们建立一个系统,它可以去除图片当中的噪音。它将一张混有噪音的图片作为输入,期待它输出一张干净的数字图片,用一个像素强度的数组表示,就像 MNIST 图片那样。注意到这个分类器的输出是多标签的(一个像素一个标签)和每个标签可以有多个值(像素强度取值范围从 0 到 255)。所以它是一个多输出分类系统的例子。

注:分类与回归之间的界限是模糊的,比如这个例子。按理说,预测一个像素的强度更类似于一个回归任务,而不是一个分类任务。而且,多输出系统不限于分类任务。你甚至可以让你一个系统给每一个样例输出多个标签,包括类标签和值标签。

让我们从 MNIST 的图片创建训练集和测试集开始,然后给图片的像素强度添加噪声,这里是用 NumPy 的randint()函数。目标图像是原始图像。

noise = np.random.randint(0, 100, (len(X_train), 784))X_train_mod = X_train + noisenoise = np.random.randint(0, 100, (len(X_test), 784))X_test_mod = X_test + noisey_train_mod = X_trainy_test_mod = X_test

让我们看一下测试集当中的一张图片(是的,我们在窥探测试集,所以你应该马上邹眉)。

左边是加噪声的输入图片,右边是干净的目标图片。现在我们训练分类器,让它清洁这张图片:

knn_clf.fit(X_train_mod, y_train_mod)clean_digit = knn_clf.predict([X_test_mod[some_index]])plot_digit(clean_digit)

看起来足够接近目标图片。现在总结我们的分类之旅。希望你现在应该知道如何选择好的性能指标,挑选出合适的准确率/召回率的折衷方案,比较分类器,更一般地说,就是为不同的任务建立起好的分类系统。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。