视频作者:菜菜TsaiTsai
链接:【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili
样本不均衡是指在一组数据集中,标签的一类天生占有很大的比例,但我们有着捕捉出某种特定的分类的需求的状况。比如,我们现在要对潜在犯罪者和普通人进行分类,潜在犯罪者占总人口的比例是相当低的,也许只有2%左右,98%的人都是普通人,而我们的目标是要捕获出潜在犯罪者。这样的标签分布会带来许多问题。
所以现在,我们首先要让算法意识到数据的标签是不均衡的,通过施加一些惩罚或者改变样本本身,来让模型向着捕获少数类的方向建模。然后,我们要改进我们的模型评估指标,使用更加针对于少数类的指标来优化模型。
对于支持向量机这个样本总是对计算速度影响巨大的算法来说,我们不想轻易地增加样本数量。况且,支持向量机中地决策仅仅受到决策边界的影响,而决策边界又仅仅受到参数C和支持向量的影响,单纯地增加样本数量不仅会增加计算时间,可能还会增加无数对决策边界无影响的样本点。因此在支持向量机中,我们要大力依赖我们调节样本均衡的参数:SVC类中的class_weight和接口fit中可以设定的sample_weight。
在SVM中,我们的分类判断是基于决策边界的,而最终决定究竟使用怎样的支持向量和决策边界的参数是参数C,所以所有的样本均衡都是通过参数C来调整的。
可输入字典或者"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就是元素值
数组,结构为 (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)
分别建模并比较
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()
从图像上可以看出
再根据之前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]
然而在现实中,我们往往都在追求捕捉少数类,因为在很多情况下,将少数类判断错的代价是巨大的。比如我们之前提到的,判断潜在犯罪者和普通人的例子,如果我们没有能够识别出潜在犯罪者,那么这些人就可能去危害社会,造成恶劣影响,但如果我们把普通人错认为是潜在犯罪者,我们也许只是需要增加一些监控和人为甄别的成本。所以对我们来说,我们宁愿把普通人判错,也不想放过任何一个潜在犯罪者。