Skip to content

类别型特征的处理方法与平均数编码

《百面》第二章「类别型特征」,提出一个问题:

在对数据进行预处理时,应该怎样处理类别型特征?

仔细研究才发现,这里面竟然有很多以前从未听过的知识——毕竟研究生不会有人手把手教你这么系统地去学,只有老板给什么就去实现什么而已……那么开始吧~

什么是类别型特征

看名字就知道,是机器学习的输入数据中,表示类别的特征。比如:

数据ID性别学历出生城市10年内深圳买房(y)
1博士深圳
2硕士重庆
3大学哈尔滨
4高中成都
5初中湖南

这些数据需要被通过某种方法转换为数字,也就是所谓的编码,才能更好地被那些机器学习算法中建立的各种数学模型来使用。我以前的转换手段就是暴力的:

  • 女=0,男=1
  • 初中=0,高中=1,大学=2,硕士=3,博士=4
  • 深圳=0,重庆=1,哈尔滨=2,成都=3,湖南=4
  • 否=0,是=1

非常简单,然而这其实并不是最好的处理方式,好在,我一直学习和使用的都是树模型……

类别型特征的处理方法

序号编码(Label Encoding)

上面的暴力方法就是序号编码,通过数字和值的一一映射达到编码的目的。但它适合于处理具有大小关系的特征,比如学历从低到高正好对应了数字从小到大,没有改变特征的特性。如果把它用于处理没有任何大小关系的特征,比如出生城市,所有的城市都是平级的,转变为数值之后却莫名地增加了一层大小关系,这对于数值大小敏感的模型(比如SVM支持向量机)是致命的——因为会影响损失函数之类计算结果。

python的机器学习包sklearn的LabelEncoder实现了序号编码。

独热编码(One-hot Encoding)

独热编码也成为哑变量编码(Dummy Encoding)(不过有的也将哑变量编码区分为“随机去项的降维独热编码”),指的是,将原始特征变量转换为原始特征值分类的多维度变量,在每个维度上使用0/1来进行是/否、有/无的量化。通俗来说就是,把每个取值作为一个新特征,一行数据中,取到了这个值就是1,没取到这个值就是0。用出生城市来举个例子:

数据ID\出生城市深圳重庆哈尔滨成都湖南
110000
201000
300100
400010
500001

这样做的好处在于:

  • 没有了数值大小关系,SVM也可以尽情使用哦~
  • 扩充了特征,将一个“出生城市”的特征扩充为了5个新特征,有效地减少了过拟合风险。

然而也很容易看出缺点:

  • 如果取值较多,很可能会分裂出大量的新特征,导致维度过高不容易处理。
  • 非常不适合树模型。如果特征很多,取这个值的数量又很少,那么一些特征变量很可能因为树模型的参数配置而无法向下分裂,导致这些信息白白丢失掉了。
  • 一个特征变为多个特征,当数据条数越多的时候,整体的数据量会暴增。

所以,在使用独热编码的时候可以这样做:

  1. 使用稀疏向量节省空间。

独热编码只有一个取值1,其余为0,符合稀疏向量的特性。也就是说,我们把上述出生城市改写为:

数据ID出生城市
15, (1,1)
25, (2,1)
35, (3,1)
45, (4,1)
55, (5,1)

只是表述方式上变了,数据量减小了,但仍然是拆分成了5个特征。

  1. 配合特征选择来降低维度。

我们都知道,K近邻算法需要有效地去衡量两点之间的距离,因此通常需要进行降维。独热编码带来的维度灾难副作用可以通过降维来减少其影响。常见的降维方式有PCA主成分分析、随机特征选择等等。我个人是最喜欢用随机了,因为通常采用的是树模型,而且随机能有效减少树的过拟合,实现起来又简单。

python的机器学习包sklearn的OneHotEncoder实现了独热编码,pandas的get_dummies同样实现了独热编码,推荐pandas。

二进制编码(Binary Encoding)

就是用二进制去表示类别,比如出生城市可以表示为:

数据ID\出生城市F1F2F3
1000
2001
3010
4011
5100

有的同学会说,哎这不是有大小关系了吗。但仔细看会发现,这种大小关系在单独一列中是不会体现出来的。不过似乎没有看到过这种二进制编码有具体应用,只看见过理解神经网络的中间层原理的时候,发现它利用了这种二进制机制。

平均数编码(Mean Encoding)

划重点!

这个方法是在kaggle圈的各位大佬们针对“高基数类别型特征(High-Cardinality Categorical Attributes)”使用之后发现非常有效(AUC的提升)的方法,它并没有被大量研究,中文资料甚至更少,但是实践真的有奇效。平均数编码的提出是在2001年的论文《A Preprocessing Scheme for High-Cardinality Categorical Attributes in Classification and Prediction Problems》,其实是相当古老的论文了,只有2017年的一篇《CatBoost: unbiased boosting with categorical features》提到了它。

1、clustering和smoothing

平均数编码主要用于处理高基数类别型特征,高基数的意思就是类别非常多,比如邮编(000001-999999)、区号、IP、家庭住址,这类特征如果直接用独热编码就不要想了。所以目前常见的做法就是:

  • 将1到N(N非常大)的映射问题通过聚类转换为1到K(K<<N)
  • 然后再进行独热编码

这种方法统称为clustering

举个直观但不真实的例子,虽然颜色可能有——深蓝、浅蓝、湖蓝、碧蓝、靛蓝、钴蓝、蔚蓝、宝蓝、藏蓝、冰蓝、宝石蓝、海蓝、湛蓝、橙红、粉红、紫红(N=16),那么我们根据分类取值(Y=0/1)把这些颜色都聚合为蓝色红色(K=2)这两个值,一下就减少了大量的特征取值。然后再将蓝色红色通过独热编码变为2列,就完成了类别值到数值的转换。然而这种做法导致了特征信息的丢失(本来有很多种蓝色特征,结果我都看成一种蓝色特征了)。

那么我们把这种clustering泛化一下,不要强迫为蓝色红色(=0、=1)了,改成概率估计是蓝色的概率是红色的概率(=0~1之间的小数),也就产生了smoothing

平均数编码就是smoothing的一种实现。

2、以二值目标分类理解平均数编码

假设我们有一个二值分类问题,给定特征X是高基数类别型特征,Y{0,1},那么对于特定的取值X=Xi,我们希望得到它后验概率(看到这种特征取这个值,这个样本的分类是Y=1的概率),那么可以用标量Si去估计这个后验概率:

XiSiP(Y|X=Xi)

由于后面需要用这些特征去训练机器学习模型,所以在特征处理这一步,我们只能使用训练数据去估计这个特征值的后验概率。设训练集有nTR个样本,测试集有nTS个样本,如果训练集在X的每个取值Xi上都能取到足够多的样本,那么很明显我们可以用最简单的样本数目来做这个后验概率的估计:

Si=niYni

其中niY表示在XXi值的时候,Y=1的样本数目;ni表示XXi值的样本数目。然而在实际的场景中,ni的数量很小(因为有大量的Xi值可以取,取每个值的样本数目非常有可能不均衡),所以这个估计是不可信的。为了削弱样本数目小带来的负面影响,我们混合先验概率和后验概率:

Si=λ(ni)niYni+(1λ(ni))nYnTR

其中,λ是阈值为[0,1]的单调递增函数。很容易理解这个式子:当Xi取值内样本数量非常多的时候, niniYλ(ni)1,相当于在使用之前的式子计算;当Xi取值内样本数量非常少的时候,ni0niY0λ(ni)0,那么我们直接采用Y=1的先验概率作为估计,也就是说这时候X=Xi已经不重要了,提供不了任何能判断Y=1的信息,那就直接用整个训练样本中正样本数占总样本数的比例来作为估计就好了。

λ采用这样的函数:

λ(n)=11+enkf

对比一下神经网络中常常使用的Sigmod函数

S(x)=11+ex

从图像上说,相当于把Sigmod函数图像向右平移k个单位+拉伸f倍。

从意义上说,k决定了我们完全信任基于这些取值内的样本得到的估计的最小样本数的一半,因为当n=kλ会等于0.5,这时候我们一半相信先验概率,一半相信后验概率;f控制函数在转折点的斜率,决定了先验概率和后验概率之间的平衡,因为当f的时候,λ0,不论n为什么值,都几乎是0.5先验+0.5后验。

以上Si的计算公式其实来源于经验贝叶斯(Empirical Bayesian),λ就是经验贝叶斯公式中的收缩因子(shrinkage factor)Bi的泛化形式。

3、处理缺失值

我们只用了训练数据,在测试数据里很可能有根本没有出现过的取值,对于这个公式来讲,处理缺失值非常简单,只需要增加一个空缺值X=X0的估计即可:

S0=λ(n0)n0Yn0+(1λ(n0))nYnTR

这样做的好处在于,如果缺失值和分类有很大相关性,那么这个公式就可以很好地表达这种相关信息;反之如果缺失值和分类没有太大关系,那么S0也会很快收敛于先验概率,符合通常情况下对缺失值的中立表示。

4、多分类问题

只需要将估计的概率转变一下就好啦:

P(Y=Yj|X=Xi)

注意之前二分类的时候我们是直接替换的那些类别型特征值,而这里多分类的时候,假设有C个类,会产生C列。为什么之前C=2的时候只有1列呢?因为特征线性相关了,比如求P(Y=1|X=Xi)的概率,也就知道了P(Y=0|X=Xi)的概率。所以同样的,这里产生了C列也需要去除最后一列。

5、连续型类别问题(回归)

对于回归来说,只需要把概率估计改为期望估计就可

Si=λ(ni)kLiYkni+(1λ(ni))k=1NTRYkNTR

如果把二分类的类别标记为0/1,那么二分类和回归可以用相同的公式计算。

6、具有层次结构的类别型特征

有的时候我们的类别型特征可能具有层级结构,比如邮编,北京市前两位全是10开头的,然后再继续往后细分是哪个区。我们可以利用这种层级关系来使得每个取值的样本数目更加合适一点。设5位邮编为ZIP5,4位ZIP4,以此类推。那么最开始的计算公式是这样的:

Si5=λ(ni)ni1ni+(1λ(ni))nYnTR

当ZIP5样本很稀疏的时候,它几乎所有的取值都会依赖于先验概率。而我们可以用这种层次结构来尽可能地提取出新的信息。很明显,ZIP5的先验概率可以用ZIP4去估计:

Si5=λ(ni)ni1ni+(1λ(ni))Si4

当样本充足时,会用ZIP5的后验概率,当样本不充足时,会去考虑ZIP4的后验概率,逐级向上,相当于根据数据密度进行了自动调节。

7、平均数编码的过拟合问题

有同学在实践中发现,如果使用全部训练数据去处理特征,那么之后的机器学习模型会产生过拟合问题。所以一种解决方法是,分成k份,每份内的特征值处理采用其他k-1份组成的数据集来训练平均数编码模型,再替换自己的值。

总结

编码方式适用类型实现难度效果
序号编码大小不敏感模型(树)容易
独热编码特征取值少、非树模型容易
二进制编码-容易-
平均数编码高基数类别型特征困难非常好

参考资料

转载请注明出处https://bananaoven.com/articles/217.html | 香蕉微波炉
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。