1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        「類別型」特征分析方法總結(jié)

        共 10833字,需瀏覽 22分鐘

         ·

        2021-12-19 00:12

        本文系統(tǒng)梳理了9種類別型特征的編碼方法。如有不足,還望指正。





        一、背景

        當(dāng)我們預(yù)處理數(shù)據(jù)時,碰到類別型變量,需要將它們編碼轉(zhuǎn)換后才能輸入進(jìn)模型當(dāng)中。按照不同的劃分標(biāo)準(zhǔn),類別型變量有:

        按照類別是否有序:有序無序的類別特征。

        按照類別數(shù)量:高基類低基類的類別特征。


        針對不同的類別特征和任務(wù),可選的類別特征編碼方法也不一樣。本文主要介紹常見且好用的類別編碼方法,希望對大家有所幫助。


        二、方法

        1. 標(biāo)簽編碼(Label Encoder)

        標(biāo)簽編碼就是簡單地賦予不同類別,不同的數(shù)字標(biāo)簽。屬于硬編碼,優(yōu)點(diǎn)是簡單直白,網(wǎng)上很多說適用于有序類別型特征,不過如果是分類任務(wù)且類別不多的情況下,LGBM只要指定categorical_feature也能有較好的表現(xiàn)。但不建議用在高基類特征上,而且標(biāo)簽編碼后的自然數(shù)對于回歸任務(wù)來說是線性不可分的。

        from sklearn.preprocessing import LabelEncoderle = LabelEncoder()x = ['male', 'female', 'male']x_trans = le.fit_transform(x)
        >>>x_transarray([1, 0, 1], dtype=int64)


        2. 哈希編碼(Hash Encoder)

        哈希編碼是使用二進(jìn)制對標(biāo)簽編碼做哈希映射。好處在于哈希編碼器不需要維持類別字典,若后續(xù)出現(xiàn)訓(xùn)練集未出現(xiàn)的類別,哈希編碼也能適用。但按位分開哈希編碼,模型學(xué)習(xí)相對比較困難。

        # !pip install category_encodersimport category_encoders as cex = pd.DataFrame({'gender':[2, 1, 1]})ce_encoder = ce.HashingEncoder(cols = ['gender']).fit(x)x_trans = ce_encoder.transform(x)
        >>x_transcol_0 ?col_1 ?col_2 ?col_3 ?col_4 ?col_5 ?col_6 ?col_70 ? ? ?0 ? ? ?0 ? ? ?0 ? ? ?0 ? ? ?1 ? ? ?0 ? ? ?0 ? ? ?01 ? ? ?0 ? ? ?0 ? ? ?0 ? ? ?1 ? ? ?0 ? ? ?0 ? ? ?0 ? ? ?02 ? ? ?0 ? ? ?0 ? ? ?0 ? ? ?1 ? ? ?0 ? ? ?0 ? ? ?0 ? ? ?0


        3. 獨(dú)熱編碼(One-hot Encoder)

        獨(dú)熱編碼能很好解決標(biāo)簽編碼對于回歸任務(wù)中線性不可分的問題,它采用N位狀態(tài)寄存器來對N個狀態(tài)進(jìn)行編碼,簡單來說就是利用0和1表示類別狀態(tài),它轉(zhuǎn)換后的變量叫啞變量(dummy variables)。同樣地,它處理不好高基數(shù)特征,基類越大會帶來過很多列的稀疏特征,消耗內(nèi)存和訓(xùn)練時間。

        x = pd.DataFrame({'gender':['male', 'female', 'male']})x_dummies = pd.get_dummies(x['gender'])
        >>>x_dummiesfemale ?male0 ? ? ? 0 ? ? 11 ? ? ? 1 ? ? 02 ? ? ? 0 ? ? 1


        4. 計(jì)數(shù)編碼(Count Encoder)

        計(jì)數(shù)編碼也叫頻次編碼。就是用分類特征下不同類別的樣本數(shù)去編碼類別。清晰地反映了類別在數(shù)據(jù)集中的出現(xiàn)次數(shù),缺點(diǎn)是忽略類別的物理意義,比如說兩個類別出現(xiàn)頻次相當(dāng),但是在業(yè)務(wù)意義上,模型的重要性也許不一樣。

        import category_encoders as cedf = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A']})count_encoder = ce.count.CountEncoder(cols = ['cat_feat']).fit(df)df_trans = count_encoder.transform(df)
        >>df_transcat_feat0 ?41 ?42 ?23 ?44 ?25 ?4


        5. 直方圖編碼(Bin Encoder)

        直方圖編碼屬于目標(biāo)編碼的一種,適用于分類任務(wù)。它先將類別屬性分類,然后在對應(yīng)屬性下,統(tǒng)計(jì)不同類別標(biāo)簽的樣本占比進(jìn)行編碼。直方圖編碼能清晰看出特征下不同類別對不同預(yù)測標(biāo)簽的貢獻(xiàn)度[1],缺點(diǎn)在于:使用了標(biāo)簽數(shù)據(jù),若訓(xùn)練集和測試集的類別特征分布不一致,那么編碼結(jié)果容易引發(fā)過擬合。此外,直方圖編碼出的特征數(shù)量是分類標(biāo)簽的類別數(shù)量,若標(biāo)簽類別很多,可能會給訓(xùn)練帶來空間和時間上的負(fù)擔(dān)。直方圖編碼樣例如下圖所示:


        圖1:直方圖編碼


        import pandas as pd
        class hist_encoder: ? ?'''直方圖編碼器 ? ?@author: alvin ai ? ?params: ? ? ? ?df ? ? ? ? (pd.DataFrame): 待編碼的dataframe數(shù)據(jù) ? ? ? ?encode_feat_name ? ?(str): 編碼的類別特征名,當(dāng)前代碼只支持單個特征編碼,若要批量編碼,請自行實(shí)現(xiàn) ? ? ? ?label_name ? ? ? ? ?(str): 類別標(biāo)簽 ? ?''' ? ?def __init__(self, df, encode_feat_name, label_name): ? ? ? ?self.df = df.copy() ? ? ? ?self.encode_feat_name = encode_feat_name ? ? ? ?self.label_name = label_name
        ? ?def fit(self): ? ? ? ?'''用訓(xùn)練集獲取編碼字典''' ? ? ? ?# 分子:類別特征下給定類別,在不同分類標(biāo)簽下各類別的數(shù)量 ? ? ? ?self.df['numerator'] = 1 ? ? ? ?numerator_df = self.df.groupby([self.encode_feat_name, self.label_name])['numerator'].count().reset_index()
        ? ? ? ?# 分母:分類標(biāo)簽下各類別的數(shù)量 ? ? ? ?self.df['denumerator'] = 1 ? ? ? ?denumerator_df = self.df.groupby(self.encode_feat_name)['denumerator'].count().reset_index()
        ? ? ? ?# 類別特征類別、分類標(biāo)簽類別:直方圖編碼映射字典 ? ? ? ?encoder_df = pd.merge(numerator_df, denumerator_df, on = self.encode_feat_name) ? ? ? ?encoder_df['encode'] = encoder_df['numerator'] / encoder_df['denumerator']
        ? ? ? ?self.encoder_df = encoder_df[[self.encode_feat_name, self.label_name, 'encode']]
        ? ?def transform(self, test_df): ? ? ? ?'''對測試集編碼''' ? ? ? ?# 依次編碼出: hist特征1, hist特征2, ... ? ? ? ?test_trans_df = test_df.copy() ? ? ? ?for label_cat in test_trans_df[self.label_name].unique(): ? ? ? ? ? ?hist_feat = [] ? ? ? ? ? ?for cat_feat_val in test_trans_df[self.encode_feat_name].values: ? ? ? ? ? ? ? ?try: ? ? ? ? ? ? ? ? ? ?encode_val = encoder_df[(encoder_df[self.label_name] == label_cat) & (encoder_df[self.encode_feat_name] == cat_feat_val)]['encode'].item() ? ? ? ? ? ? ? ? ? ?hist_feat.append(encode_val) ? ? ? ? ? ? ? ?except: ? ? ? ? ? ? ? ? ? ?hist_feat.append(0) ? ? ? ? ? ?encode_fname = self.encode_feat_name + '_en{}'.format(str(label_cat)) # 針對類別特征-類別label_cat的直方圖編碼特征名 ? ? ? ? ? ?test_trans_df[encode_fname] = hist_feat # 將編碼的特征加入到原始數(shù)據(jù)中 ? ? ? ? ? ?return test_trans_df
        # 初始化數(shù)據(jù)df = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A'], 'label':[0, 1, 0, 2, 1, 2]})encode_feat_name = 'cat_feat'label_name = 'label'
        # 直方圖編碼he = hist_encoder(df, encode_feat_name, label_name)he.fit()df_trans = he.transform(df)
        >>dfcat_feat ?label0 ?A ?01 ?A ?12 ?B ?03 ?A ?24 ?B ?15 ?A ?2
        >>df_transcat_feat ?label ?cat_feat_en0 ?cat_feat_en1 ?cat_feat_en20 ?A ?0 ?0.25 ?0.25 ?0.51 ?A ?1 ?0.25 ?0.25 ?0.52 ?B ?0 ?0.50 ?0.50 ?0.03 ?A ?2 ?0.25 ?0.25 ?0.54 ?B ?1 ?0.50 ?0.50 ?0.05 ?A ?2 ?0.25 ?0.25 ?0.5


        6. WOE編碼

        WOE(Weight of Evidence,證據(jù)權(quán)重)編碼適用于二分類任務(wù),WOE表明自變量相對于因變量的預(yù)測能力。由于它是從信用評分世界演變而來的,它通常被描述為區(qū)分好客戶和壞客戶的衡量標(biāo)準(zhǔn)?!皦目蛻簟笔侵竿锨焚J款的客戶。和“優(yōu)質(zhì)客戶”指的是誰償還貸款的客戶。[8]

        其中,參數(shù)解釋如下:

        ??:分組的數(shù)量。

        ??:組內(nèi)違約用戶數(shù)占比(即組內(nèi)label=1的樣本數(shù)占比)。

        ??:組內(nèi)正常用戶數(shù)占比(即組內(nèi)label=0的樣本數(shù)占比)。

        ??:組內(nèi)違規(guī)用戶數(shù)/所有違規(guī)用戶數(shù)。

        ???:組內(nèi)正常用戶數(shù)/所有正常用戶數(shù)。

        透過公式,我們可以把WOE理解成:每個分組內(nèi)壞客戶分布相對于優(yōu)質(zhì)客戶分布之間的差異性。


        據(jù)知乎主@馬東什么[3]指出,WOE存在幾個問題:

        (1) 分母可能為0.

        (2) 沒有考慮不同類別數(shù)量的大小帶來的影響,可能某類數(shù)量多,但最后計(jì)算出的WOE跟某樣本數(shù)量少的類別的WOE一樣。

        (3) 只針對二分類問題。

        (4) 訓(xùn)練集和測試集可能存在WOE編碼差異(通?。?/span>


        對于問題1,源碼[4]加入regularization(默認(rèn)值為1)。

        # Create a new column with regularized WOE.# Regularization helps to avoid division by zero.# Pre-calculate WOEs because logarithms are slow.nominator = (stats['sum'] + self.regularization) / (self._sum + 2*self.regularization)denominator = ((stats['count'] - stats['sum']) + self.regularization) / (self._count - self._sum + 2*self.regularization)woe = np.log(nominator / denominator)


        對于問題2,可以考慮使用IV(Information Value),可以看作對WOE的加權(quán),公式如下:?WOE和IV的區(qū)別和聯(lián)系[2]是:

        (1) WOE describes the relationship?between a predictive variable and a binary target variable.

        (2) IV measures the strength?of that relationship.


        擴(kuò)展:IV常會被用來評估變量的預(yù)測能力,用于篩選變量:


        圖2:不同IV值的預(yù)測性


        對于問題3,可以考慮借鑒直方圖編碼的思路,將多分類標(biāo)簽,獨(dú)熱后依次進(jìn)行WOE編碼。[3],而對于問題4,暫時無解。

        from category_encoders import WOEEncoderimport pandas as pd
        df = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A'], 'label':[0, 1, 0, 1, 1, 1]})enc = WOEEncoder(cols=['cat_feat']).fit(df, df['label'])df_trans = enc.transform(df)
        >>df_transcat_feat ?label0 ?0.287682 ?01 ?0.287682 ?12 ?-0.405465 ?03 ?0.287682 ?14 ?-0.405465 ?15 ?0.287682 ?1


        7. 目標(biāo)編碼(Target Encoder)

        2001年Micci等人提出的目標(biāo)編碼[5],是一種有監(jiān)督編碼方法,適用于分類和回歸任務(wù)中,高基類無序類別特征。


        編碼略顯復(fù)雜,這里以分類任務(wù)為例,假設(shè)我們有類別型特征??,分類標(biāo)簽??。其中??下有??個類別,??有??個類別?;跀?shù)據(jù),我們可以計(jì)算出先驗(yàn)概率??和后驗(yàn)概率??,??和??分別為分類標(biāo)簽??和類別特征??下的第??和第??個類別。這樣我們能通過后驗(yàn)概率直接編碼,如下圖所示:


        圖3:后驗(yàn)概率編碼


        使用后驗(yàn)概率編碼后新增的c-1列之間是線性相關(guān)的,會帶來多重共線性問題。為此,目標(biāo)編碼結(jié)合了前驗(yàn)概率和后驗(yàn)概率去估算新的概率編碼:?對于回歸問題,只需要將前面的概率換成均值即可,公式如下:?從上面公式可知,如果測試集出現(xiàn)新的特征類別,那后驗(yàn)概率為0,只有先驗(yàn)概率,此時??。權(quán)重??的計(jì)算方式如下:?

        源碼[6]如下:

        # 默認(rèn)參數(shù):min_samples_leaf=1, smoothing=1.0smoove = 1 / (1 + np.exp(-(stats['count'] - self.min_samples_leaf) / self.smoothing))smoothing = prior * (1 - smoove) + stats['mean'] * smoovesmoothing[stats['count'] == 1] = prior


        其中,默認(rèn)值:??和??。當(dāng)??時,??,當(dāng)??時,??。具體參數(shù)解釋如下:

        ??:min_samples_leaf,決定在cell內(nèi)樣本數(shù)至少為多少,我們才能完全相信它的估計(jì)。

        ??:特征類別在訓(xùn)練集中的出現(xiàn)次數(shù)。因此當(dāng)類別特征中某個類別出現(xiàn)次數(shù)越多,后驗(yàn)概率可信度越高,權(quán)重??會越大。

        ??:控制了函數(shù)??在拐點(diǎn)附近的斜率,??越大,坡度越緩。當(dāng)??時,不同??值下的??如下圖所示。


        圖4:當(dāng)k=1時,不同f值下的\lambda(n)


        目標(biāo)編碼的好處是結(jié)合了先驗(yàn)概率和后驗(yàn)概率去編碼,但由于概率是直接使用標(biāo)簽數(shù)據(jù)計(jì)算得到的,所以會引發(fā)過擬合問題。

        from category_encoders import TargetEncoderimport pandas as pd
        df = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A'], 'label':[0, 1, 0, 1, 1, 1]})enc = TargetEncoder(cols=['cat_feat']).fit(df, df['label'])df_trans = enc.transform(df)
        >>df_transcat_feat ?label0 ?0.746048 ? ? ?01 ?0.746048 ? ? ?12 ?0.544824 ? ? ?03 ?0.746048 ? ? ?14 ?0.544824 ? ? ?15 ?0.746048 ? ? ?1


        8. 平均編碼(Mean Encoder)

        平均編碼是基于目標(biāo)編碼的改進(jìn)版。它的2點(diǎn)改動如下:

        (1) 權(quán)重公式:其實(shí)沒有本質(zhì)上的區(qū)別,可自行修改函數(shù)內(nèi)的參數(shù)。

        '''param prior_weight_func:a function that takes in the number of observations,and outputs prior weight when a dict is passed,the default exponential decay function will be used: ?k: the number of observations needed for the posterior to be weighted equally as the prior ?f: larger f --> smaller slope'''self.prior_weight_func = eval('lambda x: 1 / (1 + np.exp((x - k) / f))', dict(prior_weight_func, np=np))


        (2) 由于目標(biāo)編碼使用了標(biāo)簽,為了緩解編碼帶來模型過擬合問題,平均編碼加入了K-fold編碼思路,若分為5折,則用1-4折先fit后,再transform第5折,依次類推,將類別特征分5次編碼出來。壞處是耗時。

        # :param n_splits: the number of splits used in mean encoding. 默認(rèn)n_splits=5for variable, target in product(self.categorical_features, self.target_values): ? ?nf_name = '{}_pred_{}'.format(variable, target) ? ? X_new.loc[:, nf_name] = np.nan ? ?for large_ind, small_ind in skf.split(y, y): ? ? ? ?nf_large, nf_small, prior, col_avg_y = MeanEncoder.mean_encode_subroutine(X_new.iloc[large_ind],y.iloc[large_ind],X_new.iloc[small_ind],variable, target, self.prior_weight_func) ? ? ? ?X_new.iloc[small_ind, -1] = nf_small ? ? ? ?self.learned_stats[nf_name].append((prior, col_avg_y))


        代碼過長,這里不予展示,詳情請見[7]

        # 代碼過長,這里不予展示,詳情請見[7]class MeanEncoder...
        df = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A', 'A', 'A', 'B', 'A', 'B', 'A', 'A', 'A', 'B', 'A', 'B', 'A'], 'label':[0, 1, 0, 2, 1, 2, 0, 1, 0, 2, 1, 2, 0, 1, 0, 2, 1, 2]})me = MeanEncoder(categorical_features = ['cat_feat'])x_trans = me.fit_transform(df, df['label'])
        >>x_transcat_feat ?label ?cat_feat_pred_0 ?cat_feat_pred_1 ?cat_feat_pred_20 ?A ?0 ?0.222280 ?0.222345 ?0.5553751 ?A ?1 ?0.222280 ?0.222345 ?0.5553752 ?B ?0 ?0.394580 ?0.588482 ?0.0169383 ?A ?2 ?0.222280 ?0.222345 ?0.5553754 ?B ?1 ?0.588482 ?0.394580 ?0.0169385 ?A ?2 ?0.222345 ?0.222280 ?0.5553756 ?A ?0 ?0.222345 ?0.222280 ?0.555375...


        9. 模型編碼(Model Encoder)

        之前我寫過文章【務(wù)實(shí)基礎(chǔ)】CatBoost,里面有提到模型自帶的類別特征編碼。目前GBDT模型中,只有LGBM和CatBoost自帶類別編碼。LGBM的類別編碼采用的是GS編碼(Gradient Statistics),將類別特征轉(zhuǎn)為累積值??(一階偏導(dǎo)數(shù)之和/二階偏導(dǎo)數(shù)之和)再進(jìn)行直方圖特征排序。使用起來也很簡單,定義lgb數(shù)據(jù)集時,指定categorical_feature。

        train_data = lgb.Dataset(data, label=label, feature_name=['c1', 'c2', 'c3'], categorical_feature=['c3'])


        據(jù)官方文檔介紹,GS編碼比獨(dú)熱編碼快大概8倍速度。而且文檔里也建議,當(dāng)類別變量為高基類時,哪怕是簡單忽略類別含義或把它嵌入到低維數(shù)值空間里,只要將特征轉(zhuǎn)為數(shù)值型,一般會表現(xiàn)的比較好。就個人使用來講,我一般會對無序類別型變量進(jìn)行模型編碼,有序類別型變量直接按順序標(biāo)簽編碼即可。


        雖然LGBM用GS編碼類別特征看起來挺厲害的,但是存在兩個問題:

        計(jì)算時間長:因?yàn)槊枯喍家獮槊總€類別值進(jìn)行GS計(jì)算。

        內(nèi)存消耗大:對于每次分裂,都存儲給定類別特征下,它不同樣本劃分到不同葉節(jié)點(diǎn)的索引信息。


        所以CatBoost使用Ordered TS編碼,既利用了TS省空間和速度的優(yōu)勢,也使用Ordered的方式緩解預(yù)測偏移問題。詳情可見我歷史文章。


        圖5:Ordered TS示意圖


        三、總結(jié)

        我這里總結(jié)了以上類別編碼方法的區(qū)別:


        圖6:類別特征編碼總結(jié)


        總結(jié)來說,關(guān)于類別特征,有以下心得:

        (1) 統(tǒng)計(jì)類編碼常常不適用于小樣本,因?yàn)榻y(tǒng)計(jì)意義不明顯。

        (2) 當(dāng)訓(xùn)練集和測試集分布不一致時,統(tǒng)計(jì)類編碼往往會有預(yù)測偏移問題,所以一般會考慮結(jié)合交叉驗(yàn)證。

        (3) 編碼后特征數(shù)變多的編碼方法,不適用于高基類的特征,會帶來稀疏性和訓(xùn)練成本。

        (4) 沒有完美的編碼方法,但感覺標(biāo)簽編碼、平均編碼、WOE編碼和模型編碼比較常用。


        參考資料

        [1] 特征工程之Histogram編碼, 博文: https://blog.csdn.net/Chengliangyao/article/details/82623775

        [2] 風(fēng)控模型—WOE與IV指標(biāo)的深入理解應(yīng)用 - 求是汪在路上, 知乎: https://zhuanlan.zhihu.com/p/80134853

        [3] 特征編碼方法總結(jié)—part1 - 馬東什么, 知乎: https://zhuanlan.zhihu.com/p/67475635

        [4] woe.py, 源碼: https://github.com/scikit-learn-contrib/category_encoders/blob/master/category_encoders/woe.py

        [5] Micci-Barreca, D. (2001). A preprocessing scheme for high-cardinality categorical attributes in classification and prediction problems. *ACM SIGKDD Explorations Newsletter*, *3*(1), 27-32.

        [6] target_encoder - category_encoders, 源碼: http://contrib.scikit-learn.org/category_encoders/_modules/category_encoders/target_encoder.html#TargetEncoder

        [7] 平均數(shù)編碼:針對某個分類特征類別基數(shù)特別大的編碼方式, 博文: https://www.cnblogs.com/wzdLY/p/9639519.html

        [8] 證據(jù)權(quán)重 (WOE) 和信息價值 (IV) - python風(fēng)控模型, 知乎: https://zhuanlan.zhihu.com/p/389734858

        瀏覽 126
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            又黄又爽在线观看 | 欧美日韩一级视频 | 插嫩逼视频 | 成人免费一级电影 | 日韩精品人妻在线高清不卡一区二区 | 日本一级无码视频 | 青青草久 | 偷拍自拍在线看 | 亚洲精品在线观看视频 | 国产成人在线免费观看 |