【菜菜的sklearn课堂笔记】支持向量机-二分类SVC中的样本不均衡问题:重要参数class_weight

视频作者:菜菜TsaiTsai
链接:【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili

样本不均衡是指在一组数据集中,标签的一类天生占有很大的比例,但我们有着捕捉出某种特定的分类的需求的状况。比如,我们现在要对潜在犯罪者和普通人进行分类,潜在犯罪者占总人口的比例是相当低的,也许只有2%左右,98%的人都是普通人,而我们的目标是要捕获出潜在犯罪者。这样的标签分布会带来许多问题。

  • 首先,分类模型天生会倾向于多数的类,让多数类更容易被判断正确,少数类被牺牲掉。因为对于模型而言,样本量越大的标签可以学习的信息越多,算法就会更加依赖于从多数类中学到的信息来进行判断。如果我们希望捕获少数类,模型就会失败。
  • 其次,模型评估指标会失去意义。这种分类状况下,即便模型什么也不做,全把所有人都当成不会犯罪的人,准确率也能非常高,这使得模型评估指标accuracy变得毫无意义,根本无法达到我们的“要识别出会犯罪的人”的建模目的。

所以现在,我们首先要让算法意识到数据的标签是不均衡的,通过施加一些惩罚或者改变样本本身,来让模型向着捕获少数类的方向建模。然后,我们要改进我们的模型评估指标,使用更加针对于少数类的指标来优化模型。
对于支持向量机这个样本总是对计算速度影响巨大的算法来说,我们不想轻易地增加样本数量。况且,支持向量机中地决策仅仅受到决策边界的影响,而决策边界又仅仅受到参数C和支持向量的影响,单纯地增加样本数量不仅会增加计算时间,可能还会增加无数对决策边界无影响的样本点。因此在支持向量机中,我们要大力依赖我们调节样本均衡的参数:SVC类中的class_weight和接口fit中可以设定的sample_weight
在SVM中,我们的分类判断是基于决策边界的,而最终决定究竟使用怎样的支持向量和决策边界的参数是参数C,所以所有的样本均衡都是通过参数C来调整的。

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))

说一下np.bincount,计数非负整数数组中每个值的出现次数。

import numpy as np

a = [1,2,3,5,1,3,1,1,9]
b = np.bincount(a)
b
---
array([0, 4, 1, 2, 0, 1, 0, 0, 0, 1], dtype=int64)

对于它的返回值,一维array的索引和索引对应的元素值组成一对,例如上例,b[0]=0,即在a中数值0出现的次数为0次;b[1]=4,即在a中数值1出现的次数为4次
注意只能接收非负整数,其余值报错
因此对于我们“balanced”模式中n_samples/(n_classes * np.bincount(y)),其实也是一个类似字典的对象,key就是索引,value就是元素值

SVC的接口fit的参数:sample_weight

数组,结构为 (n_samples, ),必须对应输入fit中的特征矩阵的每个样本
每个样本在fit时的权重,让权重 * 每个样本对应的C值来迫使分类器强调设定的权重更大的样本。通常,较大的权重加在少数类的样本上,以迫使模型向着少数类的方向建模

通常来说,这两个参数我们只选取一个来设置。如果我们同时设置了两个参数,则C会同时受到两个参数的影响,即class_weight中设定的权重 * sample_weight中设定的权重 * C

利用数据集,观察使用class_weight前后的效果

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

class_1 = 500
class_2 = 50
# 标签为0的有500个,标签为1的有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
                )
# make_blobs默认n_features=2

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

【菜菜的sklearn课堂笔记】支持向量机-二分类SVC中的样本不均衡问题:重要参数class_weight_第1张图片

分别建模并比较

clf = SVC(kernel="linear",C=1)
clf.fit(X,y)
wclf = SVC(kernel="linear",class_weight={1:10})
# 将标签为1的权重设置为10,标签为0的默认为1
wclf.fit(X,y)

clf.score(X,y)
---
0.9418181818181818

wclf.score(X,y) # 做样本均衡之后,准确率下降
---
0.9127272727272727

plt.figure(figsize=(6,5))
plt.scatter(X[:,0],X[:,1],c=y,cmap="rainbow",s=10)
ax = plt.gca()

X_min,X_max = X[:,0].min(),X[:,0].max()
y_min,y_max = X[:,1].min(),X[:,1].max()
XX,YY = np.mgrid[X_min:X_max:30j,y_min:y_max:30j]
Z_clf = clf.decision_function(np.c_[XX.ravel(),YY.ravel()]).reshape(XX.shape)
Z_wclf = wclf.decision_function(np.c_[XX.ravel(),YY.ravel()]).reshape(XX.shape)
a = ax.contour(XX,YY,Z_clf,levels=[0],alpha=0.5,linestyles=['-'],colors='black')
b = ax.contour(XX,YY,Z_wclf,levels=[0],alpha=0.5,linestyles=['-'],colors='red')

plt.legend([a.collections[0],b.collections[0]]
          ,["non weighted","weighted"]
          ,loc='upper right'
          )
# legend([线],[名称],loc位置)
plt.show()

【菜菜的sklearn课堂笔记】支持向量机-二分类SVC中的样本不均衡问题:重要参数class_weight_第2张图片

从图像上可以看出

  • 灰色是我们做样本平衡之前的决策边界。灰色线上方的点被分为一类,下方的点被分为另一类。可以看到,大约有一半少数类(红色)被分错,多数类(紫色点)几乎都被分类正确了。
  • 红色是我们做样本平衡之后的决策边界,同样是红色线上方一类,红色线下方一类。做了样本平衡后,少数类几乎全部都被分类正确了,但是多数类有许多被分错了。

再根据之前score的值,我们可以得出结论,不做样本平衡的时候准确率反而更高,做了样本平衡准确率反而变低了。这是因为做了样本平衡后,为了要更有效地捕捉出少数类,模型误伤了许多多数类样本,而多数类被分错的样本数量 > 少数类被分类正确的样本数量,使得模型整体的精确性下降。现在,如果我们的目的是模型整体的准确率,那我们就要拒绝样本平衡,使用class_weight被设置之前的模型。

a.collections # 返回一个惰性对象,是一个列表,列表里面有一条线,因为上面levels=[0]代表只绘制一条线,这条线代表高度值为0
---
<a list of 1 mcoll.LineCollection objects>

[*a.collections] # 里面就是等高线例所有的线的列表
---
[<matplotlib.collections.LineCollection at 0x1d71110b5c0>]

# 现在我们只有一条线,所以我们可以使用索引0来锁定这个对象
a.collections[0]

然而在现实中,我们往往都在追求捕捉少数类,因为在很多情况下,将少数类判断错的代价是巨大的。比如我们之前提到的,判断潜在犯罪者和普通人的例子,如果我们没有能够识别出潜在犯罪者,那么这些人就可能去危害社会,造成恶劣影响,但如果我们把普通人错认为是潜在犯罪者,我们也许只是需要增加一些监控和人为甄别的成本。所以对我们来说,我们宁愿把普通人判错,也不想放过任何一个潜在犯罪者。

你可能感兴趣的:(菜菜的sklearn课堂,sklearn,算法,python,支持向量机)