多元分类器适合区分两个以上的类别。

OvO(一对一)和OvR(一对多)

为了创建一个分类器,将图片分为10类(0到9),

  • 一种方法是训练10个二元分类器——“0-检测器”、“1检测器”一直到“9-检测器”;然后使用10个分类器分别对第i个图进行检测,取出其中得分最高的分类;称为一对剩余(OvR)策略,也称为一对多(one-vs-all)。
  • 另一种方法,为每一对数字训练一个二元分类器,一个区分0和1,一个区分0和2,以此类推,称为一对一策略(OvO),如果存在N个类别,那么一共需要训练No.003 多元分类器 - 图1个分类器。

对于数据量扩大而导致表现糟糕的时候,使用OvO更合适,例如SVC;对于大部分二元分类器,OvR更合适。Scikit-learn能够检测到使用二元分类器进行多类分类任务,从而根据情况自动运行OvR或者OvO。

  1. from sklearn.datasets import fetch_openml
  2. import numpy as np
  3. # 读取数据
  4. mnist = fetch_openml('mnist_784')
  5. # mnist中存在手写体数据。
  6. X, y = mnist['data'], mnist['target']
  7. print(X.shape, y.shape)
  8. # (70000, 784) (70000,)
  9. some_digit = X[0]
  10. # 将y转为int型
  11. y = y.astype(np.uint8)
  12. X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
  13. shuffle_index = np.random.permutation(60000)
  14. X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]
  15. from sklearn.svm import SVC
  16. svm_clf = SVC()
  17. svm_clf.fit(X_train, y_train)
  18. svm_clf.predict([some_digit])
  19. # 预测结果为5。

虽然我们直接使用的是y_train,而不是y_train_5,但是内部实际上训练了45个训练器二元分类器——即OvO。可以利用decision_function查看。

  1. some_digit_scores = svm_clf.decision_function([some_digit])
  2. max_score_locat = np.argmax(some_digit_scores)
  3. print(svm_clf.classes_[max_score_locat])

image.png
分数最高对应的是标签5的位置。
在训练分类器的时候,目标类的列表存在classes属性中,按照大小排序。尽管classes每个类的索引刚好对应了类本身,但是不会这么凑巧。
如果强制Scikit-learn使用OvO或者OvR,可以使用 OneVsOneClassifier或OneVsRestClassifier类。只需创建一个实例,然后将分类器传给构造函数(它甚至都不必是一个二元分类器)。例如,下面通过OvR,基于SVC构建了一个多类分类器。

  1. from sklearn.multiclass import OneVsRestClassifier
  2. ovr_clf = OneVsRestClassifier(SVC())
  3. ovr_clf.fit(X_train, y_train)
  4. ovr_clf.predict([some_digit])
  5. len(ovr_clf.estimators_)
  6. # 结果为10

同样,SGD和RandomForest也能够这样使用。

补充
有些算法仅仅适用于二元分类,例如Logistics Regression / Perceptron / SVM,因此不能够直接被用于多元分类。但实际上,我们能够通过一些方法,将多元分类问题划分为多个二元分类问题,并且为此训练多个模型。OvR和OvO均是这一类算法。
OvR:存在红蓝绿三种颜色,那么OvR划分为三种算法,算法1(红 vs [蓝绿]),算法2(蓝 vs [红绿]),算法3(绿 vs [红蓝])。

  1. from sklearn.linear_model import LogisticRegression
  2. from sklearn.multiclass import OneVsRestClassifier
  3. # 生成数据集
  4. X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, n_redundant=5, n_classes=3, random_state=1)
  5. # 方法①
  6. logistic_clf = LogisticRegression()
  7. ovr_clf = OneVsRestClassifier(logistic_clf)
  8. # fit model
  9. ovr_clf.fit(X, y)
  10. # make predictions
  11. yhat = ovr_clf.predict(X)
  12. # 方法②:在LogisticRegression直接指定
  13. ovr_logistic_clf = LogisticRegression(multi_class='ovr)
  14. ovr_logistic_clf.fit(X, y)
  15. yhat = ovr_logistic_clf.predict(X)

OvO:划分为3种组合,算法1(红vs蓝),算法2(红vs绿),算法3(蓝vs绿)

  1. from sklearn.svm import SVC
  2. # 方法①
  3. svm_clf = SVC(decision_function_shape='ovo')
  4. # fit model
  5. svm_clf.fit(X, y)
  6. # make predictions
  7. yhat = svm_clf.predict(X)
  8. # 方法②使用OvO
  9. from sklearn.multiclass import OneVsOneClassifier
  10. svm_clf = SVC()
  11. ovo_clf = OneVsOneClassifier(svm_clf)
  12. ovo_clf.fit(X, y)
  13. yhat = ovo_clf.predict(X)

误差分析

假设已经找到了一个表现较好的模型,希望找到一些方法进行改进。方法之一就是分析错误类型
首先看混淆矩阵。注意,python输出的混淆矩阵,行是真实情况,列是预测情况

  1. from sklearn.model_selection import cross_val_predict
  2. from sklearn.metrics import confusion_matrix
  3. from sklearn.linear_model import SGDClassifier
  4. from sklearn.preprocessing import StandardScaler
  5. # 通过缩放,将准确率提升。
  6. scaler = StandardScaler()
  7. X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))
  8. sgd_clf = SGDClassifier(random_state=42)
  9. sgd_clf.fit(X_train, y_train)
  10. y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
  11. conf_mx = confusion_matrix(y_train, y_train_pred)
  12. # 通过matshow查看混淆矩阵的图像
  13. import matplotlib.pyplot as plt
  14. import matplotlib
  15. plt.matshow(conf_mx, cmap=plt.cm.gray)
  16. plt.show()

image.png
根据图像,最亮的区域都在主对角线上,说明大部分分类正确。但是数字5对角线位置颜色较暗,有可能是分类器在数字5上的执行效果不如其他数字,有可能是5的图片较少。
首先,将混淆矩阵中每个值除以相应类别的图片数量,从而比较错误率,而不是错误的绝对值。可以看到分类器产生的错误原因。

  1. # 每一行求加总,得到每个真实的实例的数量。
  2. row_sums = conf_mx.sum(axis=1,, keepdims=True)
  3. norm_conf_mx = conf_mx / row_sums
  4. # 将对角线强制填充0,否则还对角线太亮了。。
  5. np.fill_diagonal(norm_conf_mx, 0)
  6. plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
  7. plt.show()

image.png
图中看来,第8列很明亮,因此很多图片被错误分类为8;但是第8行较暗,表示实际的8被正确分为了8。因此错误不对称。通过混淆矩阵调整错误:我们需要花力气改进“别的数字被误认为8”的错误。

多标签分类

当一个实例输出多个标签时。例如,输出标签y=[1,0,1]。

  1. from sklearn.neighbors import KNeighborsClassifier
  2. # 生成y_train_large和y_train_odd,形状均为(60000,)
  3. y_train_large = (y_train >= 7)
  4. y_train_odd = (y_train % 2 == 1)
  5. # 拼接为2维,形状为(60000, 2)
  6. y_multilabel = np.c_[y_train_large, y_train_odd]
  7. # 训练并预测
  8. knn_clf = KNeighborsClassifier()
  9. knn_clf.fit(X_train, y_multilabel)
  10. knn_clf.predict([some_digit])

对于some_digit=5而言,输出结果为[False, True],完全满足<7以及为基数。
评价多标签分类器的方法有很多。方法之一就是度量每个标签的F1分数,然后简单计算平均分数。这里假设全部的标签同样重要,因此进行macro等权。

  1. from sklearn.model_selection import cross_val_predict
  2. from sklearn.metrics import precision_score, recall_score, f1_score
  3. y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3)
  4. 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)
![](https://cdn.nlark.com/yuque/__latex/f5e283fa2148fac52abd58935beaf8e0.svg#card=math&code=%5Cbegin%7Baligned%7D%0A%5Cbegin%7Baligned%7D%0A%5Cleft%28%0A%5Cbegin%7Barray%7D%7B00%7D%0A%26%20z
%7B1%7D%5C%5C%0A%26%20z%7B2%7D%20%5C%5C%0A%26%20%5Cdots%20%5C%5C%0A%26%20z%7B100%7D%5C%5C%0A%5Cend%7Barray%7D%0A%5Cright%29%0A%5Cend%7Baligned%7D%0A%3D%0A%5Cleft%28%0A%5Cbegin%7Barray%7D%7B00%7D%0A%26%20x%7B1%7D%20%26%20y%7B1%7D%20%5C%5C%0A%26%20x%7B2%7D%20%26%20y%7B2%7D%20%5C%5C%0A%26%20%5Cdots%20%26%20%5Cdots%20%5C%5C%0A%26%20x%7B100%7D%20%26%20y%7B100%7D%20%5C%5C%0A%5Cend%7Barray%7D%0A%5Cright%29%0A%5Cend%7Baligned%7D&id=F6Ii8)
例如,x形状是(3,2),y形状是(3,10),那么通过np.c_[x, y]得到的形状是(3, 102)。