特征选择特征选择概述Filter 过滤法方差选择法相关系数法卡方检验互信息法Wrapper 包装法稳定性选择(Stability Selection)递归特征消除特征值排序选择Embedded 嵌入法线性模型正则化树模型类别标签不平衡处理欠采样过采样加权处理
概念及工作原理
# 加载 IRIS 数据集做演示
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import LabelEncoder
%matplotlib inline
df = sns.load_dataset('iris')
print(df.shape)
df['species'] = LabelEncoder().fit_transform(df.iloc[:, 4])
df.head()
(150, 5)
sepal_length | sepal_width | petal_length | petal_width | species | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | 0 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | 0 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | 0 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | 0 |
它主要侧重于单个特征跟目标变量的相关性。 优点是计算时间上较高效,对于过拟合问题也具有较高的鲁棒性。 缺点就是倾向于选择冗余的特征,因为他们不考虑特征之间的相关性,有可能某一个特征的分类能力很差, 但是它和某些其它特征组合起来会得到不错的效果,这样就损失了有价值的特征。
为什么方差可以用于选择特征呢? 先从方差的概念说起,方差是衡量一个变量的离散程度(即数据偏离平均值的程度大小), 变量的方差越大,我们就可以认为它的离散程度越大,也就是意味着这个变量对模型的贡献和作用 会更明显,因此要保留方差较大的变量,反之,要剔除掉无意义的特征。
思路(先计算各个特征的方差,然后根据设定的阈值或待选择阈值的个数,选择方差大于阈值的特征),公式如下:
(1)计算特征的方差。假设X=[x1,x2,…,xn],则方差为: ,其中 是平均值,一般样本方差选择n-1较好
# 方差选择法
# 自己手写理论公式来实现功能
def VarianceThreshold(df, threshold=0.):
dfc = df.iloc[:, :4].copy()
print('>>>特征名:\n', dfc.columns.tolist())
# 1 求方差
var = np.sum(np.power(np.matrix(dfc.values)-np.matrix(dfc.mean()), 2), axis=0)/(dfc.shape[0]-1)
T = []
# 2 筛选大于阈值的特征
for index, v in enumerate(var.reshape(-1, 1)):
if v > threshold:
T.append(index)
dfc = dfc.iloc[:, T]
return var, dfc
# 阈值设置为 0.6
var, dfc = VarianceThreshold(df, 0.60)
print('\n>>>原始特征对应的方差值:\n', var)
print('\n>>>方差阈值选择后的特征名:\n', dfc.columns)
dfc.head()
>>>特征名:
['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
>>>原始特征对应的方差值:
[[0.68569351 0.18997942 3.11627785 0.58100626]]
>>>方差阈值选择后的特征名:
Index(['sepal_length', 'petal_length'], dtype='object')
sepal_length | petal_length | |
---|---|---|
0 | 5.1 | 1.4 |
1 | 4.9 | 1.4 |
2 | 4.7 | 1.3 |
3 | 4.6 | 1.5 |
4 | 5.0 | 1.4 |
# 调用 sklearn 模块 API 接口
from sklearn.feature_selection import VarianceThreshold
vt = VarianceThreshold(0.6)
x_vt = vt.fit_transform(df.iloc[:,:4])
print(vt.variances_)
x_vt[:5]
[0.68112222 0.18871289 3.09550267 0.57713289]
array([[5.1, 1.4],
[4.9, 1.4],
[4.7, 1.3],
[4.6, 1.5],
[5. , 1.4]])
第一种方法:计算特征与特征的相关系数
工作原理 通过计算特征与特征之间的相关系数的大小,可判定两两特征之间的相关程度。 取值区间在[-1, 1]之间,取值关系如下: corr(x1,x2)相关系数值小于0表示负相关((这个变量下降,那个就会上升)),即x1与x2是互补特征 corr(x1,x2)相关系数值等于0表示无相关 corr(x1,x2)相关系数值大于0表示正相关,即x1与x2是替代特征 原理实现:取相关系数值的绝对值,然后把corr值大于90%~95%的两两特征中的某一个特征剔除。
如果两个特征是完全线性相关的,这个时候我们只需要保留其中一个即可。 因为第二个特征包含的信息完全被第一个特征所包含。 此时,如果两个特征同时都保留的话,模型的性能很大情况会出现下降的情况
理论公式及推导 假设X=[x1,x2,…,xn],其中x1,x2…是列向量,即x1代表一个特征,公式推导如下: , 其中 表示各个特征的平均值
优缺点 优点:容易实现 缺点:只是根据特征与特征之间的相关度来筛选特征,但并没有结合与目标的相关度来衡量
应用场景 用于特征选择,以提取最有效的特征作为目标,剔除冗余特征
# 相关系数--特征与特征
# 自己手写理论公式实现功能
def corr_selector(df):
dfc = df.copy().iloc[:,:4]
CORR = np.zeros((dfc.shape[1], dfc.shape[1]))
delete, save = [], []
for i in range(dfc.shape[1]):
if dfc.columns.tolist()[i] not in delete:
save.append(dfc.columns.tolist()[i])
for j in range(i+1, dfc.shape[1]):
# 计算特征与特征之间的相关系数
cov = np.sum((dfc.iloc[:,i]-dfc.iloc[:,i].mean()) * (df.iloc[:,j]-df.iloc[:,j].mean()))
std = np.sqrt(np.sum((df.iloc[:,i]-df.iloc[:,i].mean())**2)) * np.sqrt(np.sum((df.iloc[:,j]-df.iloc[:,j].mean())**2))
corr = cov/std
CORR[i][j] = corr
# 筛选掉高线性相关两两特征中的某一个特征
if (np.abs(corr) > 0.89) and (dfc.columns.tolist()[j] not in delete):
delete.append(dfc.columns.tolist()[j])
dfc_ = dfc[save].copy()
return CORR, dfc_
corr,dfc_ = corr_selector(df)
print(corr)
dfc_.head()
[[ 0. -0.11756978 0.87175378 0.81794113]
[ 0. 0. -0.4284401 -0.36612593]
[ 0. 0. 0. 0.96286543]
[ 0. 0. 0. 0. ]]
sepal_length | sepal_width | petal_length | |
---|---|---|---|
0 | 5.1 | 3.5 | 1.4 |
1 | 4.9 | 3.0 | 1.4 |
2 | 4.7 | 3.2 | 1.3 |
3 | 4.6 | 3.1 | 1.5 |
4 | 5.0 | 3.6 | 1.4 |
第二种方法:计算特征与目标的相关系数以及P值
原理依据 scipy.stats.pearsonr(x, y) 输出:(r, p) r:相关系数[-1,1]之间 p:相关系数显著性
相关性的强度确实是用相关系数的大小来衡量的,但相关大小的评价要以相关系数显著性的评价为前提 因此,要先检验相关系数的显著性,如果显著,证明相关系数有统计学意义,下一步再来看相关系数大小; 如果相关系数没有统计学意义,那意味着你研究求得的相关系数也许是抽样误差或者测量误差造成的,再进行一次研究结果可 能就大不一样,此时讨论相关性强弱的意义就大大减弱了。
原理实现:先计算各个特征对目标值的相关系数以及相关系数的P值
优缺点
Pearson相关系数的一个明显缺陷是,作为特征排序机制,他只对线性关系敏感。 如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近0
应用场景及意义 应用于回归问题的特征选择,旨在选择出最有效的信息和减少内存占用空间
# 相关系数--特征与目标变量
# 自己手写理论公式实现功能
def corr_selector(df):
X, y = df.iloc[:, :4], df.iloc[:, 4]
cor_list = []
for i in X.columns.tolist():
cor = np.corrcoef(X[i], y)[0, 1]
cor_list.append(cor)
print(X.columns.tolist())
print(cor_list)
return cor_list
corr_selector(df)
['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
[0.7825612318100814, -0.4266575607811243, 0.9490346990083884, 0.9565473328764028]
[0.7825612318100814,
-0.4266575607811243,
0.9490346990083884,
0.9565473328764028]
df.plot()
plt.savefig('corr.png')
从图像趋势来看:
从上面的方差选择法及自变量与应变量的相关系数法可以验证上面图像趋势分析的结论。
# 调用 sklearn 模块 API 接口
from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
from numpy import array
fun = lambda X, Y: tuple(map(tuple, array(list(map(lambda x: pearsonr(x, Y), X.T))).T))
sb = SelectKBest(fun, k=2)
x_sb = sb.fit_transform(df.iloc[:,:4], df.iloc[:, 4])
print('>>>检验统计值(相关系数):\n', sb.scores_)
print('\n>>>P值:\n', sb.pvalues_)
x_sb[:5]
>>>检验统计值(相关系数):
[ 0.78256123 -0.42665756 0.9490347 0.95654733]
>>>P值:
[2.89047835e-32 5.20156326e-08 4.20187315e-76 4.15531102e-81]
array([[1.4, 0.2],
[1.4, 0.2],
[1.3, 0.2],
[1.5, 0.2],
[1.4, 0.2]])
工作原理 卡方检验是检验定性自变量对定性因变量的相关性,求出卡方值,然后根据卡方值 匹配出其所对应的概率是否足以推翻原假设H0,如果能推翻H0,就启用备用假设H1。
理论公式及推导 假设检验: 假如提出原假设H0:化妆与性别没有关系; 备用假设H1:化妆与性别有显著关系。
卡方值(chi-square-value)计算公式 其中,Ai为i水平的观察(实际)频数,Ei为i水平的期望(理论)频数,n为总频数,pi为i水平的期望频率。 i水平的期望频数Ei等于总频数n×i水平的期望概率pi,k为单元格数(行数*列数)。
如何判定两个定性变量的卡方值在什么区间可以证明假设成不成立呢? 计算步骤如下:
优缺点 优点:可以很好地筛选出与定性应变量有显著相关的定性自变量。
应用场景及意义 应用场景:适用于分类问题的分类变量
# 调用 sklearn 模块 API 接口
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
skb = SelectKBest(chi2, k=2)
x_skb = skb.fit_transform(df.iloc[:,:4], df.iloc[:,4])
print('>>>检验统计值(卡方值):\n', skb.scores_)
print('\n>>>P值:\n', skb.pvalues_)
x_skb[:5]
>>>检验统计值(卡方值):
[ 10.81782088 3.7107283 116.31261309 67.0483602 ]
>>>P值:
[4.47651499e-03 1.56395980e-01 5.53397228e-26 2.75824965e-15]
array([[1.4, 0.2],
[1.4, 0.2],
[1.3, 0.2],
[1.5, 0.2],
[1.4, 0.2]])
工作原理 评价定性自变量对定性应变量的相关性
在处理分类问题提取特征的时候就可以用互信息来衡量某个特征和特定类别的相关性, 如果信息量越大,那么特征和这个类别的相关性越大。反之也是成立的。
理论公式及推导
互信息公式: 其中 ,,其中是总样本数
标准互信息公式: 其中信息熵的公式 ,而
优缺点
应用场景及意义 应用场景:因此非常适合于文本分类的特征和类别的配准工作
# 互信息法
# 自己手写理论公式实现功能
# np.where(condition, x, y)
# 满足条件(condition),输出x,不满足输出y。
# numpy.intersect1d(ar1, ar2, assume_unique=False) [资源]
# 找到两个数组的交集。
# 返回两个输入数组中已排序的唯一值。
# math.log(x[, base])
# x -- 数值表达式。
# base -- 可选,底数,默认为 e
def MI_and_NMI():
import math
from sklearn import metrics
A = np.array([1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3])
B = np.array([1,2,1,1,1,1,1,2,2,2,2,3,1,1,3,3,3])
N, A_ids, B_ids = len(A), set(A), set(B)
# print(N, A_ids, B_ids)
# 互信息计算
MI, eps = 0, 1.4e-45
# 你说的这种应该是为了避免log0的结果
# 所以添加一个非常小的数字,避免无穷的...log(0)的情况
for i in A_ids:
for j in B_ids:
ida = np.where(A==i) # 返回索引
idb = np.where(B==j)
idab = np.intersect1d(ida, idb) # 返回相同的部分
#print(ida,idb,idab)
# 概率值
px = 1.0*len(ida[0])/N # 出现的次数/总样本数
py = 1.0*len(idb[0])/N
pxy = 1.0*len(idab)/N
# MI 值
MI += pxy * math.log(pxy/(px*py)+eps, 2)
# 标准互信息计算
Hx = 0
for i in A_ids:
ida = np.where(A==i)
px = 1.0*len(ida[0])/N
Hx -= px * math.log(px+eps, 2)
Hy = 0
for j in B_ids:
idb = np.where(B==j)
py = 1.0*len(idb[0])/N
Hy -= py * math.log(py+eps, 2)
NMI = 2.0*MI/(Hx+Hy)
return MI, NMI, metrics.normalized_mutual_info_score(A,B)
MI_and_NMI()
(0.565445018842856, 0.3645617718571898, 0.36456177185718985)
from sklearn import metrics
A = [1, 1, 1, 2, 3, 3]
B = [1, 2, 3, 1, 2, 3]
result_NMI=metrics.normalized_mutual_info_score(A, B)
print("result_NMI:",result_NMI)
result_NMI: 0.3016631159962042
封装器用选取的特征子集对样本(标签)集进行训练学习,训练的精度(准确率)作为衡量特征子集好坏的标准, 经过比较选出最好的特征子集。 常用的有逐步回归(Stepwise regression)、向前选择(Forward selection)和向后选择(Backward selection)。 它的优点是:考虑了特征之间组合以及特征与标签之间的关联性; 缺点是:当观测数据较少时容易过拟合,而当特征数量较多时,计算时间又会增长。
工作原理 包装法是指使用算法模型对特征子集与目标(标签)集进行训练评估,根据训练的精度(准确率)衡量特征子集的好坏,从而挑选出最好的特征子集。
理论公式及推导 步骤:
组合好特征子集 假设特征X=[A,B,C],目标(标签)Y,那么特征子集可以有组合方式: 其中要剔除掉空集,进一步扩展特征子集的表达方式如下: 特征子集,其中特征数
衡量特征好坏的不同方法 model.score(Xi,Y),可选择其他合理评估方法也可以 (1) 第一种(针对特征子集):从个特征子集中挑选出训练精度(评分)对应最好的特征子集 (2) 第二种(针对单个特征):比如把含有A特征的所有特征子集的评分/频率(频数)作为相对应的衡量指标 根据评分指标,挑选出前k个MeanScore值较大的特征作为最终的特征 ,N表示含有特征A的所有特征子集的次数,比如本例子含有A的特征子集有4次 根据频率指标,挑选出前k个F频率值较大的特征作为最终的特征 含有特征的特征子集被选为重要特征的次数含有特征的特征子集的数量 那么怎样才算被选为重要特征呢?对应指标大于80%(可根据具体情况调整)的特征子集可以被认为是重要特征
优缺点 优点:考虑了特征之间组合以及特征与标签之间的关联性。 缺点:由于要划分特征为特征子集并且逐个训练评分,因此当特征数量较多时,计算时间又会增长;另外在样本数据较少的时候,容易过拟合。
工作原理 稳定性选择是一种基于二次抽样和选择算法(训练模型)相结合的方法,选择算法可以是回归、分类SVM或者类似算法。
原理实现:在不同的特征子集上运行训练模型,不断地重复,最终汇总特征选择的结果。比如可以统计某个特征被认为是重要特征的频率 (被选为重要特征的次数除以它所在的子集被测试的次数)。理想情况下,重要特征的得分会接近100%。稍微弱一点的特征得分会是非0的数, 而最无用的特征得分将会接近于0。
优缺点 优点:
from sklearn.svm import SVC
X1, X2, Y = df[['petal_width']], df[['sepal_width']], df['species']
svc = SVC()
for X in [X1, X2]:
svc.fit(X,Y)
print(svc.score(X,Y))
0.96
0.5533333333333333
从上面可以验证RandomizedLasso比RandomizedLogisticRegression更靠谱, 因此在日后使用过程中不要急着筛选,也要验证特征选择的合理性以及优越性。
Recursive Feature Elimination,简称RFE 工作原理 主要思想是: (1)反复的构建模型(比如SVM或者回归模型) (2)接着选出最好(或者最差)的特征(可以根据系数来选),把选出来的特征放到一边 (3)然后在剩余的特征上重复上面(1)(2)步骤,直到遍历完所有特征。
通识来说: RFE算法的主要思想就是使用一个基模型(这里是S模型VM)来进行多轮训练, 每轮训练后,根据每个特征的系数对特征打分,去掉得分最小的特征, 然后用剩余的特征构建新的特征集,进行下一轮训练,直到所有的特征都遍历了。
这个过程中特征被消除的次序就是特征的排序,实际上这是一种寻找最优特征子集的贪心算法。
优缺点 RFE的稳定性很大程度上取决于在迭代选择的时候,选择哪一种模型。 (1)如果RFE采用的是普通的回归,没有经过正则化的回归是不稳定的,从而RFE也是不稳定的 (2)如果采用Ridge或Lasso模型,经过正则化的回归是稳定的,从而RFE是稳定的。
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_boston
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
#use linear regression as the model
lr = LinearRegression()
#rank all features, i.e continue the elimination until the last one
rfe = RFE(lr, n_features_to_select=2)
rfe.fit(X,Y)
print("Features sorted by their rank:")
print(sorted(zip(rfe.ranking_, names)))
Features sorted by their rank:
[(1, 'NOX'), (1, 'RM'), (2, 'CHAS'), (3, 'PTRATIO'), (4, 'DIS'), (5, 'LSTAT'), (6, 'RAD'), (7, 'CRIM'), (8, 'INDUS'), (9, 'ZN'), (10, 'TAX'), (11, 'B'), (12, 'AGE')]
概念及工作原理 理论上来讲,如果某个特征进行排序或者打乱之后,会很明显的影响(无论正向影响还是负向影响)到模型(预测评分)效果评分, 那么可以说明这个特征对模型来说是重要的;反之,说明这个特征存不存在并不会影响到模型的效能。
基于这么个原理,我们可以提出: (1)特征在进行排序或者打乱之后,会很明显影响模型性能的特征,划定为重要特征。 (2)特征在进行排序或者打乱之后,对模型性能几乎没有影响,划定为不重要特征。
集成法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权重值系数, 根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。 Regularization,或者使用决策树思想,Random Forest和Gradient boosting等
包装法与嵌入法的区别:包装法根据预测效果评分来选择,而嵌入法根据预测后的特征权重值系数来选择。 工作原理 先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。
有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中, 例如回归模型,SVM,树模型(决策树、随机森林)等等
工作原理 越是重要的特征在模型中对应的系数就会越大,而跟目标(标签)变量越是无关的特征对应的系数就会越接近于0。
优缺点 缺点: (1)如果特征之间存在多个互相关联的特征,模型就会变得很不稳定 (2)对噪声很敏感,数据中细微的变化就可能导致模型发生巨大的变化
from sklearn.linear_model import LinearRegression
X = df.iloc[:,:4]
Y = df.iloc[:,4]
lr = LinearRegression()
lr.fit(X,Y)
print(X.columns.tolist())
print(lr.coef_)
['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
[-0.11190585 -0.04007949 0.22864503 0.60925205]
概念:正则化就是把额外的约束或者惩罚项加到已有模型(损失函数)上,以防止过拟合并提高泛化能力。
损失函数由原来的变为 , 是模型系数组成的向量(有些地方也叫参数parameter,coefficients), ∥⋅∥‖⋅‖一般是L1或者L2范数,α是一个可调的参数,控制着正则化的强度。 当用在线性模型上时,L1正则化和L2正则化也称为Lasso和Ridge。
工作原理 L1正则化Lasso(least absolute shrinkage and selection operator)将系数w的l1范数作为惩罚项加到损失函数上,由于正则项非零,这就迫使那些弱的特征所对应的系数变成0。因此L1正则化往往会使学到的模型很稀疏(系数w经常为0),这个特性使得L1正则化成为一种很好的特征选择方法。
L2正则化同样将系数向量的L2范数添加到了损失函数中。由于L2惩罚项中系数是二次方的,这使得L2和L1有着诸多差异,最明显的一点就是,L2正则化会让系数的取值变得平均。
优缺点 L1正则化缺点:L1正则化像非正则化线性模型一样也是不稳定的,如果特征集合中具有相关联的特征,当数据发生细微变化时也有可能导致很大的模型差异。
L2正则化优点:L2正则化对于特征选择来说一种稳定的模型,不像L1正则化那样,系数会因为细微的数据变化而波动。
总结:L2正则化和L1正则化提供的价值是不同的,L2正则化对于特征理解来说更加有用:表示能力强的特征对应的系数是非零。
from sklearn.linear_model import Lasso
X = df.iloc[:,:4]
Y = df.iloc[:,4]
lasso = Lasso(alpha=0.3)
lasso.fit(X,Y)
print(X.columns.tolist())
print(lasso.coef_)
['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
[ 0. -0. 0.3435091 0. ]
由上面情况来看,L1范数正则化效果非常差,几乎特征都被弱化了,不知道哪一些重要
from sklearn.linear_model import Ridge
X = df.iloc[:,:4]
Y = df.iloc[:,4]
ridge = Ridge(alpha=0.3)
ridge.fit(X,Y)
print(X.columns.tolist())
print(ridge.coef_)
['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
[-0.11327412 -0.03665944 0.23969479 0.58451798]
由上面情况来看,L2范数正则化从返回的回归系数就可以知道模型比较稳定,并且效果不错。
工作原理 随机森林具有准确率高、鲁棒性好、易于使用等优点,随机森林提供了两种特征选择的方法: (1)平均不纯度减少 (2)平均精确率减少
# 平均不纯度减少
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
import numpy as np
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
# 训练随机森林模型,并通过feature_importances_属性获取每个特征的重要性分数。
rf = RandomForestRegressor()
rf.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names),
reverse=True))
Features sorted by their score:
[(0.4368, 'RM'), (0.3694, 'LSTAT'), (0.0655, 'DIS'), (0.0384, 'CRIM'), (0.0248, 'NOX'), (0.0156, 'PTRATIO'), (0.0141, 'TAX'), (0.0132, 'AGE'), (0.0117, 'B'), (0.0051, 'INDUS'), (0.0036, 'RAD'), (0.001, 'ZN'), (0.0008, 'CHAS')]
# 通过sklearn中的随机森林返回特征的重要性:
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
import numpy as np
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['is_train'] = np.random.uniform(0, 1, len(df)) <= .75
df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
df.head()
train, test = df[df['is_train']==True], df[df['is_train']==False]
features = df.columns[:4]
clf = RandomForestClassifier(n_jobs=2)
y, _ = pd.factorize(train['species'])
clf.fit(train[features], y)
preds = iris.target_names[clf.predict(test[features])]
pd.crosstab(test['species'], preds, rownames=['actual'], colnames=['preds'])
clf.feature_importances_
array([0.08850542, 0.0250167 , 0.39281297, 0.49366491])
概念和工作原理 (1)类别标签不平衡问题是指: 在分类任务中,数据集中来自不同类别的样本数目相差悬殊。
举个例子: 假设类别 A 的样本数量有 M 个,类别 B 的样本数量有 N 个,并且 M >>> N(假设 M:N=9:1), 这种情况我们就可以判定此数据集存在严重的类别标签不平衡的问题,为了防止模型出现严重误差, 因此在建模前需要就样本不平衡问题处理。
(2)类别不平衡问题会造成这样的后果: 在数据分布不平衡时,其往往会导致分类器的输出倾向于在数据集中占多数的类别。 输出多数类会带来更高的分类准确率,但在我们所关注的少数类中表现不佳。
(3)常用方法: 欠采样、过采样及加权处理。
(4)类别标签不平衡情况下的评价指标: 准确率在类别不平衡数据上,说服力最差。应考虑精确率、召回率、F1 值、F-R 曲线和 AUC 曲线。
所谓欠采样是指把占比多的类别 A 样本数量(M=900)减少到与占比少的类别 B 样本数量(N=100)一致,然后进行训练。
(1)第一种方法(随机欠采样): 随机欠采样是指通过随机抽取的方式抽取类别 A 中 100 个样本数据与类别 B 中的 100 个样本进行模型训练。 理论公式推导 个
随机欠采样的缺点:欠采样只是采取少部分数据,容易造成类别 A 的信息缺失
(2)第二种方法(代表性算法:EasyEnsemble 集成学习法): 算法思想:利用集成学习机制,将占比多的类别 A 样本数据划分为若干个样本子集供不同学习器使用, 这样对每个学习器来看都进行了欠采样,但在全局来看却不会丢失重要信息。
算法原理如下: 第一步:首先从占比多的类别 A 样本中独立随机抽取出若干个类别 A 样本子集。 第二步:将每个类别 A 的样本子集与占比少的类别 B 样本数据联合起来,训练生成多个基分类器。 第三步:最后将这些基分类器组合形成一个集成学习系统。集成可采用加权模型融合或者取所有基分类器总和的平均值。
EasyEnsemble 集成学习法优点:可以解决传统随机欠采样造成的数据信息丢失问题,且表现出较好的不均衡数据分类性能。
# 生成不平衡分类数据集
from collections import Counter
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=3000, n_features=2, n_informative=2,
n_redundant=0, n_repeated=0, n_classes=3,
n_clusters_per_class=1,
weights=[0.1, 0.05, 0.85],
class_sep=0.8, random_state=2018)
Counter(y)
Counter({2: 2532, 1: 163, 0: 305})
# 使用RandomOverSampler从少数类的样本中进行随机采样来增加新的样本使各个分类均衡
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(random_state=0)
X_resampled, y_resampled = ros.fit_sample(X, y)
sorted(Counter(y_resampled).items())
Using TensorFlow backend.
[(0, 2532), (1, 2532), (2, 2532)]
所谓过采样是指把占比少的类别 B 样本数量(N=100)扩增到占比多的类别 A 样本数量(M=900)一致,然后进行训练。
第一种方法(随机过采样): 由于随机过采样采取简单复制样本的策略来增加少数类样本,这样容易产生模型过拟合的问题, 即使得模型学习到的信息过于特别(Specific)而不够泛化(General),因此很少使用这种方法。
经典代表性算法是 SMOTE 算法:
SMOTE 的全称是 Synthetic Minority Over-Sampling Technique 即“人工少数类过采样法”,非直接对少数类进行重采样, 而是设计算法来人工合成一些新的少数样本。
算法原理如下: (1)在占比少的类别 B 中随机抽取一个样本 a,从 a 的最近邻 k 个数据中又随机选择一个样本 b。 (2)在 a 和 b 的连线上(或者说[a,b]区间中)随机选择一点作为新的少数类样本。
# SMOTE: 对于少数类样本a, 随机选择一个最近邻的样本b, 然后从a与b的连线上随机选取一个点c作为新的少数类样本
from imblearn.over_sampling import SMOTE
X_resampled_smote, y_resampled_smote = SMOTE().fit_sample(X, y)
sorted(Counter(y_resampled_smote).items())
# [(0, 2532), (1, 2532), (2, 2532)]
[(0, 2532), (1, 2532), (2, 2532)]
# ADASYN: 关注的是在那些基于K最近邻分类器被错误分类的原始样本附近生成新的少数类样本
from imblearn.over_sampling import ADASYN
X_resampled_adasyn, y_resampled_adasyn = ADASYN().fit_sample(X, y)
sorted(Counter(y_resampled_adasyn).items())
# [(0, 2522), (1, 2520), (2, 2532)]
[(0, 2522), (1, 2520), (2, 2532)]
# RandomUnderSampler函数是一种快速并十分简单的方式来平衡各个类别的数据: 随机选取数据的子集.
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_sample(X, y)
sorted(Counter(y_resampled).items())
[(0, 163), (1, 163), (2, 163)]
# 在之前的SMOTE方法中, 当由边界的样本与其他样本进行过采样差值时, 很容易生成一些噪音数据. 因此, 在过采样之后需要对样本进行清洗.
# 这样TomekLink 与 EditedNearestNeighbours方法就能实现上述的要求.
from imblearn.combine import SMOTEENN
smote_enn = SMOTEENN(random_state=0)
X_resampled, y_resampled = smote_enn.fit_sample(X, y)
sorted(Counter(y_resampled).items())
[(0, 2111), (1, 2099), (2, 1893)]
from imblearn.combine import SMOTETomek
smote_tomek = SMOTETomek(random_state=0)
X_resampled, y_resampled = smote_tomek.fit_sample(X, y)
sorted(Counter(y_resampled).items())
# [(0, 2412), (1, 2414), (2, 2396)]
[(0, 2412), (1, 2414), (2, 2396)]
加权处理是指通过调整不同类型标签的权重值,增加占比少的类别 B 样本数据的权重,降低占比多的类别 A 样本数据权重, 从而使总样本占比少的类别 B 的分类识别能力与类别 A 的分类识别能力能够同等抗衡。
加权处理原理如下: 遍历每一个样本,设总样本占比多的类别 A 的权重为 W1(自定义),总样本占比少的类别 B 的权重为 W2(自定义),其中 W2 > W1。 其实这个也类似于对模型进行惩罚,从而影响各个类别标签的重要性。
# 使用SVM的权重调节处理不均衡样本 权重为balanced 意味着权重为各分类数据量的反比
from sklearn.svm import SVC
svm_model = SVC(class_weight='balanced')
svm_model.fit(X, y)
SVC(C=1.0, break_ties=False, cache_size=200, class_weight='balanced', coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='scale', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
# BalancedBaggingClassifier 允许在训练每个基学习器之前对每个子集进行重抽样. 简而言之, 该方法结合了EasyEnsemble采样器与分类器(如BaggingClassifier)的结果.
from sklearn.tree import DecisionTreeClassifier
from imblearn.ensemble import BalancedBaggingClassifier
bbc = BalancedBaggingClassifier(base_estimator=DecisionTreeClassifier(),
replacement=False,
random_state=0)
bbc.fit(X, y)
BalancedBaggingClassifier(base_estimator=DecisionTreeClassifier(ccp_alpha=0.0,
class_weight=None,
criterion='gini',
max_depth=None,
max_features=None,
max_leaf_nodes=None,
min_impurity_decrease=0.0,
min_impurity_split=None,
min_samples_leaf=1,
min_samples_split=2,
min_weight_fraction_leaf=0.0,
presort='deprecated',
random_state=None,
splitter='best'),
bootstrap=True, bootstrap_features=False,
max_features=1.0, max_samples=1.0, n_estimators=10,
n_jobs=None, oob_score=False, random_state=0,
replacement=False, sampling_strategy='auto',
verbose=0, warm_start=False)