机器学习实战_分类(二)

咱们训练一个RandomForestClassifier,而后拿它的的ROC曲线和ROC AUC数值去跟SGDClassifier的比较。首先你须要获得训练集每一个样例的数值可是因为随机森林分类器的工做方式,RandomForestClassifier不提供decision_function()方法。相反,它提供了predict_proba()方法。Skikit-Learn分类器一般两者中的一个。predict_proba()方法返回一个数组,数组的每一行表明一个样例,每一列表明一个类。数组当中的值的意思是:给定一个样例属于给定类的几率。好比,70%的几率这幅图是数字 5。git

from sklearn.ensemble import RandomForestClassifier
forest_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 = proba of positive class 预测为正例几率
fpr_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="bottom right")
plt.show()

clipboard.png

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

>>> roc_auc_score(y_train_5, y_scores_forest)
0.99312433660038291

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

多类别分类

一些算法(好比随机森林分类器或者朴素贝叶斯分类器)能够直接处理多类分类问题。其余一些算法(好比 SVM 分类器或者线性分类器)则是严格的二分类器。而后,有许多策略可让你用二分类器去执行多类分类。测试

  • 一个方法是:训练10个二分类器,每个对应一个数字(探测器 0,探测器 1,探测器 2,以此类推)。而后当你想对某张图片进行分类的时候,让每个分类器对这个图片进行分类,选出决策分数最高的那个分类器(One vs all 里面分数最高的One)。这叫作“一对全部”(OvA)策略
  • 另外一个策略是对每一对数字都训练一个二分类器:一个分类器用来处理数字 0 和数字 1,一个用来处理数字 0 和数字 2,一个用来处理数字 1 和 2,以此类推。这叫作“一对一”(OvO)策略。若是有 N 个类。你须要训练N*(N-1)/2个分类器。

一些算法(好比 SVM 分类器)在训练集的大小上很难扩展,因此对于这些算法,OvO 是比较好的,由于它能够在小的数据集上面能够更多地训练,较之于巨大的数据集而言。可是,对于大部分的二分类器来讲,OvA 是更好的选择。Scikit-Learn 能够探测出你想使用一个二分类器去完成多分类的任务,它会自动地执行 OvA(除了 SVM 分类器,它使用 OvO)让咱们试一下SGDClassifier.spa

>>> sgd_clf.fit(X_train, y_train) # y_train, not y_train_5
>>> sgd_clf.predict([some_digit])
array([ 5.])

上面的代码在训练集上训练了一个SGDClassifier。这个分类器处理原始的目标class,从 0 到 9(y_train),而不是仅仅探测是否为 5 (y_train_5)。而后它作出一个判断(在这个案例下只有一个正确的数字)。在幕后,Scikit-Learn 实际上训练了 10 个二分类器,每一个分类器都产到一张图片的决策数值,选择数值最高的那个类rest

为了证实这是真实的,你能够调用decision_function()方法。不是返回每一个样例的一个数值,而是返回 10 个数值,一个数值对应于一个类code

>>> some_digit_scores = sgd_clf.decision_function([some_digit])
>>> some_digit_scores
array([[-311402.62954431, -363517.28355739, -446449.5306454 ,
        -183226.61023518, -414337.15339485, 161855.74572176,
        -452576.39616343, -471957.14962573, -518542.33997148,
        -536774.63961222]])

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

>>> np.argmax(some_digit_scores)    # 找最大值的索引
5
>>> sgd_clf.classes_
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
>>> sgd_clf.classes[5]    # 用索引匹配类别
5.0
一个分类器被训练好了以后,它会保存目标类别列表到它的属性classes_ 中去,按照值排序。在本例子当中,在classes_ 数组当中的每一个类的索引方便地匹配了类自己,好比,索引为 5 的类刚好是类别 5 自己。但一般不会这么幸运。

若是你想强制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,你可使用OneVsOneClassifier类或者OneVsRestClassifier类。建立一个样例,传递一个二分类器给它的构造函数。举例子,下面的代码会建立一个多类分类器,使用 OvO 策略,基于SGDClassifier。

>>> 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

训练一个RandomForestClassifier一样简单:

>>> forest_clf.fit(X_train, y_train)
>>> forest_clf.predict([some_digit])
array([ 5.])

此次 Scikit-Learn 没有必要去运行 OvO 或者 OvA,由于随机森林分类器可以直接将一个样例分到多个类别。你能够调用predict_proba(),获得样例对应的类别的几率值的列表

>>> forest_clf.predict_proba([some_digit])
array([[ 0.1, 0. , 0. , 0.1, 0. , 0.8, 0. , 0. , 0. , 0. ]])

你能够看到这个分类器至关确信它的预测:在数组的索引 5 上的 0.8,意味着这个模型以 80% 的几率估算这张图片表明数字 5。它也认为这个图片多是数字 0 或者数字 3,分别都是 10% 的概率。

如今固然你想评估这些分类器。像日常同样,你想使用交叉验证。让咱们用cross_val_score()来评估SGDClassifier的精度。

>>> cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
array([ 0.84063187, 0.84899245, 0.86652998])

在全部测试折(test fold)上,它有 84% 的精度。若是你是用一个随机的分类器,你将会获得 10% 的正确率。因此这不是一个坏的分数,可是你能够作的更好。举例子,简单将输入正则化,将会提升精度到 90% 以上。

>>> 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.91011798, 0.90874544, 0.906636 ])

偏差分析:

首先,你能够检查混淆矩阵。你须要使用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_mx
array([[5725, 3, 24, 9, 10, 49, 50, 10, 39, 4],
        [ 2, 6493, 43, 25, 7, 40, 5, 10, 109, 8],
        [ 51, 41, 5321, 104, 89, 26, 87, 60, 166, 13],
        [ 47, 46, 141, 5342, 1, 231, 40, 50, 141, 92],
        [ 19, 29, 41, 10, 5366, 9, 56, 37, 86, 189],
        [ 73, 45, 36, 193, 64, 4582, 111, 30, 193, 94],
        [ 29, 34, 44, 2, 42, 85, 5627, 10, 45, 0],
        [ 25, 24, 74, 32, 54, 12, 6, 5787, 15, 236],
        [ 52, 161, 73, 156, 10, 163, 61, 25, 5027, 123],
        [ 43, 35, 26, 92, 178, 28, 2, 223, 82, 5240]])

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

plt.matshow(conf_mx, cmap=plt.cm.gray)    # #灰度图,对应位置的值越大色块越亮
plt.show()

clipboard.png

这个混淆矩阵看起来至关好,由于大多数的图片在主对角线上。在主对角线上意味着被分类正确。数字 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()

clipboard.png

如今你能够清楚看出分类器制造出来的各种偏差。记住:行表明实际类别,列表明预测的类别。第 八、9 列至关亮,这告诉你许多图片被误分红数字 8 或者数字 9。类似的,第 八、9 行也至关亮,告诉你数字 八、数字 9 常常被误觉得是其余数字。相反,一些行至关黑,好比第一行:这意味着大部分的数字 1 被正确分类(一些被误分类为数字 8 )。留意到偏差图不是严格对称的。举例子,比起将数字 8 误分类为数字 5 的数量,有更多的数字 5 被误分类为数字 8。

分析混淆矩阵一般能够给你提供深入的看法去改善你的分类器。回顾这幅图,看样子你应该努力改善分类器在数字 8 和数字 9 上的表现,和纠正 3/5 的混淆。举例子,你能够尝试去收集更多的数据,或者你能够构造新的、有助于分类器的特征。举例子,写一个算法去数闭合的环(好比,数字 8 有两个环,数字 6 有一个, 5 没有)。又或者你能够预处理图片(好比,使用 Scikit-Learn,Pillow, OpenCV)去构造一个模式,好比闭合的环。

分析独特的偏差,是得到关于你的分类器是如何工做及其为何失败的洞见的一个好途径。可是这相对难和耗时。举例子,咱们能够画出数字 3 和 5 的例子

cl_a, cl_b = 3, 5
X_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()

clipboard.png

左边两个5*5的块将数字识别为 3,右边的将数字识别为 5。一些被分类器错误分类的数字(好比左下角和右上角的块)是书写地至关差,甚至让人类分类都会以为很困难(好比第 8 行第 1 列的数字 5,看起来很是像数字 3 )。可是,大部分被误分类的数字,在咱们看来都是显而易见的错误。很难明白为何分类器会分错。缘由是咱们使用的简单的SGDClassifier,这是一个线性模型。它所作的所有工做就是分配一个类权重给每个像素,而后当它看到一张新的图片,它就将加权的像素强度相加,每一个类获得一个新的值。因此,由于 3 和 5 只有一小部分的像素有差别,这个模型很容易混淆它们。

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

多标签分类

先看一个简单点的例子,仅仅是为了阐明的目的

from sklearn.neighbors import KNeighborsClassifier
y_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]], dtype=bool)

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

有许多方法去评估一个多标签分类器,和选择正确的量度标准,这取决于你的项目。举个例子,一个方法是对每一个个体标签去量度 F1 值(或者前面讨论过的其余任意的二分类器的量度标准),而后计算平均值。下面的代码计算所有标签的平均 F1 值:

>>> y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_train, cv=3)
>>> f1_score(y_train, y_train_knn_pred, average="macro")
0.96845540180280221

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

多输出分类

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

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

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

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

noise = rnd.randint(0, 100, (len(X_train), 784))
noise = rnd.randint(0, 100, (len(X_test), 784))
X_train_mod = X_train + noise
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test

让咱们看一下测试集当中的一张图片(是的,咱们在窥探测试集,因此你应该立刻邹眉):

clipboard.png

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

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

clipboard.png

分类内容就这么多。

相关文章
相关标签/搜索