1、什麼是多分類?
參考:https://www.jianshu.com/p/9332fcfbd197
針對多類問題的分類中,具體講有兩種,即multiclass classification和multilabel classification。multiclass是指分類任務中包含不止一個類别時,每條資料僅僅對應其中一個類别,不會對應多個類别。multilabel是指分類任務中不止一個分類時,每條資料可能對應不止一個類别标簽,例如一條新聞,可以被劃分到多個闆塊。
無論是multiclass,還是multilabel,做分類時都有兩種政策,一個是one-vs-the-rest(one-vs-all),一個是one-vs-one。
在one-vs-all政策中,假設有n個類别,那麼就會建立n個二項分類器,每個分類器針對其中一個類别和剩餘類别進行分類。進行預測時,利用這n個二項分類器進行分類,得到資料屬于目前類的機率,選擇其中機率最大的一個類别作為最終的預測結果。
在one-vs-one政策中,同樣假設有n個類别,則會針對兩兩類别建立二項分類器,得到k=n*(n-1)/2個分類器。對新資料進行分類時,依次使用這k個分類器進行分類,每次分類相當于一次投票,分類結果是哪個就相當于對哪個類投了一票。在使用全部k個分類器進行分類後,相當于進行了k次投票,選擇得票最多的那個類作為最終分類結果。
在scikit-learn架構中,分别有sklearn.multiclass.OneVsRestClassifier和sklearn.multiclass.OneVsOneClassifier完成兩種政策,使用過程中要指明使用的二項分類器是什麼。另外在進行mutillabel分類時,訓練資料的類别标簽Y應該是一個矩陣,第[i,j]個元素指明了第j個類别标簽是否出現在第i個樣本資料中。例如,np.array([[1, 0, 0], [0, 1, 1], [0, 0, 0]]),這樣的一條資料,指明針對第一條樣本資料,類别标簽是第0個類,第二條資料,類别标簽是第1,第2個類,第三條資料,沒有類别标簽。有時訓練資料中,類别标簽Y可能不是這樣的可是,而是類似[[2, 3, 4], [2], [0, 1, 3], [0, 1, 2, 3, 4], [0, 1, 2]]這樣的格式,每條資料指明了每條樣本資料對應的類标号。這就需要将Y轉換成矩陣的形式,sklearn.preprocessing.MultiLabelBinarizer提供了這個功能。
2、建構多個二分類器進行分類
使用的資料集是sklearn自帶的iris資料集,該資料集總共有三類。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm,datasets
from itertools import cycle
from sklearn import svm, datasets
from sklearn.metrics import roc_curve, auc
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import label_binarize
from sklearn.multiclass import OneVsRestClassifier
from scipy import interp
# 導入鸢尾花資料集
iris = datasets.load_iris()
X = iris.data # X.shape==(150, 4)
y = iris.target # y.shape==(150, )
# 二進制化輸出
y = label_binarize(y, classes=[0, 1, 2]) # shape==(150, 3)
n_classes = y.shape[1] # n_classes==3
#np.r_是按列連接配接兩個矩陣,就是把兩矩陣上下相加,要求列數相等。
#np.c_是按行連接配接兩個矩陣,就是把兩矩陣左右相加,要求行數相等。
# 添加噪音特征,使問題更困難
random_state = np.random.RandomState(0)
n_samples, n_features = X.shape # n_samples==150, n_features==4
X = np.c_[X, random_state.randn(n_samples, 200 * n_features)] # shape==(150, 84)
複制
# 打亂資料集并切分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.5,
random_state=0)
# X_train.shape==(75, 804), X_test.shape==(75, 804), y_train.shape==(75, 3), y_test.shape==(75, 3)
# 學習區分某個類與其他的類
classifier = OneVsRestClassifier(svm.SVC(kernel='linear', probability=True,
random_state=random_state))
y_score = classifier.fit(X_train, y_train).decision_function(X_test)
複制
這裡提一下classifier.fit()後面接的函數:可以是decision_function()、predict_proba()、predict()
predict():傳回預測标簽、
predict_proba():傳回預測屬于某标簽的機率
decision_function():傳回樣本到分隔超平面的有符号距離來度量預測結果的置信度
這裡我們分别列印一下對應的y_score,隻取前三條資料:
預測标簽:[[0 0 1] [0 1 0] [1 0 0]...]
機率:[[6.96010030e-03 1.67062907e-01 9.65745632e-01] [4.57532814e-02 3.05231268e-01 4.58939259e-01] [7.00832624e-01 2.32537226e-01 4.92996070e-02]...]
距離:[[-1.18047012 -2.60334173 1.48134717] [-0.72354789 0.15798952 -0.08648247] [ 0.22439326 -1.15044791 -1.35488445]...]
同時,我們還要注意使用到了:OneVsRestClassifier,如何了解呢?
我們可以這麼看:OneVsRestClassifier實際上包含了多個分類器,有多少個類别就有多少個分類器,這裡有三個類别,是以就有三個分類器,可以通過:
print(classifier.estimators_)
複制
來檢視:
[SVC(C=1.0, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='scale', kernel='linear',
max_iter=-1, probability=True,
random_state=RandomState(MT19937) at 0x7F480F316A98, shrinking=True,
tol=0.001, verbose=False),
SVC(C=1.0, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='scale', kernel='linear',
max_iter=-1, probability=True,
random_state=RandomState(MT19937) at 0x7F480F316CA8, shrinking=True,
tol=0.001, verbose=False),
SVC(C=1.0, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='scale', kernel='linear',
max_iter=-1, probability=True,
random_state=RandomState(MT19937) at 0x7F480F316DB0, shrinking=True,
tol=0.001, verbose=False)]
複制
對于每一個分類器,都是二分類,即将目前的類視為一類,另外的其他類視為一類,比如說我們可以取得其中的分類器進行分類,以第一個标簽為例:
y_true=np.where(y_test==1)[1]
複制
array([2, 1, 0, 2, 0, 2, 0, 1, 1, 1, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 2, 1, 0, 0, 2, 0, 0, 1, 1, 0, 2, 1, 0, 2, 2, 1, 0, 1, 1, 1, 2, 0, 2, 0, 0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 2, 2, 2, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 0, 0, 2, 1, 0, 0, 1])
#這裡重新定義标簽,1代表目前标簽,0代表其他标簽
y0=[0 if i==0 else 1 for i in y_true]
print(y0)
print(classifier.estimators_[0].fit(X_train,y0).predict(X_test))
複制
[0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0]
[0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 0 1 1 1 1 0 0 0 0 0 0 1 0 1 0 1 0 1 0 0 0 1 0 1 0 1 0 0 1 0 1 0 0]
我們直接列印y_score中第0列的結果y_score[:,0]:
[0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 1 1 0 1 1 0 0 1 0 0 1 0 0 0 1 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 1 0]
就是對應的第一個分類器的結果。(這裡的結果不一緻是因為classifier.estimators_[0].fit(X_train,y0).predict(X_test)相當于有重新訓練并預測了一次。
進而,y_score中的每一列都表示了每一個分類器的結果。
是以,在y_score的結果中出現了:[1,1,0]這種就不足為怪了。但是有個問題,如果其中有兩個分類器都将某個類認為是目前類,那麼這類到底屬于哪一個類呢?是以不能直接就對每一個分類器的機率值取得标簽值,而是要計算出每一個分類器的機率值,最後再進行映射成标簽。回過頭來才發現的,以下使用的是predict(),是以是有問題的,但是基本方式是差不多的,再修改就有點麻煩了,酌情閱讀了= =。
多分類問題就轉換為了oneVsRest問題,可以分别使用二分類評價名額了,可參考:
https://www.cnblogs.com/xiximayou/p/13682052.html
比如說繪制ROC和計算AUC:
from sklearn.metrics import roc_curve, auc
# 為每個類别計算ROC曲線和AUC
fpr = dict()
tpr = dict()
roc_auc = dict()
n_classes=3
for i in range(n_classes):
fpr[i], tpr[i], _ = roc_curve(y_test[:, i], y_score[:, i])
roc_auc[i] = auc(fpr[i], tpr[i])
# fpr[0].shape==tpr[0].shape==(21, ), fpr[1].shape==tpr[1].shape==(35, ), fpr[2].shape==tpr[2].shape==(33, )
# roc_auc {0: 0.9118165784832452, 1: 0.6029629629629629, 2: 0.7859477124183007}
plt.figure()
lw = 2
for i in range(n_classes):
plt.plot(fpr[i], tpr[i], color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc[i])
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
複制

3、多分類評價名額?
宏平均 Macro-average
Macro F1:将n分類的評價拆成n個二分類的評價,計算每個二分類的F1 score,n個F1 score的平均值即為Macro F1。
微平均 Micro-average
Micro F1:将n分類的評價拆成n個二分類的評價,将n個二分類評價的TP、FP、TN、FN對應相加,計算評價準确率和召回率,由這2個準确率和召回率計算的F1 score即為Micro F1。
對于二分類問題:
TP=cnf_matrix[1][1] #預測為正的真實标簽為正
FP=cnf_matrix[0][1] #預測為正的真實标簽為負
FN=cnf_matrix[1][0] #預測為負的真實标簽為正
TN=cnf_matrix[0][0] #預測為負的真實标簽為負
accuracy=(TP+TN)/(TP+FP+FN+TN)
precision=TP/(TP+FP)
recall=TP/(TP+FN)
f1score=2 * precision * recall/(precision + recall)
複制
ROC曲線:
橫坐标:假正率(False positive rate, FPR),預測為正但實際為負的樣本占所有負例樣本的比例;
FPR = FP / ( FP +TN)
縱坐标:真正率(True positive rate, TPR),這個其實就是召回率,預測為正且實際為正的樣本占所有正例樣本的比例。
TPR = TP / ( TP+ FN)
AUC:就是roc曲線和橫坐标圍城的面積。
對于上述的oneVsRest:
# 計算微平均ROC曲線和AUC
fpr["micro"], tpr["micro"], _ = roc_curve(y_test.ravel(), y_score.ravel())
roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])
# 計算宏平均ROC曲線和AUC
# 首先彙總所有FPR
all_fpr = np.unique(np.concatenate([fpr[i] for i in range(n_classes)]))
# 然後再用這些點對ROC曲線進行插值
mean_tpr = np.zeros_like(all_fpr)
for i in range(n_classes):
mean_tpr += interp(all_fpr, fpr[i], tpr[i])
# 最後求平均并計算AUC
mean_tpr /= n_classes
fpr["macro"] = all_fpr
tpr["macro"] = mean_tpr
roc_auc["macro"] = auc(fpr["macro"], tpr["macro"])
# 繪制所有ROC曲線
plt.figure()
lw = 2
plt.plot(fpr["micro"], tpr["micro"],
label='micro-average ROC curve (area = {0:0.2f})'
''.format(roc_auc["micro"]),
color='deeppink', linestyle=':', linewidth=4)
plt.plot(fpr["macro"], tpr["macro"],
label='macro-average ROC curve (area = {0:0.2f})'
''.format(roc_auc["macro"]),
color='navy', linestyle=':', linewidth=4)
colors = cycle(['aqua', 'darkorange', 'cornflowerblue'])
for i, color in zip(range(n_classes), colors):
plt.plot(fpr[i], tpr[i], color=color, lw=lw,
label='ROC curve of class {0} (area = {1:0.2f})'
''.format(i, roc_auc[i]))
plt.plot([0, 1], [0, 1], 'k--', lw=lw)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Some extension of Receiver operating characteristic to multi-class')
plt.legend(loc="lower right")
plt.show()
複制
接下來我們将分類視為一個整體:
from sklearn.metrics import confusion_matrix
classes=[0,1,2]
y_my_test=np.where(y_test==1)[1]
y_my_score=np.zeros(y_my_test.shape)
for i in range(len(classes)):
y_my_score[np.where(y_score[:,i]==1)]=i
confusion = confusion_matrix(y_my_test, y_my_score)# 繪制熱度圖
plt.imshow(confusion, cmap=plt.cm.Greens)
indices = range(len(confusion))
plt.xticks(indices, classes)
plt.yticks(indices, classes)
plt.colorbar()
plt.xlabel('y_pred')
plt.ylabel('y_true')
# 顯示資料
for first_index in range(len(confusion)):
for second_index in range(len(confusion[first_index])):
plt.text(first_index, second_index, confusion[first_index][second_index])
# 顯示圖檔
plt.show()
複制
我們首先要将測試标簽和預測标簽轉換為非One-hot編碼,才能計算出混淆矩陣:
計算出每一類的評價名額:
from sklearn.metrics import classification_report
t = classification_report(y_my_test, y_my_score, target_names=['0', '1', '2'])
複制
precision recall f1-score support
0 0.52 0.71 0.60 21
1 0.60 0.40 0.48 30
2 0.73 0.79 0.76 24
accuracy 0.61 75
macro avg 0.62 0.64 0.61 75
weighted avg 0.62 0.61 0.60 75
複制
如果要使用上述的值,需要這麼使用:
t = classification_report(y_my_test, y_my_score, target_names=['0', '1', '2'],output_dict=True)
複制
{'0': {'precision': 0.5172413793103449, 'recall': 0.7142857142857143, 'f1-score': 0.6000000000000001, 'support': 21}, '1': {'precision': 0.6, 'recall': 0.4, 'f1-score': 0.48, 'support': 30}, '2': {'precision': 0.7307692307692307, 'recall': 0.7916666666666666, 'f1-score': 0.76, 'support': 24}, 'accuracy': 0.6133333333333333, 'macro avg': {'precision': 0.6160035366931919, 'recall': 0.6353174603174603, 'f1-score': 0.6133333333333334, 'support': 75}, 'weighted avg': {'precision': 0.6186737400530504, 'recall': 0.6133333333333333, 'f1-score': 0.6032000000000001, 'support': 75}}
我們可以分别計算每一類的相關名額:
import sklearn
for i in range(len(classes)):
precision=sklearn.metrics.precision_score(y_test[:,i], y_score[:,i], labels=None, pos_label=1, average='binary',
sample_weight=None)
print("{} precision:{}".format(i,precision))
複制
也可以整體計算:
from sklearn.metrics import precision_score
print(precision_score(y_test, y_score, average="micro"))
複制
average可選參數micro、macro、weighted
具體的計算方式可以去參考:
https://zhuanlan.zhihu.com/p/59862986
參考:
https://blog.csdn.net/hfutdog/article/details/88079934
https://blog.csdn.net/wf592523813/article/details/95202448
https://blog.csdn.net/vivian_ll/article/details/99627094