高数量类别特征(high-cardinality categorical attributes)的预处理方法

high-cardinality categorical attributes,从字面上理解,即对于某个category特征,不同值的数量非常多,这里暂且把它叫做高数量类别属性。反之,即低数量类别属性(low-cardinality)

对于低数量类别属性,通常在data science中采用的方式是将其转化为one-hot编码,即给每一个类别增加一个特征。但是当类别数量增加的时候,ont-hot编码增加的特征也在增加。所以,one-hot编码无法适用于高数量特征属性。

基本方法(clustering)

目前有一个常见的方法处理这个问题叫做clustering。即将原始的1-to-N的mapping问题变成1-to-k的mapping问题。(k<<N)

为了达到这个目标,这个高数量类别属性首先将依据target的值grouping成k个类(clusters),然后再依据这个grouping的结果进行one-hot编码。

不得不说,这个方法最大程度的保留了原始数据的信息。grouping的方法有一些:其中一个就是Hierarchical clustering Algorithm,它使用一个基于target的的统计距离来grouping,grouping的标准也可以度量每合并两个clusters带来的信息增量的影响,如gain ratio。两个距离最短的clusters将会被合并成一个。这个过程一直被迭代,直到迭代过程没有明显的改善。

这个问题更多解释可见《统计学习方法》5.2.2

cate_A target
a/b/c.... 1/0
... ...

对上述数据集, cate_A为高数量特征属性,其处理方法的伪代码如下:

while improvment > epsilon or unique(cate_A) >= k:
    compute Conditional Entropy Target|cate_A: THA1
    for _  :
        Merge two random labels in cate_A into one
        compute Conditional Entropy Target|cate_A: THA
        Find the two labels that then compute to a minimum THA2
    improvement = THA1 - THA2

上述方法其实很常见,且被运用在决策树的C4.5方法中进行特征选择。

这个方法在最后需要ont-hot编码。

另一方法(smoothing)

Smoothing,简单来说,就是将原来独立的高数量类别特征的每个值映射到概率估计上。基本来讲,这个预处理方法将原始的值放置到实际的机器学习模型之前先通过一个简单的特征处理模型(如贝叶斯模型)。

下面以binary target为例进行方法分析:

当target属性 (Yin{0,1})时,假设要处理的特征为X,该特征的每一个不同的值为(X_{i})。我们要做的是, 将高数量类别特征将映射到一个标量(S_{i})中,(S_{i})代表一个条件概率,即

$X_{i} o S_{i} cong P(Y|X=X_{i}) ---(1)$

注意到(S_{i})代表的是条件概率,那么他的值被归一到了0和1之间,这对于神经网络模型也是一个好的预处理。

下一步就是概率估计的过程。我们假设数据集被分成了(n_{TR})个训练集和(n_{TS})个测试集。因为这个概率估计成为了模型训练的一部分,所以只有训练集的数据被使用。注意到不是所有的X的可能值都会出现在训练集中,有的值可能只出现在测试集或者新进来的数据中。所以,这个映射过程必须要能够处理这个特征的不可预见性的值。

如果该特征某个值如(X=X_{i})出现的数量足够多,那么这个概率估计可以这样计算:

$S_{i}=frac{n_{iY}}{n_{i}} ---(2)$

这是一个后验概率的计算过程。然而不幸的是,特征的值的数量分布通常是不均匀的,有很多值的数量非常少。所以这种使用(P(Y|X=X_{i}))的直接估计是不太可靠的。

为了减小这种小数量值的影响,(S_{i})的计算可能被分成两个概率的组合。后验概率的计算如公式(2),先验概率的计算如(P(Y) = frac{n_{Y}}{n_{TR}})。整个组合计算公式为:

$S_{i}=lambda(n_{i})frac{n_{iY}}{n_{i}}+(1-lambda(n_{i}))frac{n_{Y}}{n_{TR}} ---(3)$

(n_{Y})代表在整个数据集中(Y=1)的数量。(lambda(n_{i}))是一个在0-1之间的单调递增函数。

原理:一方面,当特征的某个值的数量很多,即(lambdacong1)时,公式即为(2),计算后验概率。另一方面,当特征的某个值的数量很少时,即(lambdacong0)时,公式前项为0,只计算先验概率。

所以,关键我们怎么选取(lambda(n_{i}))这个函数呢?有一个典型函数如下:

$lambda(n)=frac{1}{1+exp^{-frac{n-k}{f}}} ---(4)$

这个公式是一个s形状的函数,当 n=k 时值为 0.5 。

  • 参数 f 控制函数在转折处的斜率,决定了先验概率和后验概率之间的平衡。如果(f oinfty),那么公式(3)变为一个硬间隔,即先验概率和后验概率各占0.5。

  • 参数 k 决定于我们允许的特征值数量的最小值的一半。so important!

我们在来看看经验贝叶斯估计(Empirical Bayes estimation) 的一般公式:

$P=B_{i}y_{i}+(1-B_{i})overline{y}---(5)$

(overline{y})是先验概率,(y_{i})是经验后验概率。收缩系数(B_{i})根据不同的估计方法有不同的形式。当所有概率分布服从高斯分布的时候:

$B_{i}=frac{n_{i} au^{2}}{sigma^{2}+n_{i} au^{2}}---(6)$

(sigma^{2})是值的方差,( au^{2})是样本方差。事实上,公式(6)是公式(4)的一般形式。

以下是我在kaggle中看到大佬对该方法的coding实现,借以参考,帮助理解:

def add_noise(series, noise_level):
    return series * (1 + noise_level * np.random.randn(len(series)))

def target_encode(trn_series=None, 
                  tst_series=None, 
                  target=None, 
                  min_samples_leaf=1, 
                  smoothing=1,
                  noise_level=0):
    """
    trn_series : training categorical feature as a pd.Series
    tst_series : test categorical feature as a pd.Series
    target : target data as a pd.Series
    min_samples_leaf (int) : minimum samples to take category average into account
    smoothing (int) : smoothing effect to balance categorical average vs prior  
    """ 
    assert len(trn_series) == len(target)
    assert trn_series.name == tst_series.name
    temp = pd.concat([trn_series, target], axis=1)
    # Compute target mean 
    averages = temp.groupby(by=trn_series.name)[target.name].agg(["mean", "count"])
    # Compute smoothing
    smoothing = 1 / (1 + np.exp(-(averages["count"] - min_samples_leaf) / smoothing))
    # Apply average function to all target data
    prior = target.mean()
    # The bigger the count the less full_avg is taken into account
    averages[target.name] = prior * (1 - smoothing) + averages["mean"] * smoothing
    averages.drop(["mean", "count"], axis=1, inplace=True)
    # Apply averages to trn and tst series
    ft_trn_series = pd.merge(
        trn_series.to_frame(trn_series.name),
        averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
        on=trn_series.name,
        how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
    # pd.merge does not keep the index so restore it
    ft_trn_series.index = trn_series.index 
    ft_tst_series = pd.merge(
        tst_series.to_frame(tst_series.name),
        averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
        on=tst_series.name,
        how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
    # pd.merge does not keep the index so restore it
    ft_tst_series.index = tst_series.index
    return add_noise(ft_trn_series, noise_level), add_noise(ft_tst_series, noise_level)

两个方法比较

smoothing方法可以只要通过对本地数据集的操作就可完成预处理,而clustering方法需要更复杂的算法而且可能导致信息量的减少(因为最后仍然需要进行one-hot编码)。

当然,对于smoothing方法来说, (lambda)函数的选择直接影响到了结果,可能不同的(lambda)函数可以适用于不同的分支领域,这都需要实验的考证。


PS:该篇文章主要基于对文献的翻译,这个翻译过程终于让我体会到翻译的不易了,以后我再也不会抱怨专业书翻译者的不专业了。

Reference:

  1. A Preprocessing Scheme for High-Cardinality Categorical Attributes in Classification and Prediction Problems
  2. 《统计学习方法》.李航
  3. Python target encoding for categorical features
原文地址:https://www.cnblogs.com/bjwu/p/9087071.html