【機器學(xué)習(xí)】樹模型遇上類別型特征(Python)
在數(shù)據(jù)挖掘項目的數(shù)據(jù)中,數(shù)據(jù)類型可以分為兩種:有序的連續(xù)數(shù)值 和 無序的類別型特征。
對于xgboost、GBDT等boosting樹模型,基學(xué)習(xí)通常是cart回歸樹,而cart樹的輸入通常只支持連續(xù)型數(shù)值類型的,像年齡、收入等連續(xù)型變量Cart可以很好地處理,但對于無序的類別型變量(如 職業(yè)、地區(qū)等),cart樹處理就麻煩些了,如果是直接暴力地枚舉每種可能的類別型特征的組合,這樣找類別特征劃分點計算量也很容易就爆了。
在此,本文列舉了 樹模型對于類別型特征處理的常用方法,并做了深入探討~
一、one-hot編碼處理
我們可以直接對類別型特征做Onehot處理(這也是最常用的做法),每一類別的取值都用單獨一位0/1來表示, 也就是一個“性別”類別特征可以轉(zhuǎn)換為是否為“男”、“女” 或者“其他” 來表示,如下:
display(df.loc[:,['Gender_Code']].head())
# onehot
pd.get_dummies(df['Gender_Code']).head()
但是onehot的重大缺點在于,對于取值很多的類別型特征,可能導(dǎo)致高維稀疏特征而容易導(dǎo)致樹模型的過擬合。如之前談到面對高維稀疏的onehot特征,一旦有達到劃分條件,樹模型容易加深,切分次數(shù)越多,相應(yīng)每個切分出的子特征空間的統(tǒng)計信息越來越小,學(xué)習(xí)到的可能只是噪音(即 過擬合)。
使用建議:Onehot天然適合神經(jīng)網(wǎng)絡(luò)模型,神經(jīng)網(wǎng)絡(luò)很容易從高維稀疏特征學(xué)習(xí)到低微稠密的表示。當(dāng)onehot用于樹模型時,類別型特征的取值數(shù)量少的時候還是可以學(xué)習(xí)到比較重要的交互特征,但是當(dāng)取值很多時候(如 大于100),容易導(dǎo)致過擬合,是不太適合用onehot+樹模型的。
(注:此外 onehot 還有增加內(nèi)存開銷以及訓(xùn)練時間開銷等缺點)
二、 Ordinal Encoder
OrdinalEncoder也稱為順序編碼 (與 label encoding,兩者功能基本一樣),特征/標(biāo)簽被轉(zhuǎn)換為序數(shù)整數(shù)(0 到 n_categories - 1)
使用建議:適用于ordinal feature ,也就是雖然類別型特征,但它存在內(nèi)在順序,比如衣服尺寸“S”,“M”, “L”等特征就適合從小到大進行整數(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)和分布不一樣的時候會出條件偏移問題),主流的方法是使用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ǔ)上減少條件偏移。其計算公式為:
TargetCount : 對于指定類別特征在target value的總和 prior:對于整個數(shù)據(jù)集而言,target值的總和/所有的觀測變量數(shù)目 FeatureCount:觀測的特征列表在整個數(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)的頻率,這樣做直觀上就是會以類別取值的頻次為依據(jù) 劃分高頻類別和低頻類別。至于效果,還是要結(jié)合業(yè)務(wù)和實際場景。
## 也可以直接 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ù)量很多時(onehot高維),如果直接onehot,從性能或效果來看都會比較差,這時通過神經(jīng)網(wǎng)絡(luò)embedding是不錯的方法,將類別變量onehot輸入神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)一個低維稠密的向量,如經(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的切分方式,簡單來說,是通過對每個類別取值進行數(shù)值編碼(類似于目標(biāo)編碼),根據(jù)編碼的數(shù)值尋找較優(yōu)切分點,實現(xiàn)了類別特征集合的較優(yōu)切分。
具體算法原理:
1 、特征取值數(shù)目小于等于4(參數(shù)max_cat_to_onehot):直接onehot 編碼,逐個掃描每一個bin容器,找出最佳分裂點;
2、 特征取值數(shù)目大于4:max bin的默認值是256 取值的個數(shù)大于max bin數(shù)時,會篩掉出現(xiàn)頻次少的取值。再統(tǒng)計各個特征值對應(yīng)的樣本的一階梯度之和,二階梯度之和,以一階梯度之和 / (二階梯度之和 + 正則化系數(shù))作為該特征取值的編碼。將類別轉(zhuǎn)化為數(shù)值編碼后,從大到小排序,遍歷直方圖尋找最優(yōu)的切分點
簡單來說,Lightgbm利用梯度統(tǒng)計信息對類別特征編碼。我個人的理解是這樣可以按照學(xué)習(xí)的難易程度為依據(jù)劃分類別特征組,比如某特征一共有【狼、狗、貓、豬、兔】五種類別取值,而【狼、狗】類型下的樣本分類難度相當(dāng)高(該特征取值下的梯度大),在梯度編碼后的類別特征上,尋找較優(yōu)劃分點可能就是【狼、狗】|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)驗小結(jié)
對于取值數(shù)量很少(<10)的類別型特征,相應(yīng)的各取值下的樣本數(shù)量也比較多,可以直接Onehot編碼。
對于取值數(shù)量比較多(10到幾百),這時onehot從效率或者效果,都不及l(fā)ightgbm梯度編碼或catboost目標(biāo)編碼,而且直接使用也很方便。(需要注意的是,個人實踐中這兩種方法在很多取值的類別特征,還是比較容易過擬合。這時,類別值先做下經(jīng)驗的合并或者嘗試剔除某些類別特征后,模型效果反而會更好)
當(dāng)幾百上千的類別取值,可以先onehot后(高維稀疏),借助神經(jīng)網(wǎng)絡(luò)模型做低維稠密表示。
往期精彩回顧
