scikit-learn工具包中分類模型predict_proba、predict、decision_function用法詳解

 

在使用sklearn訓練完分類模型後,下一步就是要驗證一下模型的預測結果,對於分類模型,sklearn中通常提供了predict_proba、predict、decision_function三種方法來展示模型對於輸入樣本的評判結果。

說明一下,在sklearn中,對於訓練好的分類模型,模型都有一個classes_屬性,classes_屬性中按順序保存着訓練樣本的類別標記。下面是使用Logistic Regression分類器在爲例,展示一下分類器的classes_屬性。

1、先看一下樣本標籤從0開始的場景下訓練分類模型

import numpy as np
from sklearn.linear_model import LogisticRegression

x = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([2, 2, 3, 3, 0, 0, 1, 1])

clf = LogisticRegression()
clf.fit(x, y)
print(clf.classes_)

"""
    輸出結果:[0 1 2 3]
"""

2、下面看一下樣本標籤不是從0開始的場景下訓練分類模型

import numpy as np
from sklearn.linear_model import LogisticRegression

x = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([6, 6, 2, 2, 4, 4, 8, 8])

clf = LogisticRegression()
clf.fit(x, y)
print(clf.classes_)

"""
    輸出結果:[2 4 6 8]
"""

注意觀察上述兩種情況下classes_屬性的輸出結果,該輸出結果的順序就對應後續要說predict_proba、predict、decision_function輸出結果的順序或順序組合。


在瞭解了分類模型classes_的標籤順序之後,下面看一下分類模型predict_proba、predict、decision_function三種函數輸出結果的含義,以及他們之間的相關性。

1、predict_proba:  模型預測輸入樣本屬於每種類別的概率,概率和爲1,每個位置的概率分別對應classes_中對應位置的類別標籤。以上述類別標籤爲[2 4 6 8]的那個分類器爲例,查看一下分類模型預測的概率。

輸入的[-1, -1]剛好是訓練分類器時使用的數據,訓練數據中[-1, -1]屬於類別6,在predict_proba輸出概率中,最大概率值出現在第三個位置上,第三個位置對應的classes_類別剛好也是類別6。這也就是說,predict_proba輸出概率最大值索引位置對應的classes_元素就是樣本所屬的類別。下面就來看一下predict的預測結果與predict_proba的預測結果是否一致。

2、predict: 模型預測輸入樣本所屬的類別,是則輸出1,不是則輸出0。

在上一步中知道了predict_proba是輸出樣本屬於各個類別的概率,且取概率最大的類別作爲樣本的預測結果,下面看一下predict的預測結果與predict_proba的最大值是否一致。

predict的預測結果爲類別6,對應於classes_中的第三個元素,也同時對應於predict_proba中的第三個元素,且是概率值最大的元素。

對於分類模型來說,通常知道模型的預測結果predict和預測概率predict_proba就可以了,那分類模型中的decision_function是幹什麼的呢?

3、decision_function:  幫助文檔中給出的解釋是“The confidence score for a sample is the signed distance of that sample to the hyperplane.”。意思就是使用樣本到分隔超平面的有符號距離來度量預測結果的置信度,反正我是有點懵逼。放大招,靈魂三問。他是誰?他從哪裏來?他到哪裏去?

他是誰?

看一下支持向量機SVM中關於decision_function的解釋是怎樣的?

說了兩件事情,其一是說評估樣本X的的decision_function(等於沒說,哈哈哈),其二是說,如果decision_dunction_shape='ovr',則輸出的decison_function形狀是(n_samples, n_classes), n_samples是輸入樣本的數量,n_classes是訓練樣本的類別數。這裏再補充一點,如果decision_dunction_shape='ovo,則輸出的decison_function形狀是(n_samples, n_classes * (n_classes - 1) / 2)。‘ovr’和‘ovo’又是啥?莫急,莫急。暫且知道是用於訓練多分類的就行。

大致解釋下decison_function就是用來衡量帶預測樣本到分類模型各個分隔超平面的距離(沒找到太直觀的解釋方法)。

他從哪裏來?

據說這傢伙來自遙遠的SVM星球。上面說這哥們能和分隔超平面扯上關係,熟悉SVM的會知道,SVM中通過通過支持向量來選擇分隔超平面,分隔超平面將訓練樣本分爲正反兩派,支持向量的作用就是使得選擇的分隔超平面離兩邊的類別都比較遠,這樣模型具有更強的健壯性。

他到哪裏去?

說了半天,decison_function這玩意到底有啥用?莫急,莫急。下面先說一下上面提到的'ovr'和'ovo'分別是什麼東東?

我們常見的分類器,比如LR和SVM都是隻能支持二分類的,回想一下LR分類器,通過判斷線性模型的預測結果是否大於0,進而判斷sigmoid的輸出結果是否大於0.5來判斷模型食慾正類還是負類。SVM也一樣,前面講了,SVM通過分隔超平面將樣本分到兩邊去,也就是進行二分類。那麼怎麼能將二分類的分類算法應用到多分類任務上去呢?這就是‘ovr’和‘ovo’要解決的問題。

'ovr':全稱是One-vs-Rest。就是一個人和對面一羣人幹一次架(羣毆)。假如我們訓練數據中包含[0, 1, 2, 3]四個分類,那麼分別將0, 1, 2, 3作爲正樣本,其餘的123, 023, 013, 012作爲負樣本,訓練4個分類器,每個分類器預測的結果表示屬於對應正類也就是0, 1, 2, 3 的概率。這樣對於一個輸入樣本就相當於跑進行4個二分類,然後取輸出結果最大的數值對應的classes_類別。

‘ovo’:全稱是One-vs-One。就是一個人分別和對面的每個人幹一次架(單挑,車輪戰術)。同樣,假如我們訓練數據中包含[0, 1, 2, 3]四個分類,先將類別0作爲正樣本,類別1,類別2,類別3一次作爲負樣本訓練3個分類器,然後以類別1爲正樣本,類別0,類別2, 類別3位負樣本訓練3個分類器,以此類推。由於類別0爲正樣本,類別1爲負樣本和類別1爲正樣本、類別0爲負樣本實質上是一樣的,所以不需要重複訓練。

通過上面的描述可知,加入訓練樣本有n_classes個類別,則'ovr'模式需要訓練n_classes個分類器,‘ovo’模式需要訓練n_classes * (n_classes - 1) / 2 個分類器。那麼問題來了,有多少個分類器是不是就得有多少個分隔超平面,有多少個分隔超平面是不是就得有多少個decision_function值。這也就對應了“他是誰?”那部分所說的decison_function輸出形狀的描述。

下面進入正題,來看一下decision_function的真面目。

1、二分類的decison_function

二分類模型中,decision_function返回的數組形狀等於樣本個數,也就是一個樣本返回一個decision_function值。並且,此時的decision_function_shape參數失效 ,因爲只需要訓練一個分類器就行了,就不存在是單挑還是羣毆的問題了。下面以SVM二分類的實例來看一下結果:

import numpy as np
from sklearn.svm import SVC

x = np.array([[1,2,3],
                    [1,3,4],
                    [2,1,2],
                    [4,5,6],
                    [3,5,3],
                    [1,7,2]])
 
y = np.array([3, 3, 3, 2, 2, 2])

clf = SVC(probability=True)
clf.fit(x, y)
print(clf.decision_function(x))

# 返回array([2, 3]),其中2爲negetive,3爲positive
print(clf.classes_)

在二分類的情況下,分類模型的decision_function返回結果的形狀與樣本數量相同,且返回結果的數值表示模型預測樣本屬於positive正樣本的可信度。並且,二分類情況下classes_中的第一個標籤代表是負樣本,第二個標籤代表正樣本。

模型在訓練集上的decision_function以及predict_procaba、predict結果如下:

還記得前面講過的decision_function是有符號的吧,大於0表示正樣本的可信度大於負樣本,否則可信度小於負樣本。所以對於前3個樣本,decison_function都認爲是正樣本的可信度高,後3個樣本是負樣本的可信度高。那麼再看一下predict的結果,前3個預測爲正樣本3(ps:二分類情況下正樣本對應的是classes_中的第二個類別),後3個樣本預測爲負樣本2。再看一下predict_proba預測的樣本所屬的類別概率,可以看到前3個樣本屬於類別3的概率更大,後3個樣本屬於類別2的概率更大。

2、多分類的decision_function

多分類模型中,decision_function返回的數組形狀依據使用的模式是‘ovr’還是‘ovo’而分別返回n_classes個和n_classes * (n_classes - 1) / 2個數值。下面以SVM多分類的實例來看一下結果:

One-vs-Rest多分類實例:

import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
X = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([2, 2, 3, 3, 0, 0, 1, 1])
# SVC多分類模型默認採用ovr模式
clf = SVC(probability=True, decision_function_shape="ovr")
clf.fit(X, y)

# 計算樣本距離每個分類邊界的距離
# One-vs-One 按照decision_function的得分[01, 02, 03, 12, 13, 23]判斷每個分類器的分類結果,然後進行投票
# One-vs-Rest 選擇decision_function的得分[0-Rest, 1-Rest, 2-Rest, 3-Rest]最大的作爲分類結果
print("decision_function:\n", clf.decision_function(X))
# precidt預測樣本對應的標籤類別
print("predict:\n", clf.predict(X))
# predict_proba 預測樣本對應各個類別的概率
print("predict_proba:\n", clf.predict_proba(X)) #這個是得分,每個分類器的得分,取最大得分對應的類。
print("classes_:", clf.classes_)

模型在訓練集上的decision_function以及predict_procaba、predict結果如下:

在ovr場景下,decision_function輸出的最大值對應的正樣本類別就是decision_function認爲置信度最高的預測類別。下面看一下One-vs-One場景下的多分類。

One-vs-One多分類實例:

import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
X = np.array(
    [
        [-1, -1],
        [-2, -1],
        [1, 1],
        [2, 1],
        [-1, 1],
        [-1, 2],
        [1, -1],
        [1, -2]
    ]
)
y = np.array([2, 2, 3, 3, 0, 0, 1, 1])
# SVC多分類模型默認採用ovr模式
clf = SVC(probability=True, decision_function_shape="ovo")
clf.fit(X, y)

# 計算樣本距離每個分類邊界的距離
# One-vs-One 按照decision_function的得分[01, 02, 03, 12, 13, 23]判斷每個分類器的分類結果,然後進行投票
# One-vs-Rest 選擇decision_function的得分[0-Rest, 1-Rest, 2-Rest, 3-Rest]最大的作爲分類結果
print("decision_function:\n", clf.decision_function(X))
# precidt預測樣本對應的標籤類別
print("predict:\n", clf.predict(X))
# predict_proba 預測樣本對應各個類別的概率
print("predict_proba:\n", clf.predict_proba(X)) #這個是得分,每個分類器的得分,取最大得分對應的類。
print("classes_:", clf.classes_)

模型在訓練集上的decision_function以及predict_procaba、predict結果如下:

ovo模式下,4個類別的訓練數據,需要訓練6個二分類器,得到6個decition_function值,依照classes_的類別順序,6個二分類器分別是[01, 02, 03, 12, 13, 23],前面的數字表示正類,後面的表示負類。以decision_function的第一行輸出結果爲例:

-0.07609727 對應 01分類器,且數值小於0,則分類結果爲後者,即類別1
-1.00023294  對應 02分類器,且數值小於0,則分類結果爲後者,即類別2
0.27849207  對應 03分類器,且數值大於0,則分類結果爲後者,即類別0
-0.834258626  對應 12分類器,且數值大於0,則分類結果爲後者,即類別2
0.24756982  對應 13分類器,且數值大於0,則分類結果爲後者,即類別1
1.00006256 對應 23分類器,且數值小於0,則分類結果爲後者,即類別2

最終得票數:{類別0: 1, 類別1: 2, 類別2: 3, 類別3: 0}
對以上分類結果voting投票,多數獲勝,即最終分類結果爲類別2。

通過上面講的這些大概也能得出decision_function、predict_procaba、predict之間的聯繫了:

decision_function:輸出樣本距離各個分類器的分隔超平面的置信度,並由此可以推算出predict的預測結果

predict_procaba:輸出樣本屬於各個類別的概率值,並由此可以推算出predict的預測結果

predict:輸出樣本屬於具體類別的預測結果

怎麼用?

說了這麼多,也知道decision_function的具體含義了,那麼使用decison_function可以幹什麼呢?(沒用說個毛線)

還是以SVM分類器爲例,SVM分類器有個參數用來控制是否輸出預測樣本的概率值,probability=True時SVM分類器具有predict_proba函數,可以輸出樣本的預測概率,但是當probability=False,SVM分類器沒有predict_proba函數,也就沒辦法得到樣本預測結果的置信度(簡單理解爲概率)。但是我們又知道,當我們想要計算分類器的性能時,常常需要用到ROC和AUC,ROC曲線表示分類器預測結果FPR和TPR的變化趨勢,AUC表示ROC曲線以下的面積。也就是說,要想得到ROC和AUC,就需要得到一組FPR和TPR,FPR和TPR的計算通常是基於一組樣本的預測置信度,分別選擇不同的置信度閾值,得到一組FPR和TPR值,然後得到ROC曲線的。現在沒有predict_proba就得不到樣本預測的置信度。But,還記得我們前面解釋decison_function時說過的,decision_function表示通過度量樣本距離分隔超平面距離的來表示置信度。那麼我們是不是可以使用decision_function的置信度來計算ROC呢?答案當然是可以的啦。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
from sklearn.metrics import roc_curve, roc_auc_score, auc, plot_roc_curve
from sklearn.multiclass import OneVsOneClassifier, OneVsRestClassifier
from sklearn.preprocessing import label_binarize
from sklearn import datasets
from sklearn.model_selection import train_test_split
np.random.seed(100)

# 加載iris數據集
iris = datasets.load_iris()
X = iris.data
y = iris.target
print(X.shape, y.shape)
n_samples, n_features = X.shape

# iris數據集加入噪聲,使得ROC不是那麼完美
X = np.c_[X, np.random.randn(n_samples, 50 * n_features)]
# y = label_binarize(y, classes=[0, 1, 2])
# n_classes = y.shape[1]
# 訓練樣本的類別數量
n_classes = 3

# 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.5, random_state=0)
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)

# 使用One-vs-One模式訓練SVM分類器
clf = OneVsRestClassifier(SVC(kernel="linear"))
clf.fit(X_train, y_train)

# 計算分類器在測試集上的決策值
y_scores = clf.decision_function(X_test)
print(y_scores.shape)

# 繪製每個類別的ROC曲線
fig, axes = plt.subplots(2, 2, figsize=(8, 8))
colors = ["r", "g", "b", "k"]
markers = ["o", "^", "v", "+"]

y_test = label_binarize(y_test, classes=clf.classes_)
for i in range(n_classes):
    # 計算每個類別的FPR, TPR 
    fpr, tpr, thr = roc_curve(y_test[:, i], y_scores[:, i])
#     print("classes_{}, fpr: {}, tpr: {}, threshold: {}".format(i, fpr, tpr, thr))
    # 繪製ROC曲線,並計算AUC值
    axes[int(i / 2), i % 2].plot(fpr, tpr, color=colors[i], marker=markers[i], label="AUC: {:.2f}".format(auc(fpr, tpr)))
    axes[int(i / 2), i % 2].set_xlabel("FPR")
    axes[int(i / 2), i % 2].set_ylabel("TPR")
    axes[int(i / 2), i % 2].set_title("Class_{}".format(clf.classes_[i]))
    axes[int(i / 2), i % 2].legend(loc="lower right")

print("AUC:", roc_auc_score(y_test, clf.decision_function(X_test), multi_class="ovo"))

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章