出品 | CDA數據分析研究院,轉載需受權
顧名思義,分類變量用於表示類別或標籤。例如,一個分類變量能夠表明世界上的主要城市,一年的四季,或者一個公司的所在產業(如石油,旅遊,科技)。html
在現實世界中,分類變量的值是無窮多的。這些值能夠用數字表示。然而,與數值變量不一樣,分類變量的值不能相互排序。python
例如石油做爲一種行業類型,既不高於也不低於旅遊業,這被稱爲非序數。git
那麼怎麼判斷一個變量是不是分類變量呢?能夠經過一個簡單的問題來檢驗"值的大小重不重要,是否具備計算關係?"若是值的大小重要而且具備計算關係,那麼就是數值型變量。例如,500 美圓的股價是 100 美圓的 5 倍,因此股票價格應該用一個連續的數字變量來表示。另外一方面,值的大小不重要且不能夠計算,那麼就是分類變量,例如公司的行業(石油、旅遊、技術等)是分類變量。github
少類別分類變量:類別數量的數量級小web
像是性別、顏色、產業等分類變量的類別數量級小,那麼就屬於少類別分類變量數據庫
多類別分類變量:類別數量的數量級大json
多類別分類變量在事務性記錄中特別常見。例如,許多 web 服務使用 id 跟蹤用戶,這是一個包含數億到數億個值的分類變量,具體取決於服務的惟一用戶數量。互聯網交易的 IP 地址是另外一個多類別分類變量的例子。它們是分類變量,由於即便用戶 id 和 IP 地址是數字,它們的大小一般與手頭的任務無關。例如,當對單個交易進行欺詐檢測時,IP 地址多是相關的。某些 IP 地址或子網可能比其餘 IP 地址或子網產生更多的欺詐交易。可是 164.203.x 的子網本質上並不比 164.202.x 更具欺詐性,子網的數值並不重要。api
文檔語料庫的詞彙能夠解釋爲一個多類別分類變量。若是一個類(例如 apple這個單詞)在一個數據點(文檔)中出現屢次,那麼咱們能夠將其表示爲一個計數,並經過它們的計數統計數據表示全部的類別。這就是所謂的bin-counting
。app
咱們從少類別分類變量的處理開始討論,最後進入到多類別分類變量的處理的討論。dom
範疇變量的類別一般不是數字。例如,眼睛的顏色能夠是"黑色""藍色""棕色"等等。所以,須要一種編碼方法來將這些非數字類別轉換爲數字。簡單地給每一個 k 可能的類別分配一個整數,好比 1 到 k,是很誘人的。可是獲得的值能夠相互排序,這對於分類變量來講是不容許的。
一個更好的方法是使用一組字節,每一個字節表明一個可能的類別。若是變量不能同時是多個類別,那麼組中只能有一個字節是有數據的。這被稱爲 one-hot 編碼,它在 ScikitLearn 中實現爲sklearn.preprocessing.OneHotEncoder 。每一個字節都是一個特徵。所以,可能類別爲 k 的分類變量被編碼爲長度爲 k 的特徵向量。
城市 | $e_1 $ | $e_2 $ | $e_3 $ |
---|---|---|---|
San Francisco | 1 | 0 | 0 |
New York | 0 | 1 | 0 |
Seattle | 0 | 0 | 1 |
獨熱編碼很是容易理解。若是咱們看到位中的前 k-1 位都爲零,那麼最後一位必須爲 1,由於變量必須取一個 k 值。在數學上,咱們能夠把這個約束寫成"全部位之和必須等於 1"。
約束用公式表示
$$ e_{1}+e_{2}+\ldots+e_{k}=1 $$
獨熱編碼的問題在於它容許 k 個自由度,而變量自己只須要 k-1。虛擬編碼經過在表示中僅使用 k-1 特徵來消除多餘的自由度。一個類別,由全部字節爲零的向量表示。這就是所謂的參考類別。虛擬編碼和一熱編碼,Pandas 中實現爲pandas.get_dummies。
城市 | $e_1 $ | $e_2 $ |
---|---|---|
San Francisco | 1 | 0 |
New York | 0 | 1 |
Seattle | 0 | 0 |
與獨熱編碼相比,採用虛擬編碼的建模結果更具備可解釋性。
這一點很容易在簡單線性迴歸問題中看出來。
小案例
假設咱們有三個城市的公寓租賃價格數據:舊金山、紐約和西雅圖。
import pandas as pd from sklearn import linear_model
df = pd.DataFrame({'City': ['SF', 'SF', 'SF', 'NYC', 'NYC', 'NYC', 'Seattle', 'Seattle', 'Seattle'], 'Rent': [3999, 4000, 4001, 3499, 3500, 3501, 2499, 2500, 2501]}) df
City | Rent | |
---|---|---|
0 | SF | 3999 |
1 | SF | 4000 |
2 | SF | 4001 |
3 | NYC | 3499 |
4 | NYC | 3500 |
5 | NYC | 3501 |
6 | Seattle | 2499 |
7 | Seattle | 2500 |
8 | Seattle | 2501 |
df['Rent'].mean()
3333.333333333335
咱們能夠訓練一個線性迴歸模型,僅僅根據城市的特徵來預測租金價格。
線性迴歸模型能夠這樣寫:
$$ y=w_{1} x_{1}+\ldots+w_{n} x_{n} $$
一般須要增長一個稱爲截距的常量項,這樣當 x 爲零時,y 能夠是一個非零值:
$$ y=w_{1} x_{1}+\ldots+w_{n} x_{n}+b $$
獨熱編碼處理
#將數據庫中的分類變量轉換爲 one-hot編碼 one_hot_df = pd.get_dummies(df, prefix=['city']) one_hot_df
Rent | city_NYC | city_SF | city_Seattle | |
---|---|---|---|---|
0 | 3999 | 0 | 1 | 0 |
1 | 4000 | 0 | 1 | 0 |
2 | 4001 | 0 | 1 | 0 |
3 | 3499 | 1 | 0 | 0 |
4 | 3500 | 1 | 0 | 0 |
5 | 3501 | 1 | 0 | 0 |
6 | 2499 | 0 | 0 | 1 |
7 | 2500 | 0 | 0 | 1 |
8 | 2501 | 0 | 0 | 1 |
#導入線性迴歸模型 lin_reg = linear_model.LinearRegression() #對線性迴歸模型進行訓練,分類變量做爲x,Rent做爲y lin_reg.fit(one_hot_df[['city_NYC', 'city_SF', 'city_Seattle']], one_hot_df['Rent']) LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False) w1 = lin_reg.coef_ b1 = lin_reg.intercept_
#迴歸係數 lin_reg.coef_
array([ 166.66666667, 666.66666667, -833.33333333])
#截距項 lin_reg.intercept_
3333.3333333333335
虛擬編碼處理
#pd.get_dummies裏的drop_first爲默認爲false,默認是獨熱編碼,當爲true時,則爲虛擬編碼 dummy_df = pd.get_dummies(df, prefix=['city'], drop_first=True) dummy_df
Rent | city_SF | city_Seattle | |
---|---|---|---|
0 | 3999 | 1 | 0 |
1 | 4000 | 1 | 0 |
2 | 4001 | 1 | 0 |
3 | 3499 | 0 | 0 |
4 | 3500 | 0 | 0 |
5 | 3501 | 0 | 0 |
6 | 2499 | 0 | 1 |
7 | 2500 | 0 | 1 |
8 | 2501 | 0 | 1 |
#利用分類變量和Rent進行訓練線性模型 lin_reg.fit(dummy_df[['city_SF', 'city_Seattle']], dummy_df['Rent'])
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False) w2 = lin_reg.coef_ b2 = lin_reg.intercept_
#迴歸係數 lin_reg.coef_
array([ 500., -1000.])
#截距項 lin_reg.intercept_
3500.0
兩種方法區別
獨熱編碼處理,截距項表示目標變量 Rent 的全局均值,每一個線性係數表示城市平均租金與全局均值的差別。
虛擬編碼處理,截距項表示參考類別(字節都爲零的那個類別)的 y 的平均值,例子中的參考類別是紐約市。第 i 類特徵的係數等於第 i 類特徵的平均值與參考類別的平均值之差。
$x_1$ | $x_2$ | $x_3$ | $b$ | |
---|---|---|---|---|
獨熱編碼 | 166.67 | 666.67 | -833.33 | 3333.33 |
虛擬編碼 | 0 | 500 | -1000 | 3500 |
%matplotlib inline import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt import seaborn as sns sns.set(style="whitegrid", font_scale=1.4, color_codes=True)
sns.swarmplot(x="City", y="Rent", data=df);
print('One-hot encoding weights: ',w1, ' and intercept: ', b1) print('Dummy encoding weights: ' , w2, ' and intercept: ', b2)
One-hot encoding weights: [ 166.66666667 666.66666667 -833.33333333] and intercept: 3333.33333333 Dummy encoding weights: [ 500. -1000.] and intercept: 3500.0
# 獨熱編碼和虛擬編碼對比迴歸結果 # 獨熱編碼的線性迴歸結果 one_hot_y = [((w1[0] * one_hot_df.city_NYC[i]) + (w1[1] * one_hot_df.city_SF[i]) + (w1[2] * one_hot_df.city_Seattle[i]) + b1) for i in range(0,one_hot_df.shape[0])] # 虛擬編碼的線性迴歸結果 dummy_y = [((w2[0] * dummy_df.city_SF[i]) + (w2[1] * dummy_df.city_Seattle[i]) + b2) for i in range(0,dummy_df.shape[0])] print(one_hot_y) print(dummy_y)
[4000.0, 4000.0, 4000.0, 3500.0, 3500.0, 3500.0, 2500.0, 2500.0, 2500.0] [3999.9999999999995, 3999.9999999999995, 3999.9999999999995, 3500.0, 3500.0, 3500.0, 2500.0, 2500.0, 2500.0]
另外一種類型的變量編碼方法被稱爲效應編碼。效應編碼與虛擬編碼很是類似,區別在於參考類別如今由全部 -1的向量表示。
城市 | $e_1$ | $e_2$ |
---|---|---|
San Francisco | 1 | 0 |
New York | 0 | 1 |
Seattle | -1 | -1 |
效應編碼與虛擬編碼很是類似,可是在線性迴歸模型中獲得的結果更容易解釋。
接下來演示如何將效應編碼做爲輸入。截距項表示目標變量的全局平均值,單個係數表示單個類別的平均值與全局平均值的差別程度。(這稱爲類別或級別的主要影響,所以稱爲「效應編碼」)。獨熱編碼實際上產生了相同的截距和係數,但在這種狀況下,每一個城市都有線性係數。在效應編碼中,沒有單個特徵表明參考類別。(參見什麼是效應編碼?瞭解更多詳細信息。)
小案例
效應編碼的線性迴歸模型
#複製原始數據 effect_df = dummy_df.copy() #進行效應編碼 effect_df.loc[3:5, ['city_SF', 'city_Seattle']] = -1.0 effect_df
Rent | city_SF | city_Seattle | |
---|---|---|---|
0 | 3999 | 1.0 | 0.0 |
1 | 4000 | 1.0 | 0.0 |
2 | 4001 | 1.0 | 0.0 |
3 | 3499 | -1.0 | -1.0 |
4 | 3500 | -1.0 | -1.0 |
5 | 3501 | -1.0 | -1.0 |
6 | 2499 | 0.0 | 1.0 |
7 | 2500 | 0.0 | 1.0 |
8 | 2501 | 0.0 | 1.0 |
#訓練線性模型 lin_reg.fit(effect_df[['city_SF','city_Seattle']], effect_df['Rent'])
#迴歸係數 lin_reg.coef_
array([ 666.66666667, -833.33333333])
#截距項 lin_reg.intercept_
3333.3333333333335
獨熱編碼 | 虛擬編碼 | 效應編碼 | |
---|---|---|---|
優勢 | 容易理解 可處理丟失數據 |
不冗餘 惟一性 可解釋性強 |
不冗餘 可處理丟失數據 |
缺點 | 冗餘的 非惟一性 可解釋性弱 |
沒法輕鬆處理丟失數據 全零向量映射到參考類別 |
-1 的存儲和計算都是複雜的 無現成工具包 |
當類別的數量級很是大時,上述三種編碼技術都會崩潰,所以處理多類別分類變量須要不一樣的策略。
互聯網上的自動數據收集能夠生成多類別分類變量。這在定向廣告和欺詐檢測等應用中很常見。在定向廣告中,任務是根據用戶的搜索查詢或當前頁面,爲用戶匹配一組廣告。功能包括用戶 id、廣告的網站域名、搜索查詢、當前頁面,以及這些功能的全部可能的組合。(查詢是一個文本字符串,能夠被分割成一般的文本特徵。可是,查詢一般很短,一般由短語組成。所以,在這種狀況下,最好的作法一般是保持它們的完整性,或者經過散列函數來簡化存儲和比較。
難點在於找到一個良好的特徵表示,既能有效利用內存,又能快速生成準確的模型。
現有的解決方案:
特徵縮放。
使用簡單one-hot 編碼是一個有效的選擇。對於微軟的搜索廣告引擎,使用了貝葉斯迴歸模型中的這種二進制值特性,能夠經過簡單的更新在線訓練。與此同時,其餘團體則主張採用縮放方法。雅虎的研究人員青睞特性哈希法。與此同時,微軟的其餘員工接受了bin-counting
的想法。
咱們將先介紹這三種方法,而後討論它們的優缺點。
哈希函數是一個肯定性函數,它將一個潛在的無界整數映射到一個有界的整數範圍[1,m]。
因爲輸入域可能大於輸出範圍,所以能夠將多個數字映射到相同的輸出。這就是所謂的碰撞。一個統一的哈希函數能夠確保將編號相同的數據映射到 m 個箱子中的一個。
總而言之,咱們能夠把哈希函數想象成一臺機器,它輸入編號的球,並將其映射到到 m 個箱子中的一個。號碼相同的球老是會被送到相同的箱子中。
哈希函數能夠以任何能夠用數字表示的對象構造(對於任何能夠存儲在計算機上的數據都是如此):數字、字符串、複雜結構等等。
哈希函數將鍵映射到箱子。這樣既保持了特徵空間,又減小了機器學習訓練和評估週期中的存儲
和處理時間。
當特徵不少的時候,存儲特徵向會佔用不少空間。特徵哈希經過對特徵 id 應用哈希函數將原始特徵向
量壓縮爲一個 m 維向量。例如,若是原始特徵是文檔中的單詞,那麼不管輸入中有多少個惟一的單詞,
通過哈希函數處理以後的詞彙表大小都是固定的m。
關於文本數據的特徵哈希法
def hash_features(word_list, m): output = [0] * m for word in word_list: index = hash_fcn(word) % m #hash_fcn()是須要根據不一樣情景本身定義的 output[index] += 1 return output
特性哈希的另外一種變體添加了一個符號函數,這樣就能夠從哈希容器中增減成員。
這能夠確保哈希特徵之間的內部積與原始特徵之間的指望值相等。
def hash_features(word_list, m): output = [0] * m for word in word_list: index = hash_fcn(word) % m # hash_fcn()是須要根據不一樣情景本身定義的 sign_bit = sign_hash(word) % 2 # sign_hash()一樣是須要本身定義 if (sign_bit == 0): output[index] -= 1 else: output[index] += 1 return output
哈希後內積的值在原始內積的$O\left(\frac{1}{\sqrt{m}}\right)$範圍內。所以,哈希表的大小m是在必定偏差範圍內被選擇的。
在實踐中,選擇正確的m可能須要反覆試錯。
特徵哈希方法可用於處理特徵向量的和迴歸係數的內積,如線性模型和核方法。
特性哈希的一個缺點是,哈希特徵是原始特徵的集合,再也不具備可解釋性。
在這個小案例中,咱們將使用Yelp評論數據集來演示使用Scikit-learn中FeatureHasher的存儲和可解釋性之間的權衡。
# 加載前 10000 條評論 f = open('data/yelp/v6/yelp_dataset_challenge_academic_dataset/yelp_academic_dataset_review.json') #數據書籍原文並無提供 js = [] for i in range(10000): js.append(json.loads(f.readline())) f.close() review_df = pd.DataFrame(js) review_df.shape
(10000, 8)
# 咱們將m的值定義爲business_id的惟一值的數量 m = len(review_df.business_id.unique()) print(m)
528
from sklearn.feature_extraction import FeatureHasher h = FeatureHasher(n_features=m, input_type='string') f = h.transform(review_df['business_id'])
# 這怎麼影響可解釋性的呢 review_df['business_id'].unique().tolist()[0:5]
['vcNAWiLM4dR7D2nwwJ7nCA', 'UsFtqoBl7naz8AVUBZMjQQ', 'cE27W9VPgO88Qxe4ol6y_g', 'HZdLhv6COCleJMo7nPl-RA', 'mVHrayjG3uZ_RLHkLj-AMg']
f.toarray()
array([[ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.], ..., [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0., 0., ..., 0., 0., 0.]])
可解釋性不是很好。可是,讓咱們看看咱們的特性的存儲大小
# 咱們能夠經過觀察它們的大小來了解它們在將來將如何發揮做用 from sys import getsizeof print('Our pandas Series, in bytes: ', getsizeof(review_df['business_id'])) print('Our hashed numpy array, in bytes: ', getsizeof(f))
Our pandas Series, in bytes: 790104
Our hashed numpy array, in bytes: 56
咱們能夠清楚地看到,使用特徵哈希將使咱們在計算上受益,犧牲了可解釋性。當從數據探索和可視化發展到大型數據集的機器學習工做流時,這是一個很容易接受的折衷方案。
Bin-counting
是機器學習中的一個常年不斷的從新發現。它已經被從新發明並用於各類各樣的應用,從廣告點進率預測到硬件分支預測。然而,因爲它是一種特徵工程技術,而不是一種建模或優化方法,所以目前尚未相關的研究論文。關於這項技術最詳細的描述能夠在 MishaBilenko 的 博客"Big Learning Made Easy with Counts" 找到。
Bin-counting
的想法很是簡單:與其使用類別變量的值做爲特徵,不如使用該值下目標變量的條件機率。換句話說,不是編碼和類別值統一塊兒來,而是計算該值與咱們但願預測的目標之間的關聯統計數據。對於若是熟悉樸素貝葉斯分類器,由於在假設全部特徵都是條件獨立的狀況下,這個數據就是這個類別的條件機率。
舉例說明
User | Number of clicks | Number of non-clicks | Proy of click | QueryHash,AdDomain | Number of clicks | Number of non-clicks | Probability of click |
---|---|---|---|---|---|---|---|
Alice | 5 | 120 | 0.04 | 0x598fd4fe, foo.com |
5,000 | 30,000 | 0.167 |
Bob | 20 | 230 | 0.08 | 0x50fa3cc0, bar.org |
100 | 9,00 | 0.100 |
Joe | 2 | 3 | 0.4 | 0x437a45e1, qux.net |
6 | 18 | 0.250 |
Bin-counting
假定歷史數據可用於統計計算。包含分類變量每一個可能值的彙總歷史數據。
根據用戶點擊任何廣告的次數和未點擊的次數,咱們能夠計算出用戶"Alice"點擊任何廣告的機率。
相似地,咱們能夠計算任何查詢-廣告-域組合的點擊機率。
在訓練時,每次咱們看到"Alice",使用她的點擊機率做爲模型的輸入特徵。
對於像"0x437a45e1,qux.net"這樣的 QueryHash-AdDomain 組合也是如此。
z假設有 10000 個用戶。一個獨熱編碼將生成一個長度爲 10,000 的稀疏向量,對應於當前數據點值的列中
只有一個 1。Bin-counting
將全部 10,000 個二進制列編碼爲一個實際值介於 0 和 1 之間的單一特徵。
除了歷史點擊機率以外,咱們還能夠包括其餘特徵:
咱們這裏的例子是預測廣告點擊率。可是這種技術很容易應用於通常的二分類。它也能夠很容易地擴展到多分類問題。
Bin-Counting
的計算
它經過提出這樣一個問題來考察它們之間的聯繫強度:
"當 x 爲真時y 有多大可能爲真。"
例如,咱們可能會問,"愛麗絲點擊廣告的可能性比通常人高多少?"
這裏,x 是二進制變量"當前用戶是Alice",y 是變量"是否點擊"
這種計算使用了所謂的列聯表(基本上是對應於 x 和y 的 4 種可能組合的 4 個數字)。
Click | Non-Click | Total | |
---|---|---|---|
Alice | 5 | 120 | 125 |
Not Alice | 995 | 18,880 | 19,875 |
Total | 1000 | 19,000 | 20,000 |
給定一個輸入變量$x$,和目標變量$y$,定義
odds_ratio
$ = \frac{(P(Y=1 | X=1) / P(Y=0 | X=1)}{(P(Y=1 | X=0) / P(Y=0 | X=0)}$
在咱們的例子中,這就是"Alice點擊廣告而不是不點擊的可能性有多大"和"其餘人點擊而不是不點擊的可
能性有多大"之間的比率在這個例子中是
odds ratio (user, ad click)
$=\frac{(5 / 125) /(120 / 125)}{(995 / 19,875) /(18,880 / 19,875)}=0.7906$
更簡單地說,咱們能夠看一下分子,它檢查單個用戶(Alice)點擊廣告與不點擊廣告的可能性有多大。這
適用於具備多類別的分類變量,而不只僅是兩個值。
odds ratio (Alice, ad click)
$=\frac{5 / 125}{120 / 125}=0.04166$
比值很容易變得很是小或很是大。例如,有些用戶幾乎歷來不點擊廣告,也許有些用戶點擊廣告的頻率要比不點擊的頻繁得多。
對數變換再次來拯救咱們,對數的另外一個有用的特性是它把除法變成減法。
log-odds ratio (Alice, ad click)
$=\log \left(\frac{5}{125}\right)-\log \left(\frac{120}{125}\right)=-3.178$
簡而言之,Bin-counting
將一個分類變量轉換爲關於值的統計信息。
它將分類變量的大型稀疏矩陣表示轉換爲很是小的稠密的實數數值表示。(以下圖)
在實現方面,bin-counting
須要在每一個類別及其相關計數之間存儲一個映射。(剩下的統計數據能夠從原始計數中動態地推導出來。)所以它須要$ O(k)$的複雜度,其中 k 是分類變量惟一值的個數。
小案例
點進率數據集統計案例。
競賽是用廣告數據來預測點進率,可是咱們將用它來演示如何經過計算垃圾桶來大大減小大量流數據的特徵空間。
import pandas as pd
df = pd.read_csv('data/train_subset.csv')
df.head(3)
id | click | hour | C1 | banner_pos | site_id | site_domain | site_category | app_id | app_domain | ... | device_type | device_conn_type | C14 | C15 | C16 | C17 | C18 | C19 | C20 | C21 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1.000009e+18 | 0 | 14102100 | 1005 | 0 | 1fbe01fe | f3845767 | 28905ebd | ecad2386 | 7801e8d9 | ... | 1 | 2 | 15706 | 320 | 50 | 1722 | 0 | 35 | -1 | 79 |
1 | 1.000017e+19 | 0 | 14102100 | 1005 | 0 | 1fbe01fe | f3845767 | 28905ebd | ecad2386 | 7801e8d9 | ... | 1 | 0 | 15704 | 320 | 50 | 1722 | 0 | 35 | 100084 | 79 |
2 | 1.000037e+19 | 0 | 14102100 | 1005 | 0 | 1fbe01fe | f3845767 | 28905ebd | ecad2386 | 7801e8d9 | ... | 1 | 0 | 15704 | 320 | 50 | 1722 | 0 | 35 | 100084 | 79 |
<p>3 rows × 24 columns</p>
# 分類變量的惟一值個數 len(df['device_id'].unique())
device_id
這個特徵的惟一值數量是 7201 個
對於每個分類變量 $\theta$ ,咱們都須要進行以下轉換計算:
$\theta$ = [$N^+$, $N^-$, $log(N^+)-log(N^-)$, 其餘衍生變量]
$N^+$ = $p(+)$ = $n^+/(n^+ + n^-)$
$N^-$ = $p(-)$ = $n^-/(n^+ + n^-)$
$log(N^+)-log(N^-)$ = $\frac{p(+)}{p(-)}$
$n^+$是表明一個特徵
其餘衍生變量=: (這裏沒有展現)
代碼實現以下所示
def click_counting(x, bin_column): clicks = pd.Series(x[x['click'] > 0][bin_column].value_counts(), name='clicks') no_clicks = pd.Series(x[x['click'] < 1][bin_column].value_counts(), name='no_clicks') counts = pd.DataFrame([clicks,no_clicks]).T.fillna('0') counts['total'] = counts['clicks'].astype('int64') + counts['no_clicks'].astype('int64') return counts def bin_counting(counts): counts['N+'] = counts['clicks'].astype('int64').divide(counts['total'].astype('int64')) counts['N-'] = counts['no_clicks'].astype('int64').divide(counts['total'].astype('int64')) counts['log_N+'] = counts['N+'].divide(counts['N-']) # 咱們直接篩選出['N+', 'N-', 'log_N+']這些字段 bin_counts = counts.filter(items= ['N+', 'N-', 'log_N+']) return counts, bin_counts
# 以device_id爲例 bin_column = 'device_id' device_clicks = click_counting(df.filter(items= [bin_column, 'click']), bin_column) device_all, device_bin_counts = bin_counting(device_clicks)
# 檢驗是否數目一致 len(device_bin_counts) # 7201
因而咱們利用bin-counting
的特徵處理方法,新生成的數據表結果以下。
device_all.sort_values(by = 'total', ascending=False).head(4)
clicks | no_clicks | total | N+ | N- | log_N+ | |
---|---|---|---|---|---|---|
a99f214a | 15729 | 71206 | 86935 | 0.180928 | 0.819072 | 0.220894 |
c357dbff | 33 | 134 | 167 | 0.197605 | 0.802395 | 0.246269 |
31da1bd0 | 0 | 62 | 62 | 0.000000 | 1.000000 | 0.000000 |
936e92fb | 5 | 54 | 59 | 0.084746 | 0.915254 | 0.092593 |
# 對比獨熱編碼和binning-count兩種方法處理後的結果 from sys import getsizeof print('Our pandas Series, in bytes: ', getsizeof(df.filter(items= ['device_id', 'click']))) print('Our bin-counting feature, in bytes: ', getsizeof(device_bin_counts))
Our pandas Series, in bytes: 7,300,031
Our bin-counting feature, in bytes: 525,697
從這裏咱們能夠清楚地看到,bin-counting
處理完後,數據量下降了一個數量級。
獨熱編碼方法 | 特徵哈希方法 | Bin-counting方法 | |
---|---|---|---|
優勢 | 最容易實現、多是最準確的 、可線上進行 | 容易實現、訓練成本低 適應新的類別、可處理稀有類別 可線上進行 | 訓練時最小的計算負擔、容易適應新的類別使用樹模型可解釋性強 |
缺點 | 計算效率低、不適應不斷增加的類別、只適用線性模型 | 僅適用於線性或核模型哈希後特徵不可解釋、準確度報告不一 | 須要歷史數據、須要延遲更新,不適合線上進行、更高的過擬合風險 |
沒有一個方法是完美的,選擇使用哪個取決於所採用的機器學習模型。線性模型訓練成本更低,更適合採用獨熱編碼。基於樹的模型一般採用Bin-counting
的方法。特徵哈希方法須要結合實際狀況,靈活運用。
zeof(device_bin_counts))
Our pandas Series, in bytes: **7,300,031** Our bin-counting feature, in bytes: **525,697** 從這裏咱們能夠清楚地看到,`bin-counting`處理完後,數據量下降了一個數量級。 優缺點對比 ---------- | | 獨熱編碼方法 | 特徵哈希方法 | Bin-counting方法 | | ---- | ------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | 優勢 | 最容易實現、多是最準確的 、可線上進行 | 容易實現、訓練成本低 適應新的類別、可處理稀有類別 可線上進行 | 訓練時最小的計算負擔、容易適應新的類別使用樹模型可解釋性強 | | 缺點 | 計算效率低、不適應不斷增加的類別、只適用線性模型 | 僅適用於線性或核模型哈希後特徵不可解釋、準確度報告不一 | 須要歷史數據、須要延遲更新,不適合線上進行、更高的過擬合風險 | 沒有一個方法是完美的,選擇使用哪個取決於所採用的機器學習模型。線性模型訓練成本更低,更適合採用獨熱編碼。基於樹的模型一般採用`Bin-counting`的方法。特徵哈希方法須要結合實際狀況,靈活運用。 *本文內容整理、翻譯Principl的《Feature Engineering for Machine Learning》* *原文代碼地址:https://github.com/alicezheng/feature-engineering-book.*