分类问题(五)多元分类

多元分类

之前介绍过二元分类器,可以将数据分成两个类别(例如“数字5”和“非数字5”)。多元分类器(也称为多项式分类器)可以区分两个以上的类别。

有些算法(例如随机森林或朴素贝叶斯)可以直接处理多个类别。其他如SVM、线性分类器则是严格的二元分类器。不过我们仍有很多不同的办法可以让二元分类器实现多元分类的功能。

例如,其中一种将手写数字分类成10个类别(0-9)的方式是训练10个二元分类器,每个二元分类器分类一个数字(例如0-分类器、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个二元分类器,非常耗时且麻烦。OvO主要的好处是:每个分类器仅需要在部分训练集上(这部分训练集需要包含要区分的两个类别数据)进行训练即可。

一些算法如SVM,它随着数据集的扩大,它本身的扩展并不会随着有很大的提升,所以比较适合OvO。它可以在很多个小的训练集上训练多个分类器,而不需要在一个大的训练集上训练少数几个分类器。对于大多数二元分类算法来说,OvA是更合适的。

在sk-learn中,如果使用了一个二元分类算法训练一个多分类问题,则它会自动运行OvA(除了SVM,它会自动使用OvO)。下面我们试试SGDClassifier:

sgd_clf.fit(X_train, y_train)
sgd_clf.predict(X_test[:5])
>[7 2 1 0 4]

 

可以看到用法其实很简单。在它底层,其实是训练了10个二元分类器,并在预测时将图片送到10个分类器中,从中取决策分数最高分作为判断分类的类别。

进一步验证的话,我们可以调用decision_function() 方法,它这次对每条数据返回的不会一个分数,而是10个分数,每个类别一个分数:

sgd_clf.decision_function(X_test[:1])
>array([[-27972.77566096, -52417.77039463, -14344.98217961,
         -1308.44575644, -19922.84531732,  -9208.91066356,
        -38331.13646795,   8007.54256279,  -4273.31795296,
         -5951.32911022]])

np.argmax(digit_scores)
>7

sgd_clf.classes_
>array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

sgd_clf.classes_[7]
>7

 

可以看到最高分数是7,所以最后将它分类为数字7。

这里需要注意的是:在一个分类器被训练好后,它会将目标类别作为list存储在它的classes_ 属性中,按值的大小排列。在这个例子中,classes_数组中每个类别的索引正好是对应了类别(例如,索引5的值正好对应的是数字5),但是一般情况下其实并不会是这么碰巧。

 

如果我们希望强行让sk-learn使用one-versus-one或是one-versus-all,则可以使用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(X_test[:5])
>array([7, 2, 1, 0, 4], dtype=uint8)

len(ovo_clf.estimators_)
>45

 

可以看到一共训练了45个二元分类器。

下面我们训练一个RandomForestClassifier,非常简单,并且比前几个速度快地多:

forest_clf.fit(X_train, y_train)
forest_clf.predict(X_test[:5])
>array([7, 2, 1, 0, 4], dtype=uint8)

 

这次sk-leran并没有运行OvA或OvO,因为随机森林分类器可以直接将数据分类为多个类别。我们可以调用predict_proba() 获取每条数据被分类为每个类别的概率:

forest_clf.predict_proba(X_test[1:2])
>array([[0. , 0. , 0.7, 0.2, 0. , 0. , 0.1, 0. , 0. , 0. ]])

 

可以看到这个分类器有70%的确信度认为这张图片属于第2个类别(从0起始),也就是数字2。它同时也认为这张图片应属于3和6,对应的确信度分别为20%与10%。

接下来我们评估一下这些分类器,如之前一样,使用交叉验证。使用cross_val_score() 方法来评估SGDClassifier的准确率:

cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring='accuracy')
>array([0.87082583, 0.87089354, 0.88628294])

 

可以看到在所有测试折上,准去率均达到了87%以上。如果是一个完全随机的分类器,则它的准确率应为10%。相对于10%的准确率,我们这个分类器明显已经好了很多,但是还可以做的更好。例如,将它们做一个标准化:

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.89957009, 0.89344467, 0.89963495])

 

可以看到正确率提高到了89%以上。在训练完模型后,根据机器学习项目流程,下一步我们会尝试去优化模型,其中一个方法就是误差分析,也是接下来介绍的内容。

 

posted @ 2020-02-19 13:37  ZacksTang  阅读(3369)  评论(0编辑  收藏  举报