【翻译】Sklearn 与 TensorFlow 机器学习实用指南 —— 第3章 分类(上)

浏览: 1782

作者:ApacheCN   Python机器学习爱好者

Python爱好者社区专栏作者

GitHub:https://github.com/apachecn/hands_on_Ml_with_Sklearn_and_TF


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

MNIST

在本章当中,我们将会使用 MNIST 这个数据集,它有着 70000 张规格较小的手写数字图片,由美国的高中生和美国人口调查局的职员手写而成。这相当于机器学习当中的“Hello World”,人们无论什么时候提出一个新的分类算法,都想知道该算法在这个数据集上的表现如何。机器学习的初学者迟早也会处理 MNIST 这个数据集。

Scikit-Learn 提供了许多辅助函数,以便于下载流行的数据集。MNIST 是其中一个。下面的代码获取 MNIST

>>> from sklearn.datasets import fetch_mldata
>>> mnist = fetch_mldata('MNIST original')
>>> mnist
{'COL_NAMES': ['label', 'data'],
'DESCR': 'mldata.org dataset: mnist-original',
'data': array([[0, 0, 0, ..., 0, 0, 0],
               [0, 0, 0, ..., 0, 0, 0],
               [0, 0, 0, ..., 0, 0, 0],
               ...,
               [0, 0, 0, ..., 0, 0, 0],
               [0, 0, 0, ..., 0, 0, 0],
               [0, 0, 0, ..., 0, 0, 0]], dtype=uint8),
'target': array([ 0., 0., 0., ..., 9., 9., 9.])}

一般而言,由 sklearn 加载的数据集有着相似的字典结构,这包括:

  • DESCR键描述数据集

  • data键存放一个数组,数组的一行表示一个样例,一列表示一个特征

  • target键存放一个标签数组

让我们看一下这些数组

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

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

%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
some_digit = X[36000]
some_digit_image = some_digit.reshape(28, 28)
plt.imshow(some_digit_image, cmap = matplotlib.cm.binary, interpolation="nearest")
plt.axis("off")
plt.show()

image.png

这看起来像个 5,实际上它的标签告诉我们:

>>> y[36000]
5.0

图3-1 展示了一些来自 MNIST 数据集的图片。当你处理更加复杂的分类任务的时候,它会让你更有感觉。

image.png

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

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

让我们打乱训练集。这可以保证交叉验证的每一折都是相似(你不会期待某一折缺少某类数字)。而且,一些学习算法对训练样例的顺序敏感,当它们在一行当中得到许多相似的样例,这些算法将会表现得非常差。打乱数据集将保证这种情况不会发生。   

import numpy as np

shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]

训练一个二分类器


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

y_train_5 = (y_train == 5) # True for all 5s, False for all other digits.
y_test_5 = (y_test == 5)

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

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

SGDClassifier依赖于训练集的随机程度(所以被命名为 stochastic,随机之义)。如果你想重现结果,你应该固定参数random_state

现在你可以用它来查出数字 5 的图片。

>>> sgd_clf.predict([some_digit])
array([ True], dtype=bool)

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

对性能的评估

评估一个分类器,通常比评估一个回归器更加玄学。所以我们将会花大量的篇幅在这个话题上。有许多量度性能的方法,所以拿来一杯咖啡和准备学习许多新概念和首字母缩略词吧。

使用交叉验证测量准确性

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

实现交叉验证

在交叉验证过程中,有时候你会需要更多的控制权,相较于函数cross_val_score()或者其他相似函数所提供的功能。这种情况下,你可以实现你自己版本的交叉验证。事实上它相当直接。以下代码粗略地做了和cross_val_score()相同的事情,并且输出相同的结果。

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone
skfolds = 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)) # prints 0.9502, 0.96565 and 0.96495

StratifiedKFold类实现了分层采样(详见第二章的解释),生成的折(fold)包含了各类相应比例的样例。在每一次迭代,上述代码生成分类器的一个克隆版本,在训练折(training folds)的克隆版本上进行训,在测试折(test folds)上进行预测。然后它计算出被正确预测的数目和输出正确预测的比例。

让我们使用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.9502 , 0.96565, 0.96495]

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

from sklearn.base import BaseEstimator
class 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.909 , 0.90715, 0.9128 ])

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

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

混淆矩阵

对分类器来说,一个好得多的性能评估指标是混淆矩阵。大体思路是:输出类别A被分类成类别 B 的次数。举个例子,为了知道分类器将 5 误分为 3 的次数,你需要查看混淆矩阵的第五行第三列。

为了计算混淆矩阵,首先你需要有一系列的预测值,这样才能将预测值与真实值做比较。你或许想在测试集上做预测。但是我们现在先不碰它。(记住,只有当你处于项目的尾声,当你准备上线一个分类器的时候,你才应该使用测试集)。相反,你应该使用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)

就像 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([[53272, 1307],
       [ 1077, 4344]])

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

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

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

公式 3-1 准确率

$$ precision = \frac{TP}{TP + FP} $$

其中 TP 是真正例的数目,FP 是假正例的数目。

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

公式 3-2 Recall

$$ recall = \frac{TP}{TP + FN} $$

FN 是假反例的数目。

如果你对于混淆矩阵感到困惑,图 3-2 将对你有帮助

image.png



image.png

推荐 0
本文由 Python爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册