kaggle符号化categorical featureまとめ

32327 ワード

kaggleコンテストは本質的にコースのコンテストです.この文章はkaggleコンテストにおけるcategorical featureの一般的な処理方法について述べ,主にツリーモデル(lightgbm,xgboost,etc.)に基づいている.ポイントはtarget encodingとbeta target encodingです.
まとめ:
  • label encoding
  • の特徴は内在順序
  • に存在する.
  • one hot encoding
  • 特徴内在順序なしcategory数<4
  • target encoding (mean encoding, likelihood encoding, impact encoding)
  • フィーチャーに内在的な順序はありません.category数>4
  • beta target encoding
  • 特徴内在順序なし、category数>4、K-fold cross validation
  • 処理しない(モデル自動符号化)
  • CatBoost,lightgbm


  • \1. Label encoding
    m個のcategoryの特徴がある場合、label encodingを通過すると、各categoryは0からm-1の間の数にマッピングされます.Label encodingはordinal feature(特徴に内在的な順序がある)に適している.
    コード:
    # train -> training dataframe
    # test -> test dataframe
    # cat_cols -> categorical columns
    
    for col in cat_cols:
        le = LabelEncoder()
        le.fit(np.concatenate([train[col], test[col]]))
        train[col] = le.transform(train[col])
        test[col] = le.transform(test[col])
    

    \2. One-hot encoding (OHE)
    m個のcategoryを有する特徴については、独熱符号化(OHE)処理後、m個の二元特徴となり、各特徴は1個のcategoryに対応する.このm個の二元特性は反発し,毎回1つだけ活性化した.
    独熱符号化は、元の特徴に内在的な順序が欠けているという問題を解決するが、欠点はhigh-cardinality categorical feature(categoryの数が多い)に対して、符号化後の特徴空間が大きすぎる(ここではPCAの次元ダウンを考慮することができる)、one-hot featureがunbalancedと比較されるため、ツリーモデルでは毎回の切り分け利得が小さく、ツリーモデルは通常grow very deepを必要とし、良好な精度を得ることができる.従ってOHEはcategory数<4の場合に一般的に用いられる.
    参考:Using Categorical Data with One Hot Encoding
    コード:
    # train -> training dataframe
    # test -> test dataframe
    # cat_cols -> categorical columns
    
    df = train.append(test).reset_index()
    original_column = list(df.columns)
    df = pd.get_dummies(df, columns = cat_cols, dummy_na = True)
    new_column = [c for c in df.columns if c not in original_column ]
    

    \3. Target encoding (or likelihood encoding, impact encoding, mean encoding)
    Target encodingはtarget mean value(among each category)を用いてcategorical featureを符号化する.target variable leakを減らすために、主流の方法は2 levels of cross-validationを用いてtarget meanを求めることであり、構想は以下の通りである.
  • train dataを20-folds(例:infold:fold 2-20,out of fold:fold 1)
  • に分割する.
  • は、各infold(fold 2-20)を再び10-folds(例:inner_infold:fold 2-10、Inner_oof:fold 1)
  • に分割する.
  • 10-foldsのinner out of folds値(例:inner_infold#2-10のtargetの平均値をinner_oof#1の予測値として使用する)
  • を計算する.
  • 10個のinner out of folds値を平均してinner_を得るoof_mean

  • 計算oof_mean(例:infold#2-20のinner_oof_meanを使用してout of fold#1のoof_mean
  • を予測する.
  • train dataのoof_meanマッピングtest data完了符号化
  • 参考:Likelihood encoding of categorical features
    open source package category_encoders: scikit-learn-contrib/categorical-encoding
    コード:
    # train -> training dataframe
    # test -> test dataframe
    
    n_folds = 20
    n_inner_folds = 10
    likelihood_encoded = pd.Series()
    likelihood_coding_map = {}
    
    oof_default_mean = train[target].mean()      # global prior mean
    kf = KFold(n_splits=n_folds, shuffle=True)
    oof_mean_cv = pd.DataFrame()
    split = 0
    
    for infold, oof in kf.split(train[feature]):
        print ('==============level 1 encoding..., fold %s ============' % split)
        inner_kf = KFold(n_splits=n_inner_folds, shuffle=True)
        inner_oof_default_mean = train.iloc[infold][target].mean()
        inner_split = 0
        inner_oof_mean_cv = pd.DataFrame()
    
        likelihood_encoded_cv = pd.Series()
        for inner_infold, inner_oof in inner_kf.split(train.iloc[infold]):
            print ('==============level 2 encoding..., inner fold %s ============' % inner_split)
            # inner out of fold mean
            oof_mean = train.iloc[inner_infold].groupby(by=feature)[target].mean()
            # assign oof_mean to the infold
            likelihood_encoded_cv = likelihood_encoded_cv.append(train.iloc[infold].apply(
                lambda x : oof_mean[x[feature]]
                if x[feature] in oof_mean.index
                else inner_oof_default_mean, axis = 1))
            inner_oof_mean_cv = inner_oof_mean_cv.join(pd.DataFrame(oof_mean), rsuffix=inner_split, how='outer')
            inner_oof_mean_cv.fillna(inner_oof_default_mean, inplace=True)
            inner_split += 1
        
        oof_mean_cv = oof_mean_cv.join(pd.DataFrame(inner_oof_mean_cv), rsuffix=split, how='outer')
        oof_mean_cv.fillna(value=oof_default_mean, inplace=True)
        split += 1
        print ('============final mapping...===========')
        likelihood_encoded = likelihood_encoded.append(train.iloc[oof].apply(
            lambda x: np.mean(inner_oof_mean_cv.loc[x[feature]].values)
            if x[feature] in inner_oof_mean_cv.index
            else oof_default_mean, axis=1))
    
    ######################################### map into test dataframe
    train[feature] = likelihood_encoded
    likelihood_coding_mapping = oof_mean_cv.mean(axis = 1)
    default_coding = oof_default_mean
    
    likelihood_coding_map[feature] = (likelihood_coding_mapping, default_coding)
    mapping, default_mean = likelihood_coding_map[feature]
    test[feature] = test.apply(lambda x : mapping[x[feature]]
                                           if x[feature] in mapping
                                           else default_mean,axis = 1)
    

    \4. beta target encoding
    初めてこの方法を見たのはkaggleコンテストAvito Demand Prediction Challenge 14位のsolution分かち合い:14 th Place Solution:The Almost Golden Defenders
    target encodingと同様に、beta target encodingもtarget mean value(among each category)を用いてcategorical featureを符号化する.異なる点は、target variable leakをさらに低減するために、beta target encodingは、5-fold CVの前ではなく、5-fold CVの内部で発生することである.
  • train dataを5-folds(5-fold cross validation)
  • に分割する.
  • target encoding based on infold data
  • train model
  • get out of fold prediction


  • 同時にbeta target encodingはsmoothing termに加入し、meanの代わりにbayesian meanを使用した.Bayesian mean(Bayesian average)の考え方:あるcategoryがデータ量が少ない場合(
    参考:Beta Target Encoding
    コード:
    # train -> training dataframe
    # test -> test dataframe
    # N_min -> smoothing term, minimum sample size, if sample size is less than N_min, add up to N_min 
    # target_col -> target column
    # cat_cols -> categorical colums
    # Step 1: fill NA in train and test dataframe
    
    # Step 2: 5-fold CV (beta target encoding within each fold)
    
    kf = KFold(n_splits=5, shuffle=True, random_state=0)
    for i, (dev_index, val_index) in enumerate(kf.split(train.index.values)):
        # split data into dev set and validation set
        dev = train.loc[dev_index].reset_index(drop=True) 
        val = train.loc[val_index].reset_index(drop=True)
            
        feature_cols = []    
        for var_name in cat_cols:
            feature_name = f'{var_name}_mean'
            feature_cols.append(feature_name)
            
            prior_mean = np.mean(dev[target_col])
            stats = dev[[target_col, var_name]].groupby(var_name).agg(['sum', 'count'])[target_col].reset_index()           
       
            ### beta target encoding by Bayesian average for dev set 
            df_stats = pd.merge(dev[[var_name]], stats, how='left')
            df_stats['sum'].fillna(value = prior_mean, inplace = True)
            df_stats['count'].fillna(value = 1.0, inplace = True)
            N_prior = np.maximum(N_min - df_stats['count'].values, 0)   # prior parameters
            dev[feature_name] = (prior_mean * N_prior + df_stats['sum']) / (N_prior + df_stats['count']) # Bayesian mean
    
            ### beta target encoding by Bayesian average for val set
            df_stats = pd.merge(val[[var_name]], stats, how='left')
            df_stats['sum'].fillna(value = prior_mean, inplace = True)
            df_stats['count'].fillna(value = 1.0, inplace = True)
            N_prior = np.maximum(N_min - df_stats['count'].values, 0)   # prior parameters
            val[feature_name] = (prior_mean * N_prior + df_stats['sum']) / (N_prior + df_stats['count']) # Bayesian mean
            
            ### beta target encoding by Bayesian average for test set
            df_stats = pd.merge(test[[var_name]], stats, how='left')
            df_stats['sum'].fillna(value = prior_mean, inplace = True)
            df_stats['count'].fillna(value = 1.0, inplace = True)
            N_prior = np.maximum(N_min - df_stats['count'].values, 0)   # prior parameters
            test[feature_name] = (prior_mean * N_prior + df_stats['sum']) / (N_prior + df_stats['count']) # Bayesian mean
            
            # Bayesian mean is equivalent to adding N_prior data points of value prior_mean to the data set.
            del df_stats, stats
        # Step 3: train model (K-fold CV), get oof prediction
    

    また、target encodingおよびbeta target encodingでは、target mean(or bayesian mean)を使用する必要はありません.medium、frqequency、mode、variance、skewness、and kurtosis–またはtargetとcorrelationのある集計値を含む他の統計値を使用することもできます.
    5.何も処理しない(モデル自動符号化)
  • XgBoostとRandom Forestは、categorical featureを直接処理することはできません.numerical featureに符号化する必要があります.
  • lightgbmとCatBoostは、categorical featureを直接処理することができます.
  • lightgbm:label encodingを先にする必要があります.特定のアルゴリズム(On Group for Maximum Homogeneity)でoptimal splitを見つけ、ONEよりも効果的です.one-hot encodingを採用することもできます.Features - LightGBM documentation
  • CatBoost:label encodingを先にする必要はありません.one-hot encoding,target encoding(with regularization)を選択できます.CatBoost — Transforming categorical features to numerical features — Yandex Technologies


  • 参照先:https://towardsdatascience.com/catboost-vs-light-gbm-vs-xgboost-5f93620723db