别再只会用Onehot了!Kaggle Master的上分神技

2022-02-11 09:40:31 浏览数 (1)

炼丹笔记·干货

整理编辑:DOTA

目前看到的大多数特征工程方法都是针对数值特征的。本文介绍的Target Encoding是用于类别特征的。这是一种将类别编码为数字的方法,就像One-hot或Label-encoding一样,但和这种两种方法不同的地方在于target encoding还使用目标来创建编码,这就是我们所说的有监督特征工程方法。

Target Encoding

Target Encoding是任何一种可以从目标中派生出数字替换特征类别的编码方式。这种目标编码有时被称为平均编码。应用于二进制目标时,也被称为bin counting。(可能会遇到的其他名称包括:likelihood encoding, impact encoding, and leave-one-out encoding。)

每种方法都有其缺点,target encoding的缺点主要有:

  • 未知类别,会产生过拟合风险;
  • 空值,采用填充的方法不能很好的进行评估;
  • 长尾类别,对长尾类别这种少量数据的编码会导致过拟合;

鉴于以上缺点的存在,一般会加入平滑来进行处理。

代码语言:javascript复制
encoding = weight * in_category   (1 - weight) * overall
weight = n / (n   m)

说了半天它的缺点和如何解决这些缺点,该方式的优点有哪些呢?

  • 高维数据特征:具有大量类别的可能很难编码:One-hot会生成太多维度,而替代方案(如标签编码)可能不适合该类型。Target encoding在此处就很好的解决了这个问题;
  • 领域经验特征:根据之前的经验,即使某项数据它在特征度量方面得分很低,你也可能会觉得一个分类特征应该很重要。Target encoding有助于揭示特征的真实信息。

Beta Target Encoding

在kaggle竞赛宝典中,有一篇《Kaggle Master分享编码神技-Beta Target Encoding》,很好的介绍了Beta Target Encoding,该编码方案来源于kaggle曾经的竞赛Avito Demand Prediction Challenge 第14名solution。从作者开源出来的代码,我们发现该编码和传统Target Encoding不一样。

  • Beta Target Encoding可以提取更多的特征,不仅仅是均值,还可以是方差等等;
  • 从作者的开源中,是没有进行N Fold提取特征的,所以可能在时间上提取会更快一些;

从作者的对比上我们可以看到,使用Beta Target Encoding相较于直接使用LightGBM建模的效果可以得到大幅提升。

01

Show me code

代码语言:javascript复制
class BetaTargetEncoder(object):
    def __init__(self, group):
        self.group = group
        self.stats = None
        self.whoami = "DOTA"
    # get counts from df
    def fit(self, df, target_col):
        # 先验均值
        self.prior_mean = np.mean(df[target_col]) 
        stats           = df[[target_col, self.group]].groupby(self.group)
        # count和sum
        stats           = stats.agg(['sum', 'count'])[target_col]    
        stats.rename(columns={'sum': 'n', 'count': 'N'}, inplace=True)
        stats.reset_index(level=0, inplace=True)           
        self.stats      = stats
        
    # extract posterior statistics
    def transform(self, df, stat_type, N_min=1):
        
        df_stats = pd.merge(df[[self.group]], self.stats, how='left')
        n        = df_stats['n'].copy()
        N        = df_stats['N'].copy()
        
        # fill in missing
        nan_indexs    = np.isnan(n)
        n[nan_indexs] = self.prior_mean
        N[nan_indexs] = 1.0
        
        # prior parameters
        N_prior     = np.maximum(N_min-N, 0)
        alpha_prior = self.prior_mean*N_prior
        beta_prior  = (1-self.prior_mean)*N_prior
        
        # posterior parameters
        alpha       =  alpha_prior   n
        beta        =  beta_prior    N-n
        
        # calculate statistics
        if stat_type=='mean':
            num = alpha
            dem = alpha beta
                    
        elif stat_type=='mode':
            num = alpha-1
            dem = alpha beta-2
            
        elif stat_type=='median':
            num = alpha-1/3
            dem = alpha beta-2/3
        
        elif stat_type=='var':
            num = alpha*beta
            dem = (alpha beta)**2*(alpha beta 1)
                    
        elif stat_type=='skewness':
            num = 2*(beta-alpha)*np.sqrt(alpha beta 1)
            dem = (alpha beta 2)*np.sqrt(alpha*beta)

        elif stat_type=='kurtosis':
            num = 6*(alpha-beta)**2*(alpha beta 1) - alpha*beta*(alpha beta 2)
            dem = alpha*beta*(alpha beta 2)*(alpha beta 3)
            
        # replace missing
        value = num/dem
        value[np.isnan(value)] = np.nanmedian(value)
        return value

K-Fold Target Encoding

在Target Encoding的基础上,K-Flod 目标编码的基本思想源自均值目标编码,在均值目标编码中,分类变量由对应于它们的目标均值替换。

01

Show me code

代码语言:javascript复制
class KFoldTargetEncoderTrain(base.BaseEstimator, base.TransformerMixin):
    def __init__(self,colnames,targetName,
                  n_fold=5, verbosity=True,
                  discardOriginal_col=False):
        self.colnames = colnames
        self.targetName = targetName
        self.n_fold = n_fold
        self.verbosity = verbosity
        self.discardOriginal_col = discardOriginal_col
        self.whoami = "DOTA"
    def fit(self, X, y=None):
        return self
    def transform(self,X):
        assert(type(self.targetName) == str)
        assert(type(self.colnames) == str)
        assert(self.colnames in X.columns)
        assert(self.targetName in X.columns)
        mean_of_target = X[self.targetName].mean()
        kf = KFold(n_splits = self.n_fold,
                   shuffle = False, random_state=2019)
        col_mean_name = self.colnames   '_'   'Kfold_Target_Enc'
        X[col_mean_name] = np.nan
        for tr_ind, val_ind in kf.split(X):
            X_tr, X_val = X.iloc[tr_ind], X.iloc[val_ind]
            X.loc[X.index[val_ind], col_mean_name] =    
            X_val[self.colnames].map(X_tr.groupby(self.colnames)
                                     [self.targetName].mean())
            X[col_mean_name].fillna(mean_of_target, inplace = True)
        if self.verbosity:
            encoded_feature = X[col_mean_name].values
            print('Correlation between the new feature, {} and, {} 
                   is {}.'.format(col_mean_name,self.targetName,                    
                   np.corrcoef(X[self.targetName].values,
                               encoded_feature)[0][1]))
        if self.discardOriginal_col:
            X = X.drop(self.targetName, axis=1)
        return X

参考资料

  • https://medium.com/@pouryaayria/k-fold-target-encoding-dfe9a594874b
  • https://www.kaggle.com/ryanholbrook/target-encoding
  • Kaggle Master分享编码神技-Beta Target Encoding

0 人点赞