OvO(一对一)和OvR(一对多)
为了创建一个分类器,将图片分为10类(0到9),
- 一种方法是训练10个二元分类器——“0-检测器”、“1检测器”一直到“9-检测器”;然后使用10个分类器分别对第i个图进行检测,取出其中得分最高的分类;称为一对剩余(OvR)策略,也称为一对多(one-vs-all)。
- 另一种方法,为每一对数字训练一个二元分类器,一个区分0和1,一个区分0和2,以此类推,称为一对一策略(OvO),如果存在N个类别,那么一共需要训练
个分类器。
对于数据量扩大而导致表现糟糕的时候,使用OvO更合适,例如SVC;对于大部分二元分类器,OvR更合适。Scikit-learn能够检测到使用二元分类器进行多类分类任务,从而根据情况自动运行OvR或者OvO。
from sklearn.datasets import fetch_openmlimport numpy as np# 读取数据mnist = fetch_openml('mnist_784')# mnist中存在手写体数据。X, y = mnist['data'], mnist['target']print(X.shape, y.shape)# (70000, 784) (70000,)some_digit = X[0]# 将y转为int型y = y.astype(np.uint8)X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]shuffle_index = np.random.permutation(60000)X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]from sklearn.svm import SVCsvm_clf = SVC()svm_clf.fit(X_train, y_train)svm_clf.predict([some_digit])# 预测结果为5。
虽然我们直接使用的是y_train,而不是y_train_5,但是内部实际上训练了45个训练器二元分类器——即OvO。可以利用decision_function查看。
some_digit_scores = svm_clf.decision_function([some_digit])max_score_locat = np.argmax(some_digit_scores)print(svm_clf.classes_[max_score_locat])

分数最高对应的是标签5的位置。
在训练分类器的时候,目标类的列表存在classes属性中,按照大小排序。尽管classes每个类的索引刚好对应了类本身,但是不会这么凑巧。
如果强制Scikit-learn使用OvO或者OvR,可以使用 OneVsOneClassifier或OneVsRestClassifier类。只需创建一个实例,然后将分类器传给构造函数(它甚至都不必是一个二元分类器)。例如,下面通过OvR,基于SVC构建了一个多类分类器。
from sklearn.multiclass import OneVsRestClassifierovr_clf = OneVsRestClassifier(SVC())ovr_clf.fit(X_train, y_train)ovr_clf.predict([some_digit])len(ovr_clf.estimators_)# 结果为10
同样,SGD和RandomForest也能够这样使用。
补充
有些算法仅仅适用于二元分类,例如Logistics Regression / Perceptron / SVM,因此不能够直接被用于多元分类。但实际上,我们能够通过一些方法,将多元分类问题划分为多个二元分类问题,并且为此训练多个模型。OvR和OvO均是这一类算法。
OvR:存在红蓝绿三种颜色,那么OvR划分为三种算法,算法1(红 vs [蓝绿]),算法2(蓝 vs [红绿]),算法3(绿 vs [红蓝])。
from sklearn.linear_model import LogisticRegressionfrom sklearn.multiclass import OneVsRestClassifier# 生成数据集X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, n_redundant=5, n_classes=3, random_state=1)# 方法①logistic_clf = LogisticRegression()ovr_clf = OneVsRestClassifier(logistic_clf)# fit modelovr_clf.fit(X, y)# make predictionsyhat = ovr_clf.predict(X)# 方法②:在LogisticRegression直接指定ovr_logistic_clf = LogisticRegression(multi_class='ovr)ovr_logistic_clf.fit(X, y)yhat = ovr_logistic_clf.predict(X)
OvO:划分为3种组合,算法1(红vs蓝),算法2(红vs绿),算法3(蓝vs绿)
from sklearn.svm import SVC# 方法①svm_clf = SVC(decision_function_shape='ovo')# fit modelsvm_clf.fit(X, y)# make predictionsyhat = svm_clf.predict(X)# 方法②使用OvOfrom sklearn.multiclass import OneVsOneClassifiersvm_clf = SVC()ovo_clf = OneVsOneClassifier(svm_clf)ovo_clf.fit(X, y)yhat = ovo_clf.predict(X)
误差分析
假设已经找到了一个表现较好的模型,希望找到一些方法进行改进。方法之一就是分析错误类型。
首先看混淆矩阵。注意,python输出的混淆矩阵,行是真实情况,列是预测情况。
from sklearn.model_selection import cross_val_predictfrom sklearn.metrics import confusion_matrixfrom sklearn.linear_model import SGDClassifierfrom sklearn.preprocessing import StandardScaler# 通过缩放,将准确率提升。scaler = StandardScaler()X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))sgd_clf = SGDClassifier(random_state=42)sgd_clf.fit(X_train, y_train)y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)conf_mx = confusion_matrix(y_train, y_train_pred)# 通过matshow查看混淆矩阵的图像import matplotlib.pyplot as pltimport matplotlibplt.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。因此错误不对称。通过混淆矩阵调整错误:我们需要花力气改进“别的数字被误认为8”的错误。
多标签分类
当一个实例输出多个标签时。例如,输出标签y=[1,0,1]。
from sklearn.neighbors import KNeighborsClassifier# 生成y_train_large和y_train_odd,形状均为(60000,)y_train_large = (y_train >= 7)y_train_odd = (y_train % 2 == 1)# 拼接为2维,形状为(60000, 2)y_multilabel = np.c_[y_train_large, y_train_odd]# 训练并预测knn_clf = KNeighborsClassifier()knn_clf.fit(X_train, y_multilabel)knn_clf.predict([some_digit])
对于some_digit=5而言,输出结果为[False, True],完全满足<7以及为基数。
评价多标签分类器的方法有很多。方法之一就是度量每个标签的F1分数,然后简单计算平均分数。这里假设全部的标签同样重要,因此进行macro等权。
from sklearn.model_selection import cross_val_predictfrom sklearn.metrics import precision_score, recall_score, f1_scorey_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3)f1_score(y_multilabel, y_train_knn_pred, average='macro')
但如果有些标签更加重要,应该通过 average=”weighted”评估。
- 关于np.c_函数是将两个矩阵按照列叠加,因此要求行数相同。
例如,x形状是(100, ),y形状是(100, ),通过np.c[x, y]得到的新变量z形状是(100, 2)

例如,x形状是(3,2),y形状是(3,10),那么通过np.c_[x, y]得到的形状是(3, 102)。
