多元分類
之前介紹過二元分類器,可以將數據分成兩個類別(例如“數字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%以上。在訓練完模型后,根據機器學習項目流程,下一步我們會嘗試去優化模型,其中一個方法就是誤差分析,也是接下來介紹的內容。