二元分类器区分两个类,多类分类器(也称为多项分类器)可以区分两个以上的类别。
一些算法(如随机森林分类器或朴素贝叶斯分类器)能够直接处理多个类。其他(如支持向量机分类器或线性分类器)是严格的二元分类器。但但是,您可以使用多种策略来使用多个二元分类器来执行多类别分类。
例如,创建一个可以将数字图像分类为10个类别(从0到9)的系统的一种方法是为每一个数字训练一个二元分类器(一个0检测器,1个1检测器,1个2检测器,等等)。然后,当你想要对一个图像进行分类时,你会得到每个分类器的决策分数,然后你选择分类器输出分数最高的类别。这就是所谓的“一对所有[one-versus-all]" (OvA)”策略(也叫“one-versus-the-rest”)。
另一个策略是为每一对数字训练一个二元分类器:一个用来区分0和1,另一个用来区分0和2,再另一个用来区分1和2,等等。这就是所谓的“一对一[one-versus-one]”(OvO)策略。如果有N类,你需要训练N×(N - 1)/ 2个分类器。对于MNIST问题,这意味着训练45个二元分类器!当你想要对图像进行分类时,你必须通过运行所有的45个分类器来处理图像,然后看哪个类赢得最多的决斗。OvO的主要优点是,每个分类器只需要对它必须区分的两个类别的训练集上进行训练。
一些算法(如支持向量机分类器)因为训练集大小而缩放的不充分,因此,对于这些算法,OvO是首选的,因为在小型训练集上训练许多分类器比在大型训练集上训练少数分类器更快。然而,对于大多数二元分类算法来说,OvA是首选。
Scikit-Learn检测到,当您尝试使用二元分类算法进行多类别分类任务时,它会自动运行OvA(除了用于使用OvO的SVM分类器之外)。我们用SGDClassifier来试试。
>>> sgd_clf.fit(X_train, y_train) # y_train, not y_train_5
>>> sgd_clf.predict([some_digit])
array([ 5.])
这很容易!这段代码将在训练集上使用0~9的原始目标类别来训练SGDClassifier,而不是 5-versus-all的目标类别(y_train_5)。然后它做出一个预测(在这个例子中是正确的)。在引擎盖下,Scikit-Learn实际上训练了10个二元分类器,获得了图像的决策分数,并选择了得分最高的类别。
为了证明这是事实,您可以调用decision_function()方法。现在,它不再只返回一个分数,而是返回10个分数,每个类别一个分数:
>>> 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对应的分数。
>>> 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使用one-versus-one或one-versus-all,您可以使用OneVsOneClassifier或OneVsRestClassifier类。简单地创建一个实例并将一个二元分类器传递给它的构造函数。例如,该代码基于一个SGDClassifier创建一个使用OvO策略的多类分类器。
>>> 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
训练一个随机森林分类器同样简单:
>>> forest_clf.fit(X_train, y_train)
>>> forest_clf.predict([some_digit])
array([ 5.])
这一次,Scikit-Learn不必运行OvA或OvO,因为随机森林分类器可以直接将实例分类为多个类。您可以调用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表示模型估计图像代表5的概率是80%。它还认为图像可以是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])
它在所有测试的折叠上的精度都超过了84%。如果你使用一个随机分类器,你会得到10%的精度,所以这不是一个糟糕的分数,但是你仍然可以做得更好。例如,简单地缩放输入(如第2章所讨论的),可以将精度提高奥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 ]):