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衡量有多少多数点被判断错误,性质是十分类似的。
 

 


 

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