Python 讓你一遍記住混淆矩陣及衍生指標

關注微信公共號:小程在線

關注CSDN博客:程志偉的博客

爲了更好的理解下面的話,推薦閱讀 https://blog.csdn.net/c1z2w3456789/article/details/105247565 (PYthon 教你怎麼選擇SVM的核函數kernel及案例分析),也可以直接跳過前一部分,直接閱讀混淆矩陣部分。

有一些數據,可能是線性可分,但在線性可分狀況下訓練準確率不能達到100%,即無法讓訓練誤差爲0,這樣的數
據被我們稱爲“存在軟間隔的數據”。此時此刻,我們需要讓我們決策邊界能夠忍受一小部分訓練誤差,我們就不能
單純地尋求最大邊際了。

因爲對於軟間隔地數據來說,邊際越大被分錯的樣本也就會越多,因此我們需要找出一個”最大邊際“與”被分錯的樣
本數量“之間的平衡。因此,我們引入鬆弛係數 和鬆弛係數的係數C作爲一個懲罰項,來懲罰我們對最大邊際的追求。

所以軟間隔讓決定兩條虛線超平面的支持向量可能是來自於同一個類別的樣本點,而硬間

隔的時候兩條虛線超平面必須是由來自兩個不同類別的支持向量決定的。而C值會決定我們究竟是依賴紅色點作爲

支持向量(只追求最大邊界),還是我們要依賴軟間隔中,混雜在紅色點中的紫色點來作爲支持向量(追求最大邊

界和判斷正確的平衡)。如果C值設定比較大,那SVC可能會選擇邊際較小的,能夠更好地分類所有訓練點的決策

邊界,不過模型的訓練時間也會更長。如果C的設定值較小,那SVC會盡量最大化邊界,儘量將掉落在決策邊界另

一方的樣本點預測正確,決策功能會更簡單,但代價是訓練的準確度,因爲此時會有更多紅色的點被分類錯誤。換

句話說,C在SVM中的影響就像正則化參數對邏輯迴歸的影響。

此時此刻,所有可能影響我們的超平面的樣本可能都會被定義爲支持向量,所以支持向量就不再是所有壓在虛線超

平面上的點,而是所有可能影響我們的超平面的位置的那些混雜在彼此的類別中的點了。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn import svm
from sklearn.datasets import make_circles, make_moons, make_blobs,make_classification

n_samples = 100
datasets = [
        make_moons(n_samples=n_samples, noise=0.2, random_state=0),
        make_circles(n_samples=n_samples, noise=0.2, factor=0.5, random_state=1),
        make_blobs(n_samples=n_samples, centers=2, random_state=5),
        make_classification(n_samples=n_samples,n_features =2,n_informative=2,n_redundant=0, random_state=5)
        ]

 


Kernel = ["linear"]
for X,Y in datasets:
    plt.figure(figsize=(5,4))
    plt.scatter(X[:,0],X[:,1],c=Y,s=50,cmap="rainbow")

nrows=len(datasets)
ncols=len(Kernel) + 1
fig, axes = plt.subplots(nrows, ncols,figsize=(10,16))

#四個數據集分別是什麼樣子呢?
for ds_cnt, (X,Y) in enumerate(datasets):
    #在圖像中的第一列,第一個,放置原數據的分佈
    #zorder=10表示畫布的層級,edgecolors表示邊緣額顏色
    ax = axes[ds_cnt, 0]
    if ds_cnt == 0:
        ax.set_title("Input data")
    ax.scatter(X[:, 0], X[:, 1], c=Y, zorder=10, cmap=plt.cm.Paired,edgecolors='k')
    ax.set_xticks(())
    ax.set_yticks(())
    #第二層循環:在不同的核函數中循環
    #從圖像的第二列開始,一個個填充分類結果
    for est_idx, kernel in enumerate(Kernel):
        #定義子圖位置,從第一列,第二個開始
        ax = axes[ds_cnt, est_idx + 1]
        #建模
        clf = svm.SVC(kernel=kernel, gamma=2).fit(X, Y)
        score = clf.score(X, Y)
        
        #繪製圖像本身分佈的散點圖
        ax.scatter(X[:, 0], X[:, 1], c=Y
                   ,zorder=10
                   ,cmap=plt.cm.Paired,edgecolors='k')
        
        #繪製支持向量
        ax.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=50,
                   facecolors='none', zorder=10, edgecolors='k')
        
        #繪製決策邊界
        x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
        y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
        
        #np.mgrid,合併了我們之前使用的np.linspace和np.meshgrid的用法
        #一次性使用最大值和最小值來生成網格
        #表示爲[起始值:結束值:步長]
        #如果步長是複數,則其整數部分就是起始值和結束值之間創建的點的數量,並且結束值被包含在內
        XX, YY = np.mgrid[x_min:x_max:200j, y_min:y_max:200j]
        
        #np.c_,類似於np.vstack的功能
        Z = clf.decision_function(np.c_[XX.ravel(), YY.ravel()]).reshape(XX.shape)
        
        #填充等高線不同區域的顏色
        ax.pcolormesh(XX, YY, Z > 0, cmap=plt.cm.Paired)
        
        #繪製等高線
        ax.contour(XX, YY, Z, colors=['k', 'k', 'k'], linestyles=['--', '-', '--'],
                   levels=[-1, 0, 1])
        
        #設定座標軸爲不顯示
        ax.set_xticks(())
        ax.set_yticks(())
        
        #將標題放在第一行的頂上
        if ds_cnt == 0:
            ax.set_title(kernel)
        
        #爲每張圖添加分類的分數
        ax.text(0.95, 0.06, ('%.2f' % score).lstrip('0')
                , size=15
                , bbox=dict(boxstyle='round', alpha=0.8, facecolor='white')
                #爲分數添加一個白色的格子作爲底色
                , transform=ax.transAxes #確定文字所對應的座標軸,就是ax子圖的座標軸本身
                , horizontalalignment='right' #位於座標軸的什麼方向
                )

plt.tight_layout()
plt.show()

白色圈圈出的就是我們的支持向量,大家可以看到,所有在兩條虛線超平面之間的點,和虛線超平面外,但屬於另
一個類別的點,都被我們認爲是支持向量。並不是因爲這些點都在我們的超平面上,而是因爲我們的超平面由所有
的這些點來決定,我們可以通過調節C來移動我們的超平面,讓超平面過任何一個白色圈圈出的點。參數C就是這樣
影響了我們的決策,可以說是徹底改變了支持向量機的決策過程。

 

二分類SVC中的樣本不均衡問題:重要參數class_weight

對於分類問題,永遠都逃不過的一個痛點就是樣本不均衡問題。樣本不均衡是指在一組數據集中,標籤的一類天生
佔有很大的比例,但我們有着捕捉出某種特定的分類的需求的狀況。比如,我們現在要對潛在犯罪者和普通人進行
分類,潛在犯罪者佔總人口的比例是相當低的,也許只有2%左右,98%的人都是普通人,而我們的目標是要捕獲
出潛在犯罪者。這樣的標籤分佈會帶來許多問題。
首先,分類模型天生會傾向於多數的類,讓多數類更容易被判斷正確,少數類被犧牲掉。因爲對於模型而言,樣本
量越大的標籤可以學習的信息越多,算法就會更加依賴於從多數類中學到的信息來進行判斷。如果我們希望捕獲少
數類,模型就會失敗。其次,模型評估指標會失去意義。這種分類狀況下,即便模型什麼也不做,全把所有人都當
成不會犯罪的人,準確率也能非常高,這使得模型評估指標accuracy變得毫無意義,根本無法達到我們的“要識別
出會犯罪的人”的建模目的。

SVC的參數:class_weight
可輸入字典或者"balanced”,可不填,默認None 對SVC,將類i的參數C設置爲class_weight [i] * C。如果沒有給出
具體的class_weight,則所有類都被假設爲佔有相同的權重1,模型會根據數據原本的狀況去訓練。如果希望改善
樣本不均衡狀況,請輸入形如{"標籤的值1":權重1,"標籤的值2":權重2}的字典,則參數C將會自動被設爲:
標籤的值1的C:權重1 * C,標籤的值2的C:權重2*C 或者,可以使用“balanced”模式,這個模式使用y的值自動調整與輸入數據中的類頻率成反比的權重爲n_samples/(n_classes * np.bincount(y))。


首先,我們來自建一組樣本不平衡的數據集。我們在這組數據集上建兩個SVC模型,一個設置有class_weight參
數,一個不設置class_weight參數。我們對兩個模型分別進行評估並畫出他們的決策邊界,以此來觀察
class_weight帶來的效果。
 

1. 導入需要的庫和模塊

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm
from sklearn.datasets import make_blobs
 

2. 創建樣本不均衡的數據集

class_1 = 500 #類別1有500個樣本
class_2 = 50 #類別2只有50個
centers = [[0.0, 0.0], [2.0, 2.0]] #設定兩個類別的中心
clusters_std = [1.5, 0.5] #設定兩個類別的方差,通常來說,樣本量比較大的類別會更加鬆散
X, y = make_blobs(n_samples=[class_1, class_2],
                  centers=centers,
                  cluster_std=clusters_std,
                  random_state=0, shuffle=False)

plt.scatter(X[:, 0], X[:, 1], c=y, cmap="rainbow",s=10)
plt.show()

3. 在數據集上分別建模
#不設定class_weight
clf = svm.SVC(kernel='linear', C=1.0)
clf.fit(X, y)
Out[7]: 
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
    kernel='linear', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

 

#設定class_weight
wclf = svm.SVC(kernel='linear', class_weight={1: 10})
wclf.fit(X, y)
Out[8]: 
SVC(C=1.0, cache_size=200, class_weight={1: 10}, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
    kernel='linear', max_iter=-1, probability=False, random_state=None,
    shrinking=True, tol=0.001, verbose=False)

 

#給兩個模型分別打分看看,這個分數是accuracy準確度
clf.score(X,y)
Out[9]: 0.9418181818181818

wclf.score(X,y)
Out[10]: 0.9127272727272727

結論:可以看出引入了數據處理不平衡問題,準確度反而下降了,所有準確度不在是評價模型的標準

 

4. 繪製兩個模型下數據的決策邊界
plt.figure(figsize=(6,5))
plt.scatter(X[:, 0], X[:, 1], c=y, cmap="rainbow",s=10)
ax = plt.gca() #獲取當前的子圖,如果不存在,則創建新的子圖
#繪製決策邊界的第一步:要有網格
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
#第二步:找出我們的樣本點到決策邊界的距離
Z_clf = clf.decision_function(xy).reshape(XX.shape)
a = ax.contour(XX, YY, Z_clf, colors='black', levels=[0], alpha=0.5, linestyles=['-'])
Z_wclf = wclf.decision_function(xy).reshape(XX.shape)
b = ax.contour(XX, YY, Z_wclf, colors='red', levels=[0], alpha=0.5, linestyles=['-'])
#第三步:畫圖例
plt.legend([a.collections[0], b.collections[0]], ["non weighted", "weighted"],
           loc="upper right")
plt.show()

從圖像上可以看出,灰色是我們做樣本平衡之前的決策邊界。灰色線上方的點被分爲一類,下方的點被分爲另一
類。可以看到,大約有一半少數類(紅色)被分錯,多數類(紫色點)幾乎都被分類正確了。紅色是我們做樣本平
衡之後的決策邊界,同樣是紅色線上方一類,紅色線下方一類。可以看到,做了樣本平衡後,少數類幾乎全部都被
分類正確了,但是多數類有許多被分錯了。

 

單純地追求捕捉出少數類,就會成本太高,而不顧及少數類,又會無法達成模型的效果。所以在現實中,我們往往在尋找捕獲少數類的能力和將多數類判錯後需要付出的成本的平衡。如果一個模型在能夠儘量捕獲少數類的情況下,還能夠儘量對多數類判斷正確,則這個模型就非常優秀了。爲了評估這樣的能力,我們將引入新的模型評估指標:混淆矩陣
 

################  2.1 混淆矩陣(Confusion Matrix)  ######################
混淆矩陣是二分類問題的多維衡量指標體系,在樣本不平衡時極其有用。在混淆矩陣中,我們將少數類認爲是正
例,多數類認爲是負例。
混淆矩陣中,永遠是真實值在前,預測值在後。其實可以很容易看出,11和00的對角線就是全部預測正確的,01
和10的對角線就是全部預測錯誤的。基於混淆矩陣,我們有六個不同的模型評估指標,這些評估指標的範圍都在
[0,1]之間,所有以11和00爲分子的指標都是越接近1越好,所以以01和10爲分子的指標都是越接近0越好。對於所
有的指標,我們用橙色表示分母,用綠色表示分子,則我們有:

2.1.1 模型整體效果:準確率
準確率Accuracy就是所有預測正確的所有樣本除以總樣本,通常來說越接近1越好。

2.1.2 捕捉少數類的藝術:精確度,召回率和F1 score

精確度Precision,又叫查準率,表示所有被我們預測爲是少數類的樣本中,真正的少數類所佔的比例。

在支持向量機中,精確度可以被形象地表示爲決策邊界上方的所有點中,紅色點所佔的比例。精確度越高,代表我們捕捉正
確的紅色點越多,對少數類的預測越精確。精確度越低,則代表我們誤傷了過多的多數類。精確度是”將多數類判
錯後所需付出成本“的衡量。

#所有判斷正確並確實爲1的樣本 / 所有被判斷爲1的樣本
#對於沒有class_weight,沒有做樣本平衡的灰色決策邊界來說:
(y[y == clf.predict(X)] == 1).sum()/(clf.predict(X) == 1).sum()
Out[12]: 0.7142857142857143

#對於有class_weight,做了樣本平衡的紅色決策邊界來說:
(y[y == wclf.predict(X)] == 1).sum()/(wclf.predict(X) == 1).sum()
Out[13]: 0.5102040816326531

可以看出,做了樣本平衡之後,精確度是下降的。因爲很明顯,樣本平衡之後,有更多的多數類紫色點被我們誤傷
了。精確度可以幫助我們判斷,是否每一次對少數類的預測都精確,所以又被稱爲”查準率“。在現實的樣本不平衡
例子中,當每一次將多數類判斷錯誤的成本非常高昂的時候(比如大衆召回車輛的例子),我們會追求高精確度。
精確度越低,我們對多數類的判斷就會越錯誤。當然了,如果我們的目標是不計一切代價捕獲少數類,那我們並不
在意精確度。

 

召回率Recall,又被稱爲敏感度(sensitivity),真正率,查全率,表示所有真實爲1的樣本中,被我們預測正確的樣
本所佔的比例。

在支持向量機中,召回率可以被表示爲,決策邊界上方的所有紅色點佔全部樣本中的紅色點的比例。召回率越高,代表我們儘量捕捉出了越多的少數類,召回率越低,代表我們沒有捕捉出足夠的少數類。

#所有predict爲1的點 / 全部爲1的點的比例
#對於沒有class_weight,沒有做樣本平衡的灰色決策邊界來說
(y[y == clf.predict(X)] == 1).sum()/(y == 1).sum()
Out[14]: 0.6

#對於有class_weight,做了樣本平衡的紅色決策邊界來說:
(y[y == wclf.predict(X)] == 1).sum()/(y == 1).sum()
Out[15]: 1.0

可以看出,做樣本平衡之前,我們只成功捕獲了60%左右的少數類點,而做了樣本平衡之後的模型,捕捉出了
100%的少數類點,從圖像上來看,我們的紅色決策邊界的確捕捉出了全部的少數類,而灰色決策邊界只捕捉到了
一半左右。召回率可以幫助我們判斷,我們是否捕捉除了全部的少數類,所以又叫做查全率。
如果我們希望不計一切代價,找出少數類(比如找出潛在犯罪者的例子),那我們就會追求高召回率,相反如果我
們的目標不是儘量捕獲少數類,那我們就不需要在意召回率
 

 

2.1.3 判錯多數類的考量:特異度與假正率

特異度(Specificity)表示所有真實爲0的樣本中,被正確預測爲0的樣本所佔的比例。在支持向量機中,可以形象地
表示爲,決策邊界下方的點佔所有紫色點的比例。

#所有被正確預測爲0的樣本 / 所有的0樣本
#對於沒有class_weight,沒有做樣本平衡的灰色決策邊界來說:
(y[y == clf.predict(X)] == 0).sum()/(y == 0).sum()
Out[16]: 0.976

#對於有class_weight,做了樣本平衡的紅色決策邊界來說:
(y[y == wclf.predict(X)] == 0).sum()/(y == 0).sum()
Out[17]: 0.904

特異度衡量了一個模型將多數類判斷正確的能力,而1 - specificity就是一個模型將多數類判斷錯誤的能力,這種
能力被計算如下,並叫做假正率(False Positive Rate)。

在支持向量機中,假正率就是決策邊界上方的紫色點(所有被判斷錯誤的多數類)佔所有紫色點的比例。根據我們
之前在precision處的分析,其實可以看得出來,當樣本均衡過後,假正率會更高,因爲有更多紫色點被判斷錯誤,
而樣本均衡之前,假正率比較低,被判錯的紫色點比較少。所以假正率其實類似於Precision的反向指標,
Precision衡量有多少少數點被判斷正確,而假正率FPR衡量有多少多數點被判斷錯誤,性質是十分類似的。
 

 


 

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