樹模型遇上類別型特征(Python)
在數(shù)據(jù)挖掘項(xiàng)目的數(shù)據(jù)中,數(shù)據(jù)類型可以分為兩種:有序的連續(xù)數(shù)值 和 無序的類別型特征。
對于xgboost、GBDT等boosting樹模型,基學(xué)習(xí)通常是cart回歸樹,而cart樹的輸入通常只支持連續(xù)型數(shù)值類型的,像年齡、收入等連續(xù)型變量Cart可以很好地處理,但對于無序的類別型變量(如 職業(yè)、地區(qū)等),cart樹處理就麻煩些了,如果是直接暴力地枚舉每種可能的類別型特征的組合,這樣找類別特征劃分點(diǎn)計(jì)算量也很容易就爆了。
在此,本文列舉了 樹模型對于類別型特征處理的常用方法,并做了深入探討~
一、one-hot編碼處理
我們可以直接對類別型特征做Onehot處理(這也是最常用的做法),每一類別的取值都用單獨(dú)一位0/1來表示, 也就是一個(gè)“性別”類別特征可以轉(zhuǎn)換為是否為“男”、“女” 或者“其他” 來表示,如下:
display(df.loc[:,['Gender_Code']].head())
#?onehot?
pd.get_dummies(df['Gender_Code']).head()
但是onehot的重大缺點(diǎn)在于,對于取值很多的類別型特征,可能導(dǎo)致高維稀疏特征而容易導(dǎo)致樹模型的過擬合。如之前談到面對高維稀疏的onehot特征,一旦有達(dá)到劃分條件,樹模型容易加深,切分次數(shù)越多,相應(yīng)每個(gè)切分出的子特征空間的統(tǒng)計(jì)信息越來越小,學(xué)習(xí)到的可能只是噪音(即 過擬合)。
使用建議:Onehot天然適合神經(jīng)網(wǎng)絡(luò)模型,神經(jīng)網(wǎng)絡(luò)很容易從高維稀疏特征學(xué)習(xí)到低微稠密的表示。當(dāng)onehot用于樹模型時(shí),類別型特征的取值數(shù)量少的時(shí)候還是可以學(xué)習(xí)到比較重要的交互特征,但是當(dāng)取值很多時(shí)候(如 大于100),容易導(dǎo)致過擬合,是不太適合用onehot+樹模型的。
(注:此外 onehot 還有增加內(nèi)存開銷以及訓(xùn)練時(shí)間開銷等缺點(diǎn))
二、 ?Ordinal Encoder
OrdinalEncoder也稱為順序編碼 (與 label encoding,兩者功能基本一樣),特征/標(biāo)簽被轉(zhuǎn)換為序數(shù)整數(shù)(0 到 n_categories - 1)
使用建議:適用于ordinal feature ,也就是雖然類別型特征,但它存在內(nèi)在順序,比如衣服尺寸“S”,“M”, “L”等特征就適合從小到大進(jìn)行整數(shù)編碼。
from?sklearn.preprocessing?import??LabelEncoder
?encoder?=?LabelEncoder()
?df[col]?=?encoder.transform(df[col])
三、target encoding
target encoding 目標(biāo)編碼也稱為均值編碼,是借助各類別特征對應(yīng)的標(biāo)簽信息做編碼(比如二分類 簡單以類別特征各取值 的樣本對應(yīng)標(biāo)簽值“0/1”的平均值),是一種常用有監(jiān)督編碼方法(此外還有經(jīng)典的WoE編碼),很適合邏輯回歸等弱模型使用。
使用建議 : 當(dāng)樹模型使用目標(biāo)編碼,需加入些正則化技巧,減少Target encoding方法帶來的條件偏移的現(xiàn)象(當(dāng)訓(xùn)練數(shù)據(jù)集和測試數(shù)據(jù)集數(shù)據(jù)結(jié)構(gòu)和分布不一樣的時(shí)候會(huì)出條件偏移問題),主流的方法是使用Catboost編碼 或者 使用cross-validation求出target mean或bayesian mean。

#?如下簡單的target mean代碼。也可以用:from category_encoders import TargetEncoder
target_encode_columns?=?['Gender_Code']
target?=?['y']
target_encode_df?=?score_df[target_encode_columns?+?target].reset_index().drop(columns?=?'index',?axis?=?1)
target_name?=?target[0]
target_df?=?pd.DataFrame()
for?embed_col?in?target_encode_columns:
????val_map?=?target_encode_df.groupby(embed_col)[target].mean().to_dict()[target_name]
????target_df[embed_col]?=?target_encode_df[embed_col].map(val_map).values
????
score_target_drop?=?score_df.drop(target_encode_columns,?axis?=?1).reset_index().drop(columns?=?'index',?axis?=?1)
score_target?=?pd.concat([score_target_drop,?target_df],?axis?=?1)
四、CatBoostEncoder
CatBoostEncoder是CatBoost模型處理類別變量的方法(Ordered TS編碼),在于目標(biāo)編碼的基礎(chǔ)上減少條件偏移。其計(jì)算公式為:
TargetCount : 對于指定類別特征在target value的總和 prior:對于整個(gè)數(shù)據(jù)集而言,target值的總和/所有的觀測變量數(shù)目 FeatureCount:觀測的特征列表在整個(gè)數(shù)據(jù)集中的出現(xiàn)次數(shù)。
CBE_encoder?=?CatBoostEncoder()
train_cbe?=?CBE_encoder.fit_transform(train[feature_list],?target)
test_cbe?=?CBE_encoder.transform(test[feature_list])
五、CountEncoder
也稱為頻數(shù)編碼,將類別特征各取值轉(zhuǎn)換為其在訓(xùn)練集出現(xiàn)的頻率,這樣做直觀上就是會(huì)以類別取值的頻次為依據(jù) 劃分高頻類別和低頻類別。至于效果,還是要結(jié)合業(yè)務(wù)和實(shí)際場景。
##?也可以直接?from?category_encoders?import??CountEncoder
bm?=?[]
tmp_df=train_df
for?k?in?catefeas:
????t?=?pd.DataFrame(tmp_df[k].value_counts(dropna=True,normalize=True))?#?頻率
????t.columns=[k+'vcount']
????bm.append(t)
for?k,j?in?zip(catefeas,?range(len(catefeas))):#?聯(lián)結(jié)編碼
????df?=?df.merge(bm[j],?left_on=k,?right_index=True,how='left')
六、 神經(jīng)網(wǎng)絡(luò)embedding
當(dāng)類別的取值數(shù)量很多時(shí)(onehot高維),如果直接onehot,從性能或效果來看都會(huì)比較差,這時(shí)通過神經(jīng)網(wǎng)絡(luò)embedding是不錯(cuò)的方法,將類別變量onehot輸入神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)一個(gè)低維稠密的向量,如經(jīng)典的無監(jiān)督詞向量表征學(xué)習(xí)word2vec 或者 基于有監(jiān)督神經(jīng)網(wǎng)絡(luò)編碼。
使用建議:特別適合類別變量取值很多,onehot后高維稀疏,再做NN低維表示轉(zhuǎn)換后應(yīng)用于樹模型。
#?word2vec
from?gensim.models?import?word2vec
#?加載數(shù)據(jù)
raw_sentences?=?["the?quick?brown?fox?jumps?over?the?lazy?dogs","yoyoyo?you?go?home?now?to?sleep"]
#?切分詞匯
sentences=?[s.encode('utf-8').split()?for?s?in?sentences]
#?構(gòu)建模型
model?=?word2vec.Word2Vec(sentences,size=10)??#?詞向量的維數(shù)為10
#??各單詞學(xué)習(xí)的詞向量
model['dogs']??
#?array([-0.00449447,?-0.00310097,??0.02421786,?...],?dtype=float32)
七、lgb類別特征處理
為了解決one-hot編碼(one vs many )處理類別特征的不足。lgb采用了Many vs many的切分方式,簡單來說,是通過對每個(gè)類別取值進(jìn)行數(shù)值編碼(類似于目標(biāo)編碼),根據(jù)編碼的數(shù)值尋找較優(yōu)切分點(diǎn),實(shí)現(xiàn)了類別特征集合的較優(yōu)切分。
具體算法原理:
1 、特征取值數(shù)目小于等于4(參數(shù)max_cat_to_onehot):直接onehot 編碼,逐個(gè)掃描每一個(gè)bin容器,找出最佳分裂點(diǎn);
2、 特征取值數(shù)目大于4:max bin的默認(rèn)值是256 取值的個(gè)數(shù)大于max bin數(shù)時(shí),會(huì)篩掉出現(xiàn)頻次少的取值。再統(tǒng)計(jì)各個(gè)特征值對應(yīng)的樣本的一階梯度之和,二階梯度之和,以一階梯度之和 / (二階梯度之和 + 正則化系數(shù))作為該特征取值的編碼。將類別轉(zhuǎn)化為數(shù)值編碼后,從大到小排序,遍歷直方圖尋找最優(yōu)的切分點(diǎn)
簡單來說,Lightgbm利用梯度統(tǒng)計(jì)信息對類別特征編碼。我個(gè)人的理解是這樣可以按照學(xué)習(xí)的難易程度為依據(jù)劃分類別特征組,比如某特征一共有【狼、狗、貓、豬、兔】五種類別取值,而【狼、狗】類型下的樣本分類難度相當(dāng)高(該特征取值下的梯度大),在梯度編碼后的類別特征上,尋找較優(yōu)劃分點(diǎn)可能就是【狼、狗】|vs|【貓、豬、兔】

# lgb類別處理:簡單轉(zhuǎn)化為類別型特征直接輸入Lgb模型訓(xùn)練即可。
?for?ft?in?category_list:
????????train_x[ft]?=?train_x[ft].astype('category')
clf?=?LGBMClassifier(**best_params)
clf.fit(train_x,?train_y)
經(jīng)驗(yàn)小結(jié)
對于取值數(shù)量很少(<10)的類別型特征,相應(yīng)的各取值下的樣本數(shù)量也比較多,可以直接Onehot編碼。
對于取值數(shù)量比較多(10到幾百),這時(shí)onehot從效率或者效果,都不及l(fā)ightgbm梯度編碼或catboost目標(biāo)編碼,而且直接使用也很方便。(需要注意的是,個(gè)人實(shí)踐中這兩種方法在很多取值的類別特征,還是比較容易過擬合。這時(shí),類別值先做下經(jīng)驗(yàn)的合并或者嘗試剔除某些類別特征后,模型效果反而會(huì)更好)
當(dāng)幾百上千的類別取值,可以先onehot后(高維稀疏),借助神經(jīng)網(wǎng)絡(luò)模型做低維稠密表示。
-?推薦閱讀-
深度學(xué)習(xí)系列機(jī)器學(xué)習(xí)系列
文末,粉絲福利來了!!關(guān)注【算法進(jìn)階】??
后臺(tái)回復(fù)【課程】,即可免費(fèi)領(lǐng)取Python|機(jī)器學(xué)習(xí)|AI 精品課程大全

機(jī)??器學(xué)習(xí)算法交流群,邀您加入?。。?/span>
入群:提問求助、認(rèn)識(shí)行業(yè)內(nèi)同學(xué)、交流進(jìn)步、共享資源...
掃描??下方二維碼,備注“加群”

