文章目录
离散特征为什么要连续化
作者:严林
链接:https://www.zhihu.com/question/31989952/answer/54184582
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在工业界,很少直接将连续值作为逻辑回归模型的特征输入,而是将连续特征离散化为一系列0、1特征交给逻辑回归模型,这样做的优势有以下几点:
-
离散特征的增加和减少都很容易,易于模型的快速迭代;
-
稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
-
离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
-
逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;
-
离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
-
特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
-
特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
李沐曾经说过:模型是使用离散特征还是连续特征,其实是一个“海量离散特征+简单模型” 同 “少量连续特征+复杂模型”的权衡。既可以离散化用线性模型,也可以用连续特征加深度学习。就看是喜欢折腾特征还是折腾模型了。通常来说,前者容易,而且可以n个人一起并行做,有成功经验;后者目前看很赞,能走多远还须拭目以待。
离散化处理的一般过程
- 对连续特征值按照某种指定的规则进行排序
- 初步确定连续属性的划分断点
- 按照某种给定的判断标准继续分割断点或合并断点
- 如果第三步得到判断标准的终止条件,则终止整个连续特征离散化的过程,否则继续按第三步执行
离散化方法的分类
主要的分类体系有无监督的和有监督的(又分为建立在错误码率、熵值或者统计信息基础上的离散化方法,典型代表是等频、等宽的连续特征离散化方法)、动态的和静态的、全局的和局部的、分列式的(从上至下)和合并式的(从下至上) 、单变量的和多变量的以及直接的和增量式的。
无监督的方法
无监督方法都具有的问题就是都需要事先规定划分区间这个参数,常用的方法有分箱法和直观划分,首先来说一下分箱法:
分箱法又分为等宽分箱法和等频分箱法,其实从名字就能看出算法的做法了,前者制定定长的间隔将特征放入不同箱子内,后者根据频率划分箱子,这两种做法都有一定的缺陷。
-
等宽分箱法对异常点比较敏感,比如数据正常范围是30-60,现在出现了一个特征本来是30不小心读取成了300,那么宽度会被拉长,实例就主要集中在前面的箱体中,后面的箱体几乎没有实例,解决的方法主要是设立阈值移除阈值以外的实例。
-
等频分箱法的缺陷是完全按照频率划分会出现实例特征相同却不在同一个箱体内的情况,解决办法也很简单,在划分完成后进行微调就可完成。
-
基于K-Means的聚类分箱方法
直观划分法感觉就没什么技术了,这个算法主要是根据经验和美观,常用的方法划分出来肯定都是34267、64537这样的数,但是人们一般都习惯看到30000、50000这样的数,然后就是加上个人对数据的了解人为划分。
https://blog.csdn.net/u013818406/article/details/70494800
有监督的方法
1R方法
1R是一种使用分箱的有监督的方法。它把连续的区间分成小的区间,然后再使用类标签对小区间的边界进行调整。每个区间至少包含 6 个实例,除了最后一个区间外,最后一个区间包含所有未被列入其他区间的实例。从第一个实例开始,把前 6 个实例列入第一区间,如果下一个实例与此区间中大多数实例的类标签相同,则把此实例加入区间中,再判定下一个实例按照前述操作能否加入刚才的区间中,否则形成下一个含 6 个实例的新的区间,对下一个实例重复类似的操作,直至结束。然后把区间中的大多数实例的共同类标签作为此区间的类标签,如果相邻区间经过此操作后有相同的类标签,则应把这两个相邻区间合并。
1R离散化方法也是分箱方法,操作仍然比较方便,但又不再需要用户人为指定箱的个数,也克服了无监督的分箱方法的不使用类信息的缺陷,并且能避免把具有相同特征值相同类标签的实例分入不同的小区间中[6]。
卡方检验(CHI)
找到一个分裂点看,左右2个区间,在目标值上分布是否有显著差异,有显著差异就分裂,否则就忽略。这个点可以每次找差异最大的点。合并类似,先划分如果很小单元区间,按顺序合并在目标值上分布不显著的相邻区间,直到收敛。卡方值通常由χ2分布近似求得。
χ2表示观察值与理论值之问的偏离程度。计算这种偏离程度的基本思路如下:
-
设A代表某个类别的观察频数,E代表基于H0计算出的期望频数,A与E之差称为残差。
-
显然,残差可以表示某一个类别观察值和理论值的偏离程度,但如果将残差简单相加以表示各类别观察频数与期望频数的差别,则有一定的不足之处。因为残差有正有负,相加后会彼此抵消,总和仍然为0,为此可以将残差平方后求和。
-
另一方面,残差大小是一个相对的概念,相对于期望频数为10时,期望频数为20的残差非常大,但相对于期望频数为1 000时20的残差就很小了。考虑到这一点,人们又将残差平方除以期望频数再求和,以估计观察频数与期望频数的差别。
进行上述操作之后,就得到了常用的χ2统计量,由于它最初是由英国统计学家Karl Pearson在1900年首次提出的,因此也称之为Pearson χ2,其计算公式为
χ
2
=
∑
(
A
−
E
)
2
E
=
∑
i
=
1
k
(
A
i
−
E
i
)
2
E
i
=
∑
i
=
1
k
(
A
i
−
n
p
i
)
2
n
p
i
(
i
=
1
,
2
,
3...
,
k
)
\chi^2 =\sum \frac{(A-E)^2}{E} = \sum_{i=1}^k \frac{(A_i-E_i)^2}{E_i} = \sum_{i=1}^k \frac{(A_i-np_i)^2}{np_i} (i=1,2,3...,k)
χ2=∑E(A−E)2=i=1∑kEi(Ai−Ei)2=i=1∑knpi(Ai−npi)2(i=1,2,3...,k)
当n比较大时,χ2统计量近似服从k-1(计算Ei时用到的参数个数)个自由度的卡方分布。
信息增益法(IG)
这个和决策树的学习很类似。分裂方法,就是找到一个分裂点看,左右2个区间,看分裂前后信息增益变化阈值,如果差值超过阈值(正值,分列前-分裂后信息熵),则分裂。每次找差值最大的点做分裂点,直到收敛。合并类似,先划分如果很小单元区间,按顺序合并信息增益小于阈值的相邻区间,直到收敛。
离散化方法的评价
- 区间的个数:是对模型简洁性的要求
- 离散化所导致的不一致性:离散化后的不一致性不能比离散化之前高。
- 预测准确性:通常通过交叉检验模式建立分叉树来衡量。
- 具有最简单的离散化结果
离散化工具
sklearn.preprocessing.KBinsDiscretizer
Parameters:
n_bins :分桶个数.
encode : {‘onehot’, ‘onehot-dense’, ‘ordinal’}, (default=’onehot’)
分桶后结果的编码方式。
strategy : {‘uniform’, ‘quantile’, ‘kmeans’}, (default=’quantile’)
只支持三种分桶策略,不支持手工给定:
- uniform:每个值作为一个桶
- quantile: 均分,每个桶内的数据量一样
- kmeans: 一维数据聚类
>>> X = [[-2, 1, -4, -1],
... [-1, 2, -3, -0.5],
... [ 0, 3, -2, 0.5],
... [ 1, 4, -1, 2]]
>>> est = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='uniform')
>>> est.fit(X)
KBinsDiscretizer(...)
>>> Xt = est.transform(X)
>>> Xt
array([[ 0., 0., 0., 0.],
[ 1., 1., 1., 0.],
[ 2., 2., 2., 1.],
[ 2., 2., 2., 2.]])
pd.cut
适用于数据分割分桶,也适用于将连续特征转化为类别特征,支持将数据等分进各个桶,即KBinsDiscretizer中的quantile方法,也支持手动设定桶区间,主要是用np.linespace实现的。
bins = [0, 4, 10, 30, 45, 99999]
labels = ['Very_Low_Fare', 'Low_Fare', 'Med_Fare', 'High_Fare','Very_High_Fare']
train_orig.Fare[:10]
Out[0]:
0 7.2500
1 71.2833
2 7.9250
3 53.1000
4 8.0500
5 8.4583
6 51.8625
7 21.0750
8 11.1333
9 30.0708
Name: Fare, dtype: float64
pd.cut(train_orig.Fare, bins=bins, labels=labels)[:10]
Out[50]:
0 Low_Fare
1 Very_High_Fare
2 Low_Fare
3 Very_High_Fare
4 Low_Fare
5 Low_Fare
6 Very_High_Fare
7 Med_Fare
8 Med_Fare
9 High_Fare
Name: Fare, dtype: category
Categories (5, object): [High_Fare < Low_Fare < Med_Fare < Very_High_Fare < Very_Low_Fare]