kaggle競(jìng)賽之類別特征處理
寫在前面
類別型特征(categorical feature)主要是指職業(yè),血型等在有限類別內(nèi)取值的特征。它的原始輸入通常是字符串形式,大多數(shù)算法模型不接受數(shù)值型特征的輸入,針對(duì)數(shù)值型的類別特征會(huì)被當(dāng)成數(shù)值型特征,從而造成訓(xùn)練的模型產(chǎn)生錯(cuò)誤。
文章目錄
Label encoding 序列編碼(Ordinal Encoding) 獨(dú)熱編碼(One-Hot Encoding) 頻數(shù)編碼(Frequency Encoding/Count Encoding) 目標(biāo)編碼(Target Encoding/Mean Encoding) Beta Target Encoding M-Estimate Encoding James-Stein Encoding Weight of Evidence Encoder Leave-one-out Encoder (LOO or LOOE) Binary Encoding Hashing Encoding Probability Ratio Encoding Sum Encoder (Deviation Encoder, Effect Encoder) Helmert Encoding CatBoost Encoding
Label encoding
Label Encoding是使用字典的方式,將每個(gè)類別標(biāo)簽與不斷增加的整數(shù)相關(guān)聯(lián),即生成一個(gè)名為class_的實(shí)例數(shù)組的索引。
Scikit-learn中的LabelEncoder是用來(lái)對(duì)分類型特征值進(jìn)行編碼,即對(duì)不連續(xù)的數(shù)值或文本進(jìn)行編碼。其中包含以下常用方法:
fit(y) :fit可看做一本空字典,y可看作要塞到字典中的詞。 fit_transform(y):相當(dāng)于先進(jìn)行fit再進(jìn)行transform,即把y塞到字典中去以后再進(jìn)行transform得到索引值。 inverse_transform(y):根據(jù)索引值y獲得原始數(shù)據(jù)。 transform(y) :將y轉(zhuǎn)變成索引值。
from?sklearn.preprocessing?import?LabelEncoder
le?=?LabelEncoder()
city_list?=?["paris",?"paris",?"tokyo",?"amsterdam"]
le.fit(city_list)
print(le.classes_)??#?輸出為:['amsterdam'?'paris'?'tokyo']
city_list_le?=?le.transform(city_list)??#?進(jìn)行Encode
print(city_list_le)??#?輸出為:[1 1 2 0]
city_list_new?=?le.inverse_transform(city_list_le)??#?進(jìn)行decode
print(city_list_new)?#?輸出為:['paris'?'paris'?'tokyo'?'amsterdam']
多列數(shù)據(jù)編碼方式:
import?pandas?as?pd
from?sklearn.preprocessing?import?LabelEncoder
df?=?pd.DataFrame({
????'pets':?['cat',?'dog',?'cat',?'monkey',?'dog',?'dog'],
????'owner':?['Champ',?'Ron',?'Brick',?'Champ',?'Veronica',?'Ron'],
????'location':?['San_Diego',?'New_York',?'New_York',?'San_Diego',?'San_Diego',
?????????????????'New_York']
})
d?=?{}
le?=?LabelEncoder()
cols_to_encode?=?['pets',?'owner',?'location']
for?col?in?cols_to_encode:
????df_train[col]?=?le.fit_transform(df_train[col])
????d[col]?=?le.classes_
Pandas的factorize()可以將Series中的標(biāo)稱型數(shù)據(jù)映射稱為一組數(shù)字,相同的標(biāo)稱型映射為相同的數(shù)字。factorize函數(shù)的返回值是一個(gè)tuple(元組),元組中包含兩個(gè)元素。第一個(gè)元素是一個(gè)array,其中的元素是標(biāo)稱型元素映射為的數(shù)字;第二個(gè)元素是Index類型,其中的元素是所有標(biāo)稱型元素,沒有重復(fù)。
import?numpy?as?np
import?pandas?as?pd
df?=?pd.DataFrame(['green','bule','red','bule','green'],columns=['color'])
pd.factorize(df['color'])??#(array([0,?1,?2,?1,?0],?dtype=int64),Index(['green',?'bule',?'red'],?dtype='object'))
pd.factorize(df['color'])[0]?#array([0,?1,?2,?1,?0],?dtype=int64)
pd.factorize(df['color'])[1]??#Index(['green',?'bule',?'red'],?dtype='object')
Label Encoding只是將文本轉(zhuǎn)化為數(shù)值,并沒有解決文本特征的問(wèn)題:所有的標(biāo)簽都變成了數(shù)字,算法模型直接將根據(jù)其距離來(lái)考慮相似的數(shù)字,而不考慮標(biāo)簽的具體含義。使用該方法處理后的數(shù)據(jù)適合支持類別性質(zhì)的算法模型,如LightGBM。
序列編碼(Ordinal Encoding)
Ordinal Encoding即最為簡(jiǎn)單的一種思路,對(duì)于一個(gè)具有m個(gè)category的Feature,我們將其對(duì)應(yīng)地映射到 [0,m-1] 的整數(shù)。當(dāng)然 Ordinal Encoding 更適用于 Ordinal Feature,即各個(gè)特征有內(nèi)在的順序。例如對(duì)于”學(xué)歷”這樣的類別,”學(xué)士”、”碩士”、”博士” 可以很自然地編碼成 [0,2],因?yàn)樗鼈儍?nèi)在就含有這樣的邏輯順序。但如果對(duì)于“顏色”這樣的類別,“藍(lán)色”、“綠色”、“紅色”分別編碼成[0,2]是不合理的,因?yàn)槲覀儾]有理由認(rèn)為“藍(lán)色”和“綠色”的差距比“藍(lán)色”和“紅色”的差距對(duì)于特征的影響是不同的。
ord_map?=?{'Gen?1':?1,?'Gen?2':?2,?'Gen?3':?3,?'Gen?4':?4,?'Gen?5':?5,?'Gen?6':?6}
df['GenerationLabel']?=?df['Generation'].map(gord_map)
獨(dú)熱編碼(One-Hot Encoding)
在實(shí)際的機(jī)器學(xué)習(xí)的應(yīng)用任務(wù)中,特征有時(shí)候并不總是連續(xù)值,有可能是一些分類值,如性別可分為male和female。在機(jī)器學(xué)習(xí)任務(wù)中,對(duì)于這樣的特征,通常我們需要對(duì)其進(jìn)行特征數(shù)字化,比如有如下三個(gè)特征屬性:
性別:[“male”,”female”] 地區(qū):[“Europe”,”US”,”Asia”] 瀏覽器:[“Firefox”,”Chrome”,”Safari”,”Internet Explorer”]
對(duì)于某一個(gè)樣本,如[“male”,”US”,”Internet Explorer”],我們需要將這個(gè)分類值的特征數(shù)字化,最直接的方法,我們可以采用序列化的方式:[0,1,3]。但是,即使轉(zhuǎn)化為數(shù)字表示后,上述數(shù)據(jù)也不能直接用在我們的分類器中。因?yàn)?,分類器往往默認(rèn)數(shù)據(jù)是連續(xù)的,并且是有序的。按照上述的表示,數(shù)字并不是有序的,而是隨機(jī)分配的。這樣的特征處理并不能直接放入機(jī)器學(xué)習(xí)算法中。
為了解決上述問(wèn)題,其中一種可能的解決方法是采用獨(dú)熱編碼(One-Hot Encoding)。獨(dú)熱編碼,又稱為一位有效編碼。其方法是使用N位狀態(tài)寄存器來(lái)對(duì)N個(gè)狀態(tài)進(jìn)行編碼,每個(gè)狀態(tài)都由他獨(dú)立的寄存器位,并且在任意時(shí)候,其中只有一位有效。可以這樣理解,對(duì)于每一個(gè)特征,如果它有m個(gè)可能值,那么經(jīng)過(guò)獨(dú)熱編碼后,就變成了m個(gè)二元特征。并且,這些特征互斥,每次只有一個(gè)激活。因此,數(shù)據(jù)會(huì)變成稀疏的。
對(duì)于上述的問(wèn)題,性別的屬性是二維的,同理,地區(qū)是三維的,瀏覽器則是四維的,這樣,我們可以采用One-Hot編碼的方式對(duì)上述的樣本[“male”,”US”,”Internet Explorer”]編碼,male則對(duì)應(yīng)著[1,0],同理US對(duì)應(yīng)著[0,1,0],Internet Explorer對(duì)應(yīng)著[0,0,0,1]。則完整的特征數(shù)字化的結(jié)果為:[1,0,0,1,0,0,0,0,1]。

為什么能使用One-Hot Encoding?
使用one-hot編碼,將離散特征的取值擴(kuò)展到了歐式空間,離散特征的某個(gè)取值就對(duì)應(yīng)歐式空間的某個(gè)點(diǎn)。在回歸,分類,聚類等機(jī)器學(xué)習(xí)算法中,特征之間距離的計(jì)算或相似度的計(jì)算是非常重要的,而我們常用的距離或相似度的計(jì)算都是在歐式空間的相似度計(jì)算,計(jì)算余弦相似性,也是基于的歐式空間。 將離散型特征使用one-hot編碼,可以會(huì)讓特征之間的距離計(jì)算更加合理。比如,有一個(gè)離散型特征,代表工作類型,該離散型特征,共有三個(gè)取值,不使用one-hot編碼,計(jì)算出來(lái)的特征的距離是不合理。那如果使用one-hot編碼,顯得更合理。
獨(dú)熱編碼優(yōu)缺點(diǎn)
優(yōu)點(diǎn):獨(dú)熱編碼解決了分類器不好處理屬性數(shù)據(jù)的問(wèn)題,在一定程度上也起到了擴(kuò)充特征的作用。它的值只有0和1,不同的類型存儲(chǔ)在垂直的空間。 缺點(diǎn):當(dāng)類別的數(shù)量很多時(shí),特征空間會(huì)變得非常大。在這種情況下,一般可以用PCA(主成分分析)來(lái)減少維度。而且One-Hot Encoding+PCA這種組合在實(shí)際中也非常有用。
One-Hot Encoding的使用場(chǎng)景
獨(dú)熱編碼用來(lái)解決類別型數(shù)據(jù)的離散值問(wèn)題。將離散型特征進(jìn)行one-hot編碼的作用,是為了讓距離計(jì)算更合理,但如果特征是離散的,并且不用one-hot編碼就可以很合理的計(jì)算出距離,那么就沒必要進(jìn)行one-hot編碼,比如,該離散特征共有1000個(gè)取值,我們分成兩組,分別是400和600,兩個(gè)小組之間的距離有合適的定義,組內(nèi)的距離也有合適的定義,那就沒必要用one-hot 編碼。 基于樹的方法是不需要進(jìn)行特征的歸一化,例如隨機(jī)森林,bagging 和 boosting等。對(duì)于決策樹來(lái)說(shuō),one-hot的本質(zhì)是增加樹的深度,決策樹是沒有特征大小的概念的,只有特征處于他分布的哪一部分的概念。
基于Scikit-learn 的one hot encoding
LabelBinarizer:將對(duì)應(yīng)的數(shù)據(jù)轉(zhuǎn)換為二進(jìn)制型,類似于onehot編碼,這里有幾點(diǎn)不同:
可以處理數(shù)值型和類別型數(shù)據(jù) 輸入必須為1D數(shù)組 可以自己設(shè)置正類和父類的表示方式
from?sklearn.preprocessing?import?LabelBinarizer
?
lb?=?LabelBinarizer()
?
city_list?=?["paris",?"paris",?"tokyo",?"amsterdam"]
?
lb.fit(city_list)
print(lb.classes_)??#?輸出為:['amsterdam'?'paris'?'tokyo']
?
city_list_le?=?lb.transform(city_list)??#?進(jìn)行Encode
print(city_list_le)??#?輸出為:
#?[[0?1?0]
#??[0?1?0]
#??[0?0?1]
#??[1?0?0]]
?
city_list_new?=?lb.inverse_transform(city_list_le)??#?進(jìn)行decode
print(city_list_new)??#?輸出為:['paris'?'paris'?'tokyo'?'amsterdam']
OneHotEncoder只能對(duì)數(shù)值型數(shù)據(jù)進(jìn)行處理,需要先將文本轉(zhuǎn)化為數(shù)值(Label encoding)后才能使用,只接受2D數(shù)組:
import?pandas?as?pd
from?sklearn.preprocessing?import?LabelEncoder
from?sklearn.preprocessing?import?OneHotEncoder
def?LabelOneHotEncoder(data,?categorical_features):
????d_num?=?np.array([])
????for?f?in?data.columns:
????????if?f?in?categorical_features:
????????????le,?ohe?=?LabelEncoder(),?OneHotEncoder()
????????????data[f]?=?le.fit_transform(data[f])
????????????if?len(d_num)?==?0:
????????????????d_num?=?np.array(ohe.fit_transform(data[[f]]))
????????????else:
????????????????d_num?=?np.hstack((d_num,?ohe.fit_transform(data[[f]]).A))
????????else:
????????????if?len(d_num)?==?0:
????????????????d_num?=?np.array(data[[f]])
????????????else:
????????????????d_num?=?np.hstack((d_num,?data[[f]]))
????return?d_num
df?=?pd.DataFrame([
????['green',?'Chevrolet',?2017],
????['blue',?'BMW',?2015],
????['yellow',?'Lexus',?2018],
])
df.columns?=?['color',?'make',?'year']
df_new?=?LabelOneHotEncoder(df,?['color',?'make',?'year'])
基于Pandas的one hot encoding
其實(shí)如果我們跳出 scikit-learn, 在 pandas 中可以很好地解決這個(gè)問(wèn)題,用 pandas 自帶的get_dummies函數(shù)即可
import?pandas?as?pd
?
df?=?pd.DataFrame([
????['green',?'Chevrolet',?2017],
????['blue',?'BMW',?2015],
????['yellow',?'Lexus',?2018],
])
df.columns?=?['color',?'make',?'year']
df_processed?=?pd.get_dummies(df,?prefix_sep="_",?columns=df.columns[:-1])
print(df_processed)
get_dummies的優(yōu)勢(shì)在于:
本身就是 pandas 的模塊,所以對(duì) DataFrame 類型兼容很好 不管你列是數(shù)值型還是字符串型,都可以進(jìn)行二值化編碼 能夠根據(jù)指令,自動(dòng)生成二值化編碼后的變量名
get_dummies雖然有這么多優(yōu)點(diǎn),但畢竟不是 sklearn 里的transformer類型,所以得到的結(jié)果得手動(dòng)輸入到 sklearn 里的相應(yīng)模塊,也無(wú)法像 sklearn 的transformer一樣可以輸入到pipeline中進(jìn)行流程化地機(jī)器學(xué)習(xí)過(guò)程。
頻數(shù)編碼(Frequency Encoding/Count Encoding)
將類別特征替換為訓(xùn)練集中的計(jì)數(shù)(一般是根據(jù)訓(xùn)練集來(lái)進(jìn)行計(jì)數(shù),屬于統(tǒng)計(jì)編碼的一種,統(tǒng)計(jì)編碼,就是用類別的統(tǒng)計(jì)特征來(lái)代替原始類別,比如類別A在訓(xùn)練集中出現(xiàn)了100次則編碼為100)。這個(gè)方法對(duì)離群值很敏感,所以結(jié)果可以歸一化或者轉(zhuǎn)換一下(例如使用對(duì)數(shù)變換)。未知類別可以替換為1。
頻數(shù)編碼使用頻次替換類別。有些變量的頻次可能是一樣的,這將導(dǎo)致碰撞。盡管可能性不是非常大,沒法說(shuō)這是否會(huì)導(dǎo)致模型退化,不過(guò)原則上我們不希望出現(xiàn)這種情況。
import?pandas?as?pd
data_count?=?data.groupby('城市')['城市'].agg({'頻數(shù)':'size'}).reset_index()
data?=?pd.merge(data,?data_count,?on?=?'城市',?how?=?'left')
目標(biāo)編碼(Target Encoding/Mean Encoding)
目標(biāo)編碼(target encoding),亦稱均值編碼(mean encoding)、似然編碼(likelihood encoding)、效應(yīng)編碼(impact encoding),是一種能夠?qū)Ω呋鶖?shù)(high cardinality)自變量進(jìn)行編碼的方法 (Micci-Barreca 2001) 。
如果某一個(gè)特征是定性的(categorical),而這個(gè)特征的可能值非常多(高基數(shù)),那么目標(biāo)編碼(Target encoding)是一種高效的編碼方式。在實(shí)際應(yīng)用中,這類特征工程能極大提升模型的性能。
一般情況下,針對(duì)定性特征,我們只需要使用sklearn的OneHotEncoder或LabelEncoder進(jìn)行編碼。
LabelEncoder能夠接收不規(guī)則的特征列,并將其轉(zhuǎn)化為從0到n-1的整數(shù)值(假設(shè)一共有n種不同的類別);OneHotEncoder則能通過(guò)啞編碼,制作出一個(gè)m*n的稀疏矩陣(假設(shè)數(shù)據(jù)一共有m行,具體的輸出矩陣格式是否稀疏可以由sparse參數(shù)控制)。
定性特征的基數(shù)(cardinality)指的是這個(gè)定性特征所有可能的不同值的數(shù)量。在高基數(shù)(high cardinality)的定性特征面前,這些數(shù)據(jù)預(yù)處理的方法往往得不到令人滿意的結(jié)果。
高基數(shù)定性特征的例子:IP地址、電子郵件域名、城市名、家庭住址、街道、產(chǎn)品號(hào)碼。
主要原因:
LabelEncoder編碼高基數(shù)定性特征,雖然只需要一列,但是每個(gè)自然數(shù)都具有不同的重要意義,對(duì)于y而言線性不可分。使用簡(jiǎn)單模型,容易欠擬合(underfit),無(wú)法完全捕獲不同類別之間的區(qū)別;使用復(fù)雜模型,容易在其他地方過(guò)擬合(overfit)。 OneHotEncoder編碼高基數(shù)定性特征,必然產(chǎn)生上萬(wàn)列的稀疏矩陣,易消耗大量?jī)?nèi)存和訓(xùn)練時(shí)間,除非算法本身有相關(guān)優(yōu)化(例:SVM)。
如果某個(gè)類別型特征基數(shù)比較低(low-cardinality features),即該特征的所有值去重后構(gòu)成的集合元素個(gè)數(shù)比較少,一般利用One-hot編碼方法將特征轉(zhuǎn)為數(shù)值型。One-hot編碼可以在數(shù)據(jù)預(yù)處理時(shí)完成,也可以在模型訓(xùn)練的時(shí)候完成,從訓(xùn)練時(shí)間的角度,后一種方法的實(shí)現(xiàn)更為高效,CatBoost對(duì)于基數(shù)較低的類別型特征也是采用后一種實(shí)現(xiàn)。
顯然,在高基數(shù)類別型特征(high cardinality features) 當(dāng)中,比如 user ID,這種編碼方式會(huì)產(chǎn)生大量新的特征,造成維度災(zāi)難。一種折中的辦法是可以將類別分組成有限個(gè)的群體再進(jìn)行One-hot編碼。一種常被使用的方法是根據(jù)目標(biāo)變量統(tǒng)計(jì)(Target Statistics,以下簡(jiǎn)稱TS)進(jìn)行分組,目標(biāo)變量統(tǒng)計(jì)用于估算每個(gè)類別的目標(biāo)變量期望值。甚至有人直接用TS作為一個(gè)新的數(shù)值型變量來(lái)代替原來(lái)的類別型變量。重要的是,可以通過(guò)對(duì)TS數(shù)值型特征的閾值設(shè)置,基于對(duì)數(shù)損失、基尼系數(shù)或者均方差,得到一個(gè)對(duì)于訓(xùn)練集而言將類別一分為二的所有可能劃分當(dāng)中最優(yōu)的那個(gè)。在LightGBM當(dāng)中,類別型特征用每一步梯度提升時(shí)的梯度統(tǒng)計(jì)(Gradient Statistics,以下簡(jiǎn)稱GS)來(lái)表示。雖然為建樹提供了重要的信息,但是這種方法有以下兩個(gè)缺點(diǎn):
增加計(jì)算時(shí)間,因?yàn)樾枰獙?duì)每一個(gè)類別型特征,在迭代的每一步,都需要對(duì)GS進(jìn)行計(jì)算 增加存儲(chǔ)需求,對(duì)于一個(gè)類別型變量,需要存儲(chǔ)每一次分離每個(gè)節(jié)點(diǎn)的類別
為了克服這些缺點(diǎn),LightGBM以損失部分信息為代價(jià)將所有的長(zhǎng)尾類別歸為一類,作者聲稱這樣處理高基數(shù)類別型特征時(shí)比One-hot編碼還是好不少。不過(guò)如果采用TS特征,那么對(duì)于每個(gè)類別只需要計(jì)算和存儲(chǔ)一個(gè)數(shù)字。因此,采用TS作為一個(gè)新的數(shù)值型特征是最有效、信息損失最小的處理類別型特征的方法。TS也被廣泛應(yīng)用在點(diǎn)擊預(yù)測(cè)任務(wù)當(dāng)中,這個(gè)場(chǎng)景當(dāng)中的類別型特征有用戶、地區(qū)、廣告、廣告發(fā)布者等。接下來(lái)我們著重討論TS,暫時(shí)將One-hot編碼和GS放一邊。
以下是計(jì)算公式:
其中 n 代表的是該某個(gè)特征取值的個(gè)數(shù),
代表某個(gè)特征取值下正Label的個(gè)數(shù),mdl為一個(gè)最小閾值,樣本數(shù)量小于此值的特征類別將被忽略,prior是Label的均值。注意,如果是處理回歸問(wèn)題的話,可以處理成相應(yīng)該特征下label取值的average/max。對(duì)于k分類問(wèn)題,會(huì)生成對(duì)應(yīng)的k-1個(gè)特征。此方法同樣容易引起過(guò)擬合,以下方法用于防止過(guò)擬合:
增加正則項(xiàng)a的大小 在訓(xùn)練集該列中添加噪聲 使用交叉驗(yàn)證
目標(biāo)編碼屬于有監(jiān)督的編碼方式,如果運(yùn)用得當(dāng)則能夠有效地提高預(yù)測(cè)模型的準(zhǔn)確性 (Pargent, Bischl, and Thomas 2019) ;而這其中的關(guān)鍵,就是在編碼的過(guò)程中引入正則化,避免過(guò)擬合問(wèn)題。
例如類別A對(duì)應(yīng)的標(biāo)簽1有200個(gè),標(biāo)簽2有300個(gè),標(biāo)簽3有500個(gè),則可以編碼為:2/10,3/10,3/6。中間最重要的是如何避免過(guò)擬合(原始的target encoding直接對(duì)全部的訓(xùn)練集數(shù)據(jù)和標(biāo)簽進(jìn)行編碼,會(huì)導(dǎo)致得到的編碼結(jié)果太過(guò)依賴與訓(xùn)練集),常用的解決方法是使用2 levels of cross-validation求出target mean,思路如下:
把train data劃分為20-folds (舉例:infold: fold #2-20, out of fold: fold #1) 計(jì)算 10-folds的 inner out of folds值 (舉例:使用inner_infold #2-10 的target的均值,來(lái)作為inner_oof #1的預(yù)測(cè)值) 對(duì)10個(gè)inner out of folds 值取平均,得到 inner_oof_mean 將每一個(gè) infold (fold #2-20) 再次劃分為10-folds (舉例:inner_infold: fold #2-10, Inner_oof: fold #1) 計(jì)算oof_mean (舉例:使用 infold #2-20的inner_oof_mean 來(lái)預(yù)測(cè) out of fold #1的oof_mean 將train data 的 oof_mean 映射到test data完成編碼
比如劃分為10折,每次對(duì)9折進(jìn)行標(biāo)簽編碼然后用得到的標(biāo)簽編碼模型預(yù)測(cè)第10折的特征得到結(jié)果,其實(shí)就是常說(shuō)的均值編碼。
目標(biāo)編碼嘗試對(duì)分類特征中每個(gè)級(jí)別的目標(biāo)總體平均值進(jìn)行測(cè)量。這意味著,當(dāng)每個(gè)級(jí)別的數(shù)據(jù)更少時(shí),估計(jì)的均值將與“真實(shí)”均值相距更遠(yuǎn),方差更大。
from?category_encoders?import?TargetEncoder
import?pandas?as?pd
from?sklearn.datasets?import?load_boston
#?prepare?some?data
bunch?=?load_boston()
y_train?=?bunch.target[0:250]
y_test?=?bunch.target[250:506]
X_train?=?pd.DataFrame(bunch.data[0:250],?columns=bunch.feature_names)
X_test?=?pd.DataFrame(bunch.data[250:506],?columns=bunch.feature_names)
#?use?target?encoding?to?encode?two?categorical?features
enc?=?TargetEncoder(cols=['CHAS',?'RAD'])
#?transform?the?datasets
training_numeric_dataset?=?enc.fit_transform(X_train,?y_train)
testing_numeric_dataset?=?enc.transform(X_test)
Beta Target Encoding
Kaggle競(jìng)賽Avito Demand Prediction Challenge 第14名的solution分享:14th Place Solution: The Almost Golden Defenders。和target encoding 一樣,beta target encoding 也采用 target mean value (among each category) 來(lái)給categorical feature做編碼。不同之處在于,為了進(jìn)一步減少target variable leak,beta target encoding發(fā)生在在5-fold CV內(nèi)部,而不是在5-fold CV之前:
把train data劃分為5-folds (5-fold cross validation) target encoding based on infold data train model get out of fold prediction
同時(shí)beta target encoding 加入了smoothing term,用 bayesian mean 來(lái)代替mean。Bayesian mean (Bayesian average) 的思路:某一個(gè)category如果數(shù)據(jù)量較少( 另外,對(duì)于target encoding和beta target encoding,不一定要用target mean (or bayesian mean),也可以用其他的統(tǒng)計(jì)值包括 medium, frqequency, mode, variance, skewness, and kurtosis — 或任何與target有correlation的統(tǒng)計(jì)值。 M-Estimate Encoding 相當(dāng)于 一個(gè)簡(jiǎn)化版的Target Encoding: 其中??+代表所有正Label的個(gè)數(shù),m是一個(gè)調(diào)參的參數(shù),m越大過(guò)擬合的程度就會(huì)越小,同樣的在處理連續(xù)值時(shí)??+可以換成label的求和,??+換成所有l(wèi)abel的求和。 James-Stein Encoding 同樣是基于target的一種算法。算法的思想很簡(jiǎn)單,對(duì)于特征的每個(gè)取值 k 可以根據(jù)下面的公式獲得: 其中B由以下公式估計(jì): 但是它有一個(gè)要求是target必須符合正態(tài)分布,這對(duì)于分類問(wèn)題是不可能的,因此可以把y先轉(zhuǎn)化成概率的形式?;蛘咴趯?shí)際操作中,使用grid search的方法選擇一個(gè)比較好的B值。 Weight Of Evidence 同樣是基于target的方法。 使用WOE作為變量,第i類的WOE等于: WOE特別合適邏輯回歸,因?yàn)長(zhǎng)ogit=log(odds)。WOE編碼的變量被編碼為統(tǒng)一的維度(是一個(gè)被標(biāo)準(zhǔn)化過(guò)的值),變量之間直接比較系數(shù)即可。 這個(gè)方法類似于SUM的方法,只是在計(jì)算訓(xùn)練集每個(gè)樣本的特征值轉(zhuǎn)換時(shí)都要把該樣本排除(消除特征某取值下樣本太少導(dǎo)致的嚴(yán)重過(guò)擬合),在計(jì)算測(cè)試集每個(gè)樣本特征值轉(zhuǎn)換時(shí)與SUM相同。可見以下公式: 把每一類的序號(hào)用二進(jìn)制進(jìn)行編碼,使用log2N維向量來(lái)編碼N類。例如:(0,0)代表第一類,(0,1)代表第二類,(1,0)代表第三類,(1,1)代表第四類 類似于One-hot encoding,但是通過(guò)hash函數(shù)映射到一個(gè)低維空間,并且使得兩個(gè)類對(duì)應(yīng)向量的空間距離基本保持一致。使用低維空間來(lái)降低了表示向量的維度。 特征哈希可能會(huì)導(dǎo)致要素之間發(fā)生沖突。但哈希編碼的優(yōu)點(diǎn)是它不需要制定和維護(hù)原變量與新變量之間的映射關(guān)系。因此,哈希編碼器的大小及復(fù)雜程度不隨數(shù)據(jù)類別的增多而增多。 和WOE相似,只是去掉了log,即: 求和編碼通過(guò)比較某一特征取值下對(duì)應(yīng)標(biāo)簽(或其他相關(guān)變量)的均值與標(biāo)簽的均值之間的差別來(lái)對(duì)特征進(jìn)行編碼。如果做不好細(xì)節(jié),這個(gè)方法非常容易出現(xiàn)過(guò)擬合,所以需要配合留一法或者五折交叉驗(yàn)證進(jìn)行特征的編碼。還有根據(jù)方差加入懲罰項(xiàng)防止過(guò)擬合的方法。 Helmert編碼通常在計(jì)量經(jīng)濟(jì)學(xué)中使用。在Helmert編碼(分類特征中的每個(gè)值對(duì)應(yīng)于Helmert矩陣中的一行)之后,線性模型中編碼后的變量系數(shù)可以反映在給定該類別變量某一類別值的情形下因變量的平均值與給定該類別其他類別值的情形下因變量的平均值的差值。 Helmet編碼是僅次于One-Hot Encoding和Sum Encoder使用最廣泛的編碼方法,與Sum Encoder不同的是,它比較的是某一特征取值下對(duì)應(yīng)標(biāo)簽(或其他相關(guān)變量)的均值與他之前特征的均值之間的差異,而不是和所有特征的均值比較。這個(gè)特征同樣容易出現(xiàn)過(guò)擬合的情況。 對(duì)于可取值的數(shù)量比獨(dú)熱最大量還要大的分類變量,CatBoost 使用了一個(gè)非常有效的編碼方法,這種方法和均值編碼類似,但可以降低過(guò)擬合情況。它的具體實(shí)現(xiàn)方法如下: 其中 CountInClass 表示在當(dāng)前分類特征值中,有多少樣本的標(biāo)記值是1;Prior 是分子的初始值,根據(jù)初始參數(shù)確定。TotalCount 是在所有樣本中(包含當(dāng)前樣本),和當(dāng)前樣本具有相同的分類特征值的樣本數(shù)量。 推薦閱讀: 萬(wàn)字長(zhǎng)文詳解|Python庫(kù)collections,讓你擊敗99%的Pythoner 刷爆網(wǎng)絡(luò)的動(dòng)態(tài)條形圖,3行Python代碼就能搞定 Python初學(xué)者必須吃透這69個(gè)內(nèi)置函數(shù)! 全面理解Python集合,17個(gè)方法全解,看完就夠了 ↓掃描關(guān)注本號(hào)↓#?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?predictionM-Estimate Encoding
James-Stein Encoding
Weight of Evidence Encoder
Leave-one-out Encoder (LOO or LOOE)
Binary Encoding
Hashing Encoding
Probability Ratio Encoding
Sum Encoder (Deviation Encoder, Effect Encoder)
Helmert Encoding
CatBoost Encoding
CatBoost處理Categorical features總結(jié):
