high-cardinality categorical attributes,從字面上理解,即對於某個category特征,不同值的數量非常多,這里暫且把它叫做高數量類別屬性。反之,即低數量類別屬性(low-cardinality)
對於低數量類別屬性,通常在data science中采用的方式是將其轉化為one-hot編碼,即給每一個類別增加一個特征。但是當類別數量增加的時候,ont-hot編碼增加的特征也在增加。所以,one-hot編碼無法適用於高數量特征屬性。
基本方法(clustering)
目前有一個常見的方法處理這個問題叫做clustering。即將原始的1-to-N的mapping問題變成1-to-k的mapping問題。(k<<N)
為了達到這個目標,這個高數量類別屬性首先將依據target的值grouping成k個類(clusters),然后再依據這個grouping的結果進行one-hot編碼。
不得不說,這個方法最大程度的保留了原始數據的信息。grouping的方法有一些:其中一個就是Hierarchical clustering Algorithm,它使用一個基於target的的統計距離來grouping,grouping的標准也可以度量每合並兩個clusters帶來的信息增量的影響,如gain ratio
。兩個距離最短的clusters將會被合並成一個。這個過程一直被迭代,直到迭代過程沒有明顯的改善。
這個問題更多解釋可見《統計學習方法》5.2.2
cate_A | target |
---|---|
a/b/c.... | 1/0 |
... | ... |
對上述數據集, cate_A為高數量特征屬性,其處理方法的偽代碼如下:
while improvment > epsilon or unique(cate_A) >= k:
compute Conditional Entropy Target|cate_A: THA1
for _ :
Merge two random labels in cate_A into one
compute Conditional Entropy Target|cate_A: THA
Find the two labels that then compute to a minimum THA2
improvement = THA1 - THA2
上述方法其實很常見,且被運用在決策樹的C4.5方法中進行特征選擇。
這個方法在最后需要ont-hot編碼。
另一方法(smoothing)
Smoothing,簡單來說,就是將原來獨立的高數量類別特征的每個值映射到概率估計上。基本來講,這個預處理方法將原始的值放置到實際的機器學習模型之前先通過一個簡單的特征處理模型(如貝葉斯模型)。
下面以binary target為例進行方法分析:
當target屬性 \(Y\in\{0,1\}\)時,假設要處理的特征為X,該特征的每一個不同的值為\(X_{i}\)。我們要做的是, 將高數量類別特征將映射到一個標量\(S_{i}\)中,\(S_{i}\)代表一個條件概率,即
注意到\(S_{i}\)代表的是條件概率,那么他的值被歸一到了0和1之間,這對於神經網絡模型也是一個好的預處理。
下一步就是概率估計的過程。我們假設數據集被分成了\(n_{TR}\)個訓練集和\(n_{TS}\)個測試集。因為這個概率估計成為了模型訓練的一部分,所以只有訓練集的數據被使用。注意到不是所有的X的可能值都會出現在訓練集中,有的值可能只出現在測試集或者新進來的數據中。所以,這個映射過程必須要能夠處理這個特征的不可預見性的值。
如果該特征某個值如\(X=X_{i}\)出現的數量足夠多,那么這個概率估計可以這樣計算:
這是一個后驗概率的計算過程。然而不幸的是,特征的值的數量分布通常是不均勻的,有很多值的數量非常少。所以這種使用\(P(Y|X=X_{i})\)的直接估計是不太可靠的。
為了減小這種小數量值的影響,\(S_{i}\)的計算可能被分成兩個概率的組合。后驗概率的計算如公式(2),先驗概率的計算如\(P(Y) = \frac{n_{Y}}{n_{TR}}\)。整個組合計算公式為:
\(n_{Y}\)代表在整個數據集中\(Y=1\)的數量。\(\lambda(n_{i})\)是一個在0-1之間的單調遞增函數。
原理:一方面,當特征的某個值的數量很多,即\(\lambda\cong1\)時,公式即為(2),計算后驗概率。另一方面,當特征的某個值的數量很少時,即\(\lambda\cong0\)時,公式前項為0,只計算先驗概率。
所以,關鍵我們怎么選取\(\lambda(n_{i})\)這個函數呢?有一個典型函數如下:
這個公式是一個s形狀的函數,當 n=k 時值為 0.5 。
-
參數 f 控制函數在轉折處的斜率,決定了先驗概率和后驗概率之間的平衡。如果\(f\to\infty\),那么公式(3)變為一個硬間隔,即先驗概率和后驗概率各占0.5。
-
參數 k 決定於我們允許的特征值數量的最小值的一半。so important!
我們在來看看經驗貝葉斯估計(Empirical Bayes estimation) 的一般公式:
\(\overline{y}\)是先驗概率,\(y_{i}\)是經驗后驗概率。收縮系數\(B_{i}\)根據不同的估計方法有不同的形式。當所有概率分布服從高斯分布的時候:
\(\sigma^{2}\)是值的方差,\(\tau^{2}\)是樣本方差。事實上,公式(6)是公式(4)的一般形式。
以下是我在kaggle中看到大佬對該方法的coding實現,借以參考,幫助理解:
def add_noise(series, noise_level):
return series * (1 + noise_level * np.random.randn(len(series)))
def target_encode(trn_series=None,
tst_series=None,
target=None,
min_samples_leaf=1,
smoothing=1,
noise_level=0):
"""
trn_series : training categorical feature as a pd.Series
tst_series : test categorical feature as a pd.Series
target : target data as a pd.Series
min_samples_leaf (int) : minimum samples to take category average into account
smoothing (int) : smoothing effect to balance categorical average vs prior
"""
assert len(trn_series) == len(target)
assert trn_series.name == tst_series.name
temp = pd.concat([trn_series, target], axis=1)
# Compute target mean
averages = temp.groupby(by=trn_series.name)[target.name].agg(["mean", "count"])
# Compute smoothing
smoothing = 1 / (1 + np.exp(-(averages["count"] - min_samples_leaf) / smoothing))
# Apply average function to all target data
prior = target.mean()
# The bigger the count the less full_avg is taken into account
averages[target.name] = prior * (1 - smoothing) + averages["mean"] * smoothing
averages.drop(["mean", "count"], axis=1, inplace=True)
# Apply averages to trn and tst series
ft_trn_series = pd.merge(
trn_series.to_frame(trn_series.name),
averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
on=trn_series.name,
how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
# pd.merge does not keep the index so restore it
ft_trn_series.index = trn_series.index
ft_tst_series = pd.merge(
tst_series.to_frame(tst_series.name),
averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
on=tst_series.name,
how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
# pd.merge does not keep the index so restore it
ft_tst_series.index = tst_series.index
return add_noise(ft_trn_series, noise_level), add_noise(ft_tst_series, noise_level)
兩個方法比較
smoothing方法可以只要通過對本地數據集的操作就可完成預處理,而clustering方法需要更復雜的算法而且可能導致信息量的減少(因為最后仍然需要進行one-hot編碼)。
當然,對於smoothing方法來說, \(\lambda\)函數的選擇直接影響到了結果,可能不同的\(\lambda\)函數可以適用於不同的分支領域,這都需要實驗的考證。
PS:該篇文章主要基於對文獻的翻譯,這個翻譯過程終於讓我體會到翻譯的不易了,以后我再也不會抱怨專業書翻譯者的不專業了。
Reference: