使用gbdt时类别特征的处理方法
在去年第一次参加ctr比赛中碰到类别特征时,第一反应是进行one-hot编码而不能使用序号编码,因为序号编码给类别的不同属性赋予了数值的意义,然而在实际比赛中发现,one-hot编码后的效果并不好,甚至和直接序号编码的效果不相上下,但是带来训练时间的增加非常大,那么为什么对类别进行one-hot编码和label编码效果差不多,而不是更好呢?在参加比赛之后我对这些有了更多的见解,在此总结。
其实对类别特征进行onehot编码带了效果很差更多指的是类别特征维度很高的时候(相对样本量来说),主要的问题是:
1.可能无法在这个类别特征上进行切分。使用one-hot coding的话,意味着在每一个决策节点上只能用 one-vs-rest (例如是不是狗,是不是猫,等等) 的切分方式。当特征纬度高时,每个类别上的数据都会比较少,这时候产生的切分不平衡,切分增益(split gain)也会很小(比较直观的理解是,不平衡的切分和不切分几乎没有区别)。
2.会影响决策树的学习。因为就算可以在这个类别特征进行切分,也会把数据切分到很多零散的小空间上,如下图左所示。而决策树学习时利用的是统计信息,在这些数据量小的空间上,统计信息不准确,学习会变差。但如果使用下图右边的切分方法,数据会被切分到两个比较大的空间,进一步的学习也会更好。
也就是说,正确的做法应该是一次切分,均匀的切分成两部分,使得两边都包含足够的样本继续学习,然而使用one-hot编码,在类别维度很大的情况下,无法保证这种情况。那么gbdt应该怎样使用类别特征?
我总结为以下三种方法:
1.类别特征的最优切分。这个方法需要对应工具的支持,我所知的支持这个方法的工具有h2o.gbm和LightGBM。用LightGBM可以直接输入类别特征,并产生同上图右边的最优切分。在一个k维的类别特征寻找最优切分,朴素的枚举算法的复杂度是指数的 O(2^k)。LightGBM 用了一个 O(klogk)[1] 的算法。算法流程如下图所示:在枚举分割点之前,先把直方图按照每个类别对应的label均值进行排序;然后按照排序的结果依次枚举最优分割点。当然,这个方法很容易过拟合,所以LightGBM里面还增加了很多对于这个方法的约束和正则化。当然了,如果不具备这种工具,那可以对数据进行可视化,然这个特征不同属性对应的label分布,手动选择合适的切分点!
2.进行信息压缩:转成数值特征。在使用 sklearn 或 XGBoost 等不支持类别特征的最优切分工具时,可以用这个方法。常见的转换方法有: a) 把类别特征转成one-hot coding扔到NN里训练个embedding;b) 类似于CTR特征,统计每个类别对应的label(训练目标)的均值。统计的时候有一些小技巧,比如不把自身的label算进去(leave-me-out, leave-one-out)统计, 防止信息泄露。ps:我在比赛中的做法一般是统计出现的次数+正样本数,两个特征在表示原特征,进行压缩,效果不错~
3.使用其他的编码方法。