作者:韓信子@ShowMeAI
教程地址:https://www.showmeai.tech/tutorials/41
本文地址:https://www.showmeai.tech/article-detail/208
聲明:版權所有,轉載請聯系平台與作者並注明出處
收藏ShowMeAI查看更多精彩內容
引言
上圖為大家熟悉的機器學習建模流程圖,ShowMeAI在前序機器學習實戰文章 Python機器學習算法應用實踐中和大家講到了整個建模流程非常重要的一步,是對於數據的預處理和特征工程,它很大程度決定了最后建模效果的好壞,在本篇內容匯總,我們給大家展開對數據預處理和特征工程的實戰應用細節做一個全面的解讀。
特征工程
首先我們來了解一下「特征工程」,事實上大家在ShowMeAI的實戰系列文章 Python機器學習綜合項目-電商銷量預估 和 Python機器學習綜合項目-電商銷量預估<進階> 中已經看到了我們做了特征工程的處理。
如果我們對特征工程(feature engineering)做一個定義,那它指的是:利用領域知識和現有數據,創造出新的特征,用於機器學習算法;可以手動(manual)或自動(automated)。
- 特征:數據中抽取出來的對結果預測有用的信息。
- 特征工程:使用專業背景知識和技巧處理數據,使得特征能在機器學習算法上發揮更好的作用的過程。
在業界有一個很流行的說法:
數據與特征工程決定了模型的上限,改進算法只不過是逼近這個上限而已。
這是因為,在數據建模上,「理想狀態」和「真實場景」是有差別的,很多時候原始數據並不是規矩干凈含義明確充分的形態:
而特征工程處理,相當於對數據做一個梳理,結合業務提取有意義的信息,以干凈整齊地形態進行組織:
特征工程有着非常重要的意義:
- 特征越好,靈活性越強。只要特征選得好,即使是一般的模型(或算法)也能獲得很好的性能,好特征的靈活性在於它允許你選擇不復雜的模型,同時運行速度也更快,也更容易理解和維護。
- 特征越好,構建的模型越簡單。有了好的特征,即便你的參數不是最優的,你的模型性能也能仍然會表現的很好,所以你就不需要花太多的時間去尋找最優參數,這大大的降低了模型的復雜度,使模型趨於簡單。
- 特征越好,模型的性能越出色。顯然,這一點是毫無爭議的,我們進行特征工程的最終目的就是提升模型的性能。
本篇內容,ShowMeAI帶大家一起來系統學習一下特征工程,包括「特征類型」「數據清洗」「特征構建」「特征變換」「特征選擇」等板塊內容。
我們這里用最簡單和常用的Titanic數據集給大家講解。
Titanic數據集是非常適合數據科學和機器學習新手入門練習的數據集,數據集為1912年泰坦尼克號沉船事件中一些船員的個人信息以及存活狀況。我們可以根據數據集訓練出合適的模型並預測新數據(測試集)中的存活狀況。
Titanic數據集可以通過seaborn工具庫直接加載,如下代碼所示:
import pandas as pd
import numpy as np
import seaborn as sns
df_titanic = sns.load_dataset('titanic')
其中數據集的數據字段描述如下圖所示:
1.特征類型
在具體演示Titanic的數據預處理與特征工程之前,ShowMeAI再給大家構建一些關於數據的基礎知識。
1.1 結構化 vs 非結構化數據
數據可以分為「結構化數據」和「非結構化數據」,比如在互聯網領域,大部分存儲在數據庫內的表格態業務數據,都是結構化數據;而文本、語音、圖像視頻等就屬於非結構化數據。
1.2 定量 vs 定性數據
對於我們記錄到的數據,我們通常又可以以「定量數據」和「定性數據」對齊進行區分,其中:
- 定量數據:指的是一些數值,用於衡量數量與大小。
- 例如高度,長度,體積,面積,濕度,溫度等測量值。
- 定性數據:指的是一些類別,用於描述物品性質。
- 例如紋理,味道,氣味,顏色等。
如下圖是兩類數據示例以及它們常見的處理分析方法的總結:
2.數據清洗
實際數據挖掘或者建模之前,我們會有「數據預處理」環節,對原始態的數據進行數據清洗等操作處理。因為現實世界中數據大體上都是不完整、不一致的「臟數據」,無法直接進行數據挖掘,或者挖掘結果差強人意。
「臟數據」產生的主要成因包括:
- 篡改數據
- 數據不完整
- 數據不一致
- 數據重復
- 異常數據
數據清洗過程包括數據對齊、缺失值處理、異常值處理、數據轉化等數據處理方法,如下圖所示:
下面我們注意對上述提到的處理方法做一個講解。
2.1 數據對齊
采集到的原始數據,格式形態不一,我們會對時間、字段以及相關量綱等進行數據對齊處理,數據對齊和規整化之后的數據整齊一致,更加適合建模。如下圖為一些處理示例:
(1) 時間
- 日期格式不一致【
2022-02-20
、20220220
、2022/02/20
、20/02/2022
】。 - 時間戳單位不一致,有的用秒表示,有的用毫秒表示。
- 使用無效時間表示,時間戳使用0表示,結束時間戳使用FFFF表示。
(2) 字段
- 姓名寫了性別,身份證號寫了手機號等。
(3) 量綱
- 數值類型統一【如1、2.0、3.21E3、四】。
- 單位統一【如180cm、1.80m】。
2.2 缺失值處理
數據缺失是真實數據中常見的問題,因為種種原因我們采集到的數據並不一定是完整的,我們有一些缺失值的常見處理方式:
具體的處理方式可以展開成下圖:
下面回到我們的Titanic數據集,我們演示一下各種方法:
我們先對數據集的缺失值情況做一個了解(匯總分布):
df_titanic.isnull().sum()
survived 0
pclass 0
sex 0
age 177
sibsp 0
parch 0
fare 0
embarked 2
class 0
who 0
adult_male 0
deck 688
embark_town 2
alive 0
alone 0
(1) 刪除
最直接粗暴的處理是剔除缺失值,即將存在遺漏信息屬性值的對象(字段,樣本/記錄)刪除,從而得到一個完備的信息表。優缺點如下:
- 優點:簡單易行,在對象有多個屬性缺失值、被刪除的含缺失值的對象與初始數據集的數據量相比非常小的情況下有效;
- 不足:當缺失數據所占比例較大,特別當遺漏數據非隨機分布時,這種方法可能導致數據發生偏離,從而引出錯誤的結論。
在我們當前Titanic的案例中,embark_town
字段有2個空值,考慮刪除缺失處理下。
df_titanic[df_titanic["embark_town"].isnull()]
df_titanic.dropna(axis=0,how='any',subset=['embark_town'],inplace=True)
(2) 數據填充
第2大類是我們可以通過一些方法去填充缺失值。比如基於統計方法、模型方法、結合業務的方法等進行填充。
① 手動填充
根據業務知識來進行人工手動填充。
② 特殊值填充
將空值作為一種特殊的屬性值來處理,它不同於其他的任何屬性值。如所有的空值都用unknown
填充。一般作為臨時填充或中間過程。
代碼實現
df_titanic['embark_town'].fillna('unknown', inplace=True)
③ 統計量填充
若缺失率較低,可以根據數據分布的情況進行填充。常用填充統計量如下:
- 中位數:對於數據存在傾斜分布的情況,采用中位數填補缺失值。
- 眾數:離散特征可使用眾數進行填充缺失值。
- 平均值:對於數據符合均勻分布,用該變量的均值填補缺失值。
中位數填充——fare:缺失值較多,使用中位數填充
df_titanic['fare'].fillna(df_titanic['fare'].median(), inplace=True)
眾數填充——embarked:只有兩個缺失值,使用眾數填充
df_titanic['embarked'].isnull().sum()
#執行結果:2
df_titanic['embarked'].fillna(df_titanic['embarked'].mode(), inplace=True)
df_titanic['embarked'].value_counts()
#執行結果:
#S 64
同類均值填充
age:根據sex、pclass和who分組,如果落在相同的組別里,就用這個組別的均值或中位數填充。
df_titanic.groupby(['sex', 'pclass', 'who'])['age'].mean()
age_group_mean = df_titanic.groupby(['sex', 'pclass', 'who'])['age'].mean().reset_index()
def select_group_age_median(row):
condition = ((row['sex'] == age_group_mean['sex']) &
(row['pclass'] == age_group_mean['pclass']) &
(row['who'] == age_group_mean['who']))
return age_group_mean[condition]['age'].values[0]
df_titanic['age'] =df_titanic.apply(lambda x: select_group_age_median(x) if np.isnan(x['age']) else x['age'],axis=1)
④ 模型預測填充
如果其他無缺失字段豐富,我們也可以借助於模型進行建模預測填充,將待填充字段作為Label,沒有缺失的數據作為訓練數據,建立分類/回歸模型,對待填充的缺失字段進行預測並進行填充。
最近距離鄰法(KNN)
- 先根據歐式距離或相關分析來確定距離具有缺失數據樣本最近的K個樣本,將這K個值加權平均/投票來估計該樣本的缺失數據。
回歸(Regression)
- 基於完整的數據集,建立回歸方程。對於包含空值的對象,將已知屬性值代入方程來估計未知屬性值,以此估計值來進行填充。當變量不是線性相關時會導致有偏差的估計,常用線性回歸。
我們以Titanic案例中的age字段為例,講解一下:
- age缺失量較大,這里我們用sex、pclass、who、fare、parch、sibsp六個特征構建隨機森林模型,填充年齡缺失值。
df_titanic_age = df_titanic[['age', 'pclass', 'sex', 'who','fare', 'parch', 'sibsp']]
df_titanic_age = pd.get_dummies(df_titanic_age)
df_titanic_age.head()
# 乘客分成已知年齡和未知年齡兩部分
known_age = df_titanic_age[df_titanic_age.age.notnull()]
unknown_age = df_titanic_age[df_titanic_age.age.isnull()]
# y 即目標年齡
y_for_age = known_age['age']
# X 即特征屬性值
X_train_for_age = known_age.drop(['age'], axis=1)
X_test_for_age = unknown_age.drop(['age'], axis=1)
from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X_train_for_age, y_for_age)
# 用得到的模型進行未知年齡結果預測
y_pred_age = rfr.predict(X_test_for_age)
# 用得到的預測結果填補原缺失數據
df_titanic.loc[df_titanic.age.isnull(), 'age'] = y_pred_age
sns.distplot(df_titanic.age)
⑤ 插值法填充
還可以用插值法對數據填充,細分一下包括線性插值、多重插補、熱平台插補、拉格朗日插值、牛頓插值等。
線性插值法
使用插值法可以計算缺失值的估計值,所謂的插值法就是通過兩點(x0, y0),(x1, y1)估計中間點的值。假設y=f(x)是一條直線,通過已知的兩點來計算函數f(x),然后只要知道x就能求出y,以此方法來估計缺失值。
.interpolate(method = 'linear', axis)
方法將通過linear
插值使用沿着給定axis
的值替換NaN值,這個差值也就是前后或者上下的中間值
df_titanic['fare'].interpolate(method = 'linear', axis = 0)
同時,也可用行值插入
df_titanic['fare'].interpolate(method = 'linear', axis = 1)
多重插補(Multiple Imputation)
多值插補的思想來源於貝葉斯估計,認為待插補的值是隨機的,它的值來自於已觀測到的值。具體實踐上通常是估計出待插補的值,然后再加上不同的噪聲,形成多組可選插補值。根據某種選擇依據,選取最合適的插補值。
多重插補方法分為三個步驟:
- ① 為每個空值產生一套可能的插補值,這些值反映了無響應模型的不確定性;每個值都可以被用來插補數據集中的缺失值,產生若干個完整數據集合;
- ② 每個插補數據集合都用針對完整數據集的統計方法進行統計分析;
- ③ 對來自各個插補數據集的結果,根據評分函數進行選擇,產生最終的插補值。
⑥ 啞變量填充
有另外一種非常有意思的填充方式,叫做「啞變量填充」,在變量為離散型,且不同值較少的情況下可以采用,以Titanic數據為例:
- 性別SEX變量,存在male,fameal,NA(缺失)三個不同的值,可將該列轉換成
IS_SEX_MALE
、IS_SEX_FEMALE
、IS_SEX_NA
。 - 若某個變量存在十幾個不同的值,可根據每個值的頻數,將頻數較小的值歸為一類
other
,降低維度。此做法可最大化保留變量的信息。
以下為參考代碼示例:
sex_list = ['MALE', 'FEMALE', np.NaN, 'FEMALE', 'FEMALE', np.NaN, 'MALE']
df = pd.DataFrame({'SEX': sex_list})
display(df)
df.fillna('NA', inplace=True)
df = pd.get_dummies(df['SEX'],prefix='IS_SEX')
display(df)
# 原始數據
SEX
0 MALE
1 FEMALE
2 NaN
3 FEMALE
4 FEMALE
5 NaN
6 MALE
# 填充后
IS_SEX_FEMALE IS_SEX_MALE IS_SEX_NA
0 0 1 0
1 1 0 0
2 0 0 1
3 1 0 0
4 1 0 0
5 0 0 1
6 0 1
當特征值缺失超過80%以上,建議刪除【或加入「是」「否」標記位信息】,容易影響模型效果
df_titanic.drop(["deck"],axis=1)
2.3 異常值處理
數據質量也會很大程度影響機器學習應用效果,數據的錯誤值或異常值可能會造成測量誤差或異常系統條件的結果,給模型學習帶來很大的問題。實際我們很多時候會有異常值檢測與處理環節,下面給大家做一個梳理。
(1) 異常檢測方法
① 基於統計分析
通常用戶用某個統計分布對數據點進行建模,再以假定的模型,根據點的分布來確定是否異常。
如通過分析統計數據的散度情況,即數據變異指標,對數據的分布情況有所了解,進而通過數據變異指標來發現數據中的異常點數據。
常用的數據變異指標有極差、四分位數間距、均差、標准差、變異系數等等,如變異指標的值大表示變異大、散布廣;值小表示離差小,較密集。
比如,最大最小值可以用來判斷這個變量的取值是否超過了合理的范圍,如客戶的年齡為-20歲或200歲,為異常值。
② 3σ原則
如果數據近似正態分布,在3σ原則下,異常值為一組測定值中與平均值的偏差超過3倍標准差的值。
- 如果數據服從正態分布,距離平均值3σ之外的值出現的概率為P(|x-μ|>3σ)<=0.003,屬於極個別的小概率事件。
- 如果數據不服從正態分布,也可以用遠離平均值的多少倍標准差來描述。
③ 箱線圖分析
大家還記得在數據分析部分有一個很有效的工具叫做箱線圖,箱線圖提供了識別異常值的一個標准:如果一個值小於Q1-1.5IQR或大於Q3+1.5IQR的值,則被稱為異常值。
- Q1為下四分位數,表示全部觀察值中有四分之一的數據取值比它小;
- Q4為上四分位數,表示全部觀察值中有四分之一的數據取值比它大;
- IQR為四分位數間距,是上四分位數Q1與下四分位數Q3的差值,包含了全部觀察值的一半。
箱型圖判斷異常值的方法以四分位數和四分位距為基礎,四分位數具有魯棒性:25%的數據可以變得任意遠並且不會干擾四分位數,所以異常值不能對這個標准施加影響。因此箱型圖識別異常值比較客觀,在識別異常值時有一定的優越性。
sns.catplot(y="fare",x="survived", kind="box", data=df_titanic,palette="Set2")
④ 基於模型檢測
我們也可以基於模型對異常值檢測,基本思路是先建立一個數據模型,那些同模型不能完美擬合的對象就視作異常。
- 如果模型是簇的集合,則異常是不顯著屬於任何簇的對象。
- 在使用回歸模型時,異常是相對遠離預測值的對象。
優點:有堅實的統計學理論基礎,當存在充分的數據和所用的檢驗類型的知識時,這些檢驗可能非常有效。
缺點:對於多元數據,可用的選擇少一些,並且對於高維數據,這些檢測可能性很差。
⑤ 基於距離
我們還有基於距離的方法可以用於異常檢測。這類方法基於下面這個假設:如果一個數據對象和大多數點距離都很遠,那這個對象就是異常。通過定義對象之間的臨近性度量,根據距離判斷異常對象是否遠離其他對象,主要使用的距離度量方法有絕對距離(曼哈頓距離)、歐氏距離和馬氏距離等方法。
-
優點:
- 基於距離的方法比基於統計類方法要簡單得多;因為為一個數據集合定義一個距離的度量要比確定數據集合的分布容易的多。
-
缺點:
- 基於鄰近度的方法需要O(m2)時間,大數據集不適用;
- 該方法對參數的選擇也是敏感的;
- 不能處理具有不同密度區域的數據集,因為它使用全局閾值,不能考慮這種密度的變化。
⑥ 基於密度
一個很直接的異常檢測思路是基於分布密度來做,具體為:考察當前點周圍密度,局部異常點/離群點的局部密度顯著低於大部分近鄰點。這類方法適用於非均勻的數據集。
-
優點:
- 給出了對象是離群點的定量度量,並且即使數據具有不同的區域也能夠很好的處理。
-
缺點:
- 與基於距離的方法一樣,這些方法必然具有O(m2)的時間復雜度。
- 對於低維數據使用特定的數據結構可以達到O(mlogm);
- 參數選擇困難。
- 雖然算法通過觀察不同的k值,取得最大離群點得分來處理該問題,但是,仍然需要選擇這些值的上下界。
⑦ 基於聚類
我們可以基於聚類的方法進行異常檢測,遠離cluster的樣本更可能是異常值。
不過該方法會受到聚類cluster個數k的影響,一種策略是對於不同的簇個數重復該分析;另一種方法是找出大量小簇,其想法是:
- 較小的簇傾向於更加凝聚;
- 如果存在大量小簇時一個對象是異常點,則它多半是一個真正的異常點。
- 不利的一面是一組異常點可能形成小簇而逃避檢測。
-
優點:
- 基於線性和接近線性復雜度(k均值)的聚類技術來發現離群點可能是高度有效的;
- 簇的定義通常是離群點的補,因此可能同時發現簇和離群點。
-
缺點:
- 產生的離群點集和它們的得分可能非常依賴所用的簇的個數和數據中離群點的存在性;
- 聚類算法產生的簇的質量對該算法產生的離群點的質量影響非常大。
⑧ 基於鄰近度的異常點檢測
同樣的,我們也有基於近鄰度的思路來做異常檢測,我們認為異常點遠離大部分的點。這種方法比統計學方法更一般、更容易使用,因為確定數據集的有意義的鄰近性度量比確定它的統計分布更容易。一個對象的異常點得分由到它的K-最近鄰的距離給定,所以異常點得分對K的取值高度敏感:
- 如果K太小(例如1),則少量的鄰近異常異常點可能導致較異常低的異常點得分。
- 如果K太大,則點數少於K的簇中所有的對象可能都成了異常異常點。
為了使該方案對於K的選取更具有魯棒性,可以使用K個最近鄰的平均距離。
-
優點:
- 簡單
-
缺點:
- 基於鄰近度的方法需要O(m2)時間,大數據集不適用;
- 該方法對參數的選擇也是敏感的;
- 不能處理具有不同密度區域的數據集,因為它使用全局閾值,不能考慮這種密度的變化。
在數據處理階段將離群點作為影響數據質量的異常點考慮,而不是作為通常所說的異常檢測目標點,一般采用較為簡單直觀的方法,結合箱線圖和MAD的統計方法判斷變量的離群點。如下為繪制散點圖根據分布直接判斷。
sns.scatterplot(x="fare", y="age", hue="survived",data=df_titanic,palette="Set1")
(2) 異常處理方法
對異常值處理,需要具體情況具體分析,異常值處理方法常用的有以下幾種:
- 刪除含有異常值的記錄;
- 某些篩選出來的異常樣本是否真的是不需要的異常特征樣本,最好結合業務再確認一編,防止正常樣本被過濾。
- 將異常值視為缺失值,交給缺失值處理方法來處理;
- 使用均值/中位數/眾數來修正;
- 不處理。
3.特征構建
前序的數據預處理過程能保證我們拿到干凈整齊准確的數據,但這些數據未必對於建模是最有效的,下一步我們通常會進行特征構建,結合業務場景產生衍生變量來提升數據表達能力和模型建模效果。
3.1 統計特征構建
統計特征是一類非常有效的特征,尤其在時序問題場景中,以下為統計特征構建的一些思考維度和方法:
- ① 基於業務規則、先驗知識等構建新特征。
- ② 四分位數、中位數、平均值、標准差、偏差、偏度、偏鋒、離散系統。
- ③ 構造長、短期統計量(如周、月)。
- ④ 時間衰減(越靠近觀測權重值高)。
回到Titanic數據集,我們來看看結合業務理解,我們可以做哪些新特征:
年齡處理
我們對年齡age字段進行進一步處理,考慮到不同的年齡段對應的人群可能獲救概率不同,我們根據年齡值分成不同區間段,對應到child、young、midlife、old等
def age_bin(x):
if x <= 18:
return 'child'
elif x <= 30:
return 'young'
elif x <= 55:
return 'midlife'
else:
return 'old'
df_titanic['age_bin'] = df_titanic['age'].map(age_bin)
df_titanic['age_bin'].unique()
執行結果:
array(['young', 'midlife', 'child', 'old'], dtype=object)
抽取「稱呼」特征
我們在name字段里,可以看到各種不同的稱呼,如「Mr」「Master」「Dr」等,這些稱呼體現了乘客的身份等信息,我們可以對其做抽取構建新的特征。
# 提取稱呼
df_titanic['title'] = df_titanic['name'].map(lambda x: x.split(',')[1].split('.')[0].strip())
df_titanic['title'].value_counts()
執行結果如下:
Mr 757
Miss 260
Mrs 197
Master 61
Rev 8
Dr 8
Col 4
Ms 2
Major 2
Mlle 2
Dona 1
Sir 1
Capt 1
Don 1
Lady 1
Mme 1
the Countess 1
Jonkheer 1
我們做一個簡單的「稱呼」統計
# 對稱呼細分,是官員,還是皇室,還是女士、先生、小姐
df_titanic['title'].unique()
執行結果:
array(['Mr', 'Mrs', 'Miss', 'Master', 'Don', 'Rev', 'Dr', 'Mme', 'Ms',
'Major', 'Lady', 'Sir', 'Mlle', 'Col', 'Capt', 'the Countess',
'Jonkheer', 'Dona'], dtype=object)
下面我們對這些「稱呼」「稱謂」做一個規范化統一。
title_dictionary = {
"Mr": "Mr",
"Mrs": "Mrs",
"Miss": "Miss",
"Master": "Master",
"Don": "Royalty",
"Rev": "Officer",
"Dr": "Officer",
"Mme": "Mrs",
"Ms": "Mrs",
"Major": "Officer",
"Lady": "Royalty",
"Sir": "Royalty",
"Mlle": "Miss",
"Col": "Officer",
"Capt": "Officer",
"the Countess": "Royalty",
"Jonkheer": "Royalty",
"Dona": 'Mrs'
}
df_titanic['title'] = df_titanic['title'].map(title_dictionary)
df_titanic['title'].value_counts()
執行結果如下:
Mr 757
Miss 262
Mrs 201
Master 61
Officer 23
Royalty 5
抽取家庭規模
在Titanic上,有的成員之間有親屬關系,考慮到家族大小對於最終是否獲救也有影響,我們可以構建一個family_size
的特征,用於表征家庭規模。
df_titanic['family_size'] = df_titanic['sibsp'] + df_titanic['parch'] + 1
df_titanic['family_size'].head()
執行結果如下:
0 2
1 2
2 1
3 2
4 1
3.2 周期值
在電商等場景下,數據有一定的周期規律,我們可以提取一些周期值作為有效信息。
時序周期的一些考慮維度如下:
- ①前n個周期/天/月/年的周期值,如過去5天分位數、平均值等
- ②同比/環比
3.3 數據分桶
數據分桶,是對連續值屬性處理的一種常用方法,它指的是我們把連續數值切段,並把連續值歸屬到對應的段中。數據分桶也叫做數據分箱或離散化。
(1) 等頻、等距分桶
(a) 自定義分箱
指根據業務經驗或者常識等自行設定划分的區間,然后將原始數據歸類到各個區間中。
(b) 等距分箱
按照相同寬度將數據分成幾等份。
從最小值到最大值之間,均分為N等份。如果A、B為最小最大值,則每個區間的長度為W=(B−A)/N,區間邊界值為A+W、A+2W、…、A+(N−1)W。
等距分箱只考慮邊界,每個等份里面的實例數量可能不等。等距分桶的缺點是受到異常值的影響比較大。
(c) 等頻分箱
將數據分成幾等份,每等份數據里面的個數是一樣的。
在等頻分箱中,區間的邊界值要經過計算獲得,最終每個區間包含大致相等的實例數量。比如說N=5,每個區間應該包含大約20%的實例。
- 數值變量分箱
我們先對船票價格做一個等頻切分(大家如果對船票價格進行分布繪圖,會發現是很長尾的分布,並不適合等距切分),看看分開的區間段。
# qcut 等頻率分箱
df_titanic['fare_bin'], bins = pd.qcut(df_titanic['fare'], 5, retbins=True)
df_titanic['fare_bin'].value_counts()
結果如下:
(7.854, 10.5] 184
(21.679, 39.688] 180
(-0.001, 7.854] 179
(39.688, 512.329] 176
(10.5, 21.679] 172
bins #array([ 0. , 7.8542, 10.5 , 21.6792, 39.6875, 512.3292])
下面根據區間段對其進行等頻切分
# 對船票fare進行分段分桶
def fare_cut(fare):
if fare <= 7.8958:
return 0
if fare <= 10.5:
return 1
if fare <= 21.6792:
return 2
if fare <= 39.6875:
return 3
return 4
df_titanic['fare_bin'] = df_titanic['fare'].map(fare_cut)
相比船票價格,年齡age字段的分布更加集中,且區間大小比較明確,我們采用等距切分,代碼如下:
# cut 等距離分箱
bins = [0, 12, 18, 65, 100]
pd.cut(df_titanic['age'], bins).value_counts
(2) Best-KS分桶
- 1.將特征值值進行從小到大的排序。
- 2.計算出KS最大的那個值,即為切點,記為D。然后把數據切分成兩部分。
- 3.重復步驟2,進行遞歸,D左右的數據進一步切割。直到KS的箱體數達到我們的預設閾值即可。
- 4.連續型變量:分箱后的KS值<=分箱前的KS值
- 5.分箱過程中,決定分箱后的KS值是某一個切點,而不是多個切點的共同作用。這個切點的位置是原始KS值最大的位置。
(3) 卡方分桶
自底向上的(即基於合並的)數據離散化方法,依賴於卡方檢驗:具有最小卡方值的相鄰區間合並在一起,直到滿足確定的停止准則。
基本思想:
如果兩個相鄰的區間具有非常類似的類分布,則這兩個區間可以合並;否則,它們應當保持分開。而低卡方值表明它們具有相似的類分布。
實現步驟:
- ①預先定義一個卡方的閾值
- ②初始化;根據要離散的屬性對實例進行排序,每個實例屬於一個區間
- ③合並區間
- 計算每一對相鄰區間的卡方值
- 將卡方值最小的一對區間合並
(4) 最小熵法分箱
還有最小熵分箱法,需要使總熵值達到最小,也就是使分箱能夠最大限度地區分因變量的各類別。
熵是信息論中數據無序程度的度量標准,提出信息熵的基本目的是找出某種符號系統的信息量和冗余度之間的關系,以便能用最小的成本和消耗來實現最高效率的數據存儲、管理和傳遞。
數據集的熵越低,說明數據之間的差異越小,最小熵划分就是為了使每箱中的數據具有最好的相似性。給定箱的個數,如果考慮所有可能的分箱情況,最小熵方法得到的箱應該是具有最小熵的分箱。
3.4 特征組合
我們在有些場景下會考慮特征組合構建強特征,如下為常用的特征組合構建方式:
- 離散+離散:構建笛卡爾積(即兩兩組合「且」關系)。
- 離散+連續:連續特征分桶后進行笛卡爾積或基於類別特征group by構建統計特征。
- 連續+連續:加減乘除,多項式特征,二階差分等。
- 多項式特征
針對連續值特征,我們對幾個特征構建多項式特征,以達到特征組合與高階增強的作用。
在Titanic的例子中,如下為數值型特征:
df_titanic_numerical = df_titanic[['age','sibsp','parch','fare','family_size']]
df_titanic_numerical.head()
我們可以參考下述代碼構建多項式特征
# 擴展數值特征
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, include_bias=False, interaction_only=False)
df_titanic_numerical_poly = poly.fit_transform(df_titanic_numerical)
pd.DataFrame(df_titanic_numerical_poly, columns=poly.get_feature_names()).head()
在構建完成特征后,我們查看下衍生新特征變量的相關性情況,下面的熱力圖heatmap里顏色越深相關性越大:
sns.heatmap(pd.DataFrame(df_titanic_numerical_poly, columns=poly.get_feature_names()).corr())
4.特征變換
我們對於構建完的特征,會做一些「特征變換」的操作,以適應不同的模型,更好地完成建模。
4.1 標准化(Standardization)
標准化操作也稱作Z-score變換,它使數值特征列的算數平均為0,方差(以及標准差)為1,如下圖所示。
注意:如果數值特征列中存在數值極大或極小的outlier(通過EDA發現),應該使用更穩健(robust)的統計數據:用中位數而不是算術平均數,用分位數(quantile)而不是方差。這種標准化方法有一個重要的參數:(分位數下限,分位數上限),最好通過EDA的數據可視化確定。免疫outlier。
標准化操作的參考代碼如下:
from sklearn.preprocessing import StandardScale
#標准化模型訓練
Stan_scaler = StandardScaler()
Stan_scaler.fit(x)
x_zscore = Stan_scaler.transform(x)
x_test_zscore = Stan_scaler.transform(x_test)
joblib.dump(Stan_scaler,'zscore.m') #寫入文件
4.2 歸一化(Normalization)
歸一化操作會基於向量模長調整數據幅度大小,但並不會改變原始數據的順序。如下圖所示:
4.3 幅度縮放(scaling)
幅度縮放是為了讓不同特征的取值在大體一致的數量級和數據區間內,比較常用的方法是最大最小值縮放,如下圖所示:
下面為幅度縮放操作的參考代碼:
from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
min_max_scaler.fit_transform(x)
x_minmax = min_max_scaler.transform(x)
x_test_minmax = min_max_scaler.transform(x_test)
joblib.dump(min_max_scaler,'min_max_scaler.m') #寫入文件
4.4 歸一化 VS 標准化
歸一化和標准化是兩個非常常見的特征變換操作,下面我們來對比一下標准化和歸一化:
- 目的不同,歸一化是為了消除綱量壓縮到[0,1]區間;標准化只是調整特征整體的分布。
- 歸一化與最大,最小值有關;標准化與均值,標准差有關。
- 歸一化輸出在[0,1]之間;標准化無限制。
它們分別的適用場景可以歸納總結如下:
-
在分類、聚類算法中(參考ShowMeAI教程 圖解機器學習算法:從入門到精通系列教程),需要使用距離來度量相似性的時候(如SVM、KNN)或者使用PCA技術進行降維的時候,標准化(Z-score standardization)表現更好。
-
在不涉及距離度量、協方差計算、數據不符合正太分布的時候,可以使用第一種方法或其他歸一化方法。例如圖像處理時,將RGB圖像轉換為灰度圖像后將其值限定在[0,255]的范圍。
-
基於樹的模型(如隨機森林、GBDT、XGBoost、LightGBM等,具體模型參考ShowMeAI教程 圖解機器學習算法:從入門到精通系列教程)不需要進行特征的歸一化。如果是基於參數的模型或者基於距離的模型(邏輯回歸、K-Means聚類、神經網絡等),因為需要對參數或者距離進行計算,都需要進行歸一化。
4.5 非線性變換
我們在有些場景下,還會對數值字段進行分布調整或者校正,利用統計或數學變換來減輕數據分布傾斜的影響。使原本密集的區間的值盡可能的分散,原本分散的區間的值盡量的聚合。
大部分變換函數都屬於冪變換函數簇,主要作用是穩定方差,保持分布接近於正態分布並使得數據與分布的平均值無關。
我們來看看一些典型的非線性統計變換。
(1) log變換
log變換通常用來創建單調的數據變換。主要作用為穩定方差,始終保持分布接近於正態分布並使得數據與分布的平均值無關。
- log變換傾向於拉伸那些落在較低的幅度范圍內自變量值的范圍,傾向於壓縮或減少更高幅度范圍內的自變量值的范圍,從而使得傾斜分布盡可能的接近正態分布。
- 針對一些數值連續特征的方差不穩定,特征值重尾分布我們需要采用log化來調整整個數據分布的方差,屬於方差穩定型數據轉換。
log變換屬於冪變換函數簇,數學表達式為
下面我們對Titanic數據集中的船票價格字段進行log1p變換,示例代碼如下:
sns.distplot(df_titanic.fare,kde=False)
df_titanic['fare_log'] = np.log((1+df_titanic['fare']))
sns.distplot(df_titanic.fare_log,kde=False)
(2) box-cox變換
box-cox變換是box和cox在1964年提出的一種廣義冪變換方法,是統計建模中常用的一種數據變換,用於連續的響應變量不滿足正態分布的情況。box-cox變換之后,可以一定程度上減小不可觀測的誤差和預測變量的相關性。
box-cox變換的主要特點是引入一個參數,通過數據本身估計該參數進而確定應采取的數據變換形式,box-cox變換可以明顯地改善數據的正態性、對稱性和方差相等性,對許多實際數據都是行之有效的。
box-cox變換函數數學表達式如下:
生成的變換后的輸出y,是輸入x和變換參數的函數;當λ=0時,該變換就是自然對數log變換,前面我們已經提到過了。λ的最佳取值通常由最大似然或最大對數似然確定。
下面我們對Titanic數據集中的船票價格字段進行box-cox變換,示例代碼如下:
# 從數據分布中移除非零值
fare_positive_value = df_titanic[(~df_titanic['fare'].isnull()) & (df_titanic['fare']>0)]['fare']
import scipy.stats as spstats
# 計算最佳λ值
l, opt_lambda = spstats.boxcox(fare_positive_value)
print('Optimal lambda value:', opt_lambda) # -0.5239075895755266
# 進行 Box-Cox 變換
fare_boxcox_lambda_opt = spstats.boxcox(df_titanic[df_titanic['fare']>0]['fare'],lmbda=opt_lambda)
sns.distplot(fare_boxcox_lambda_opt,kde=Fal
4.6 離散變量處理
對於類別型的字段特征(比如顏色、類型、好壞程度),有很多模型並不能直接處理,我們對其進行編碼后能更好地呈現信息和支撐模型學習。有以下常見的類別型變量編碼方式:
(1) 標簽編碼(label encoding)
標簽編碼(label encoding)是最常見的類別型數據編碼方式之一,編碼值介於 0 和 n_classes-1 之間的標簽。
例如:比如有[dog,cat,dog,mouse,rabbit],我們把其轉換為[0,1,0,2,3]。
- 優點:相對於OneHot編碼,LabelEncoder編碼占用內存空間小,並且支持文本特征編碼。
- 缺點:它的編碼方式給不同類別帶來了額外的大小順序關系,在有些計算型模型(比如邏輯回歸)里有影響,它可以使用在樹模型中。
標簽編碼的參考代碼如下:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit(["超一線", "一線", "二線", "三線"])
print('特征:{}'.format(list(le.classes_)))
# 輸出 特征:['一線', '三線', '二線', '超一線']
print('轉換標簽值:{}'.format(le.transform(["超一線", "一線", "二線"])))
# 輸出 轉換標簽值:array([3 0 2]...)
print('特征標簽值反轉:{}'.format(list(le.inverse_transform([2, 2, 1]))))
# 輸出 特征標簽值反轉:['二線', '二線', '三線
(2) 獨熱向量編碼(one hot encoding )
獨熱編碼通常用於處理類別間不具有大小關系的特征。
例如:特征:血型,一共有四種類別(A,B,AB,O),采用獨熱編碼后,會把血型變成有一個4維的稀疏向量
- A表示為[1,0,0,0]
- B表示為[0,1,0,0]
- AB表示為[0,0,1,0]
- O表示為[0,0,0,1]
最終生成的稀疏向量的維度,和類別數相同。
- 優點:獨熱編碼解決了分類器不好處理屬性數據的問題,在一定程度上也起到了擴充特征的作用。它的值只有0和1,不同的類型存儲在垂直的空間。
- 缺點:只能對數值型變量二值化,無法直接對字符串型的類別變量編碼。當類別的數量很多時,特征空間會變得非常大。在這種情況下,一般可以用PCA來減少維度。而且 one hot encoding+PCA 這種組合在實際中也非常有用。
如果借助於pandas工具庫(查看ShowMeAI的 數據分析系列教程 和 數據科學工具速查 | Pandas使用指南 進行詳細了解),獨熱向量編碼的Python代碼參考示例如下:
sex_list = ['MALE', 'FEMALE', np.NaN, 'FEMALE', 'FEMALE', np.NaN, 'MALE']
df = pd.DataFrame({'SEX': sex_list})
display(df)
df.fillna('NA', inplace=True)
df = pd.get_dummies(df['SEX'],prefix='IS_SEX')
display(df)
最終變換前后的結果如下:
# 原始數據
SEX
0 MALE
1 FEMALE
2 NaN
3 FEMALE
4 FEMALE
5 NaN
6 MALE
# 獨熱向量編碼后
IS_SEX_FEMALE IS_SEX_MALE IS_SEX_NA
0 0 1 0
1 1 0 0
2 0 0 1
3 1 0 0
4 1 0 0
5 0 0 1
下面我們對'sex', 'class', 'pclass', 'embarked', 'who', 'family_size', 'age_bin'這些字段都進行獨熱向量編碼。
pd.get_dummies(df_titanic, columns=['sex', 'class', 'pclass', 'embarked', 'who', 'family_size', 'age_bin'],drop_first=True)
當然,我們也可以借助SKLearn(查看ShowMeAI教程 SKLearn最全應用指南 和 AI建模工具速查 | Scikit-learn使用指南 詳細學習),進行獨熱向量編碼實現:
import numpy as np
from sklearn.preprocessing import OneHotEncoder
# 非負整數表示的標簽列表
labels = [0,1,0,2]
# 行向量轉列向量
labels = np.array(labels).reshape(len(labels), -1)
# 獨熱向量編碼
enc = OneHotEncoder()
enc.fit(labels)
targets = enc.transform(labels).toarray()
# 如果不加 toarray() 的話,輸出的是稀疏的存儲格式,即索引加值的形式,也可以通過參數指定 sparse = False 來達到同樣的效果
輸出結果如下:
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 1., 0., 0.],
[ 0., 0., 1.]])
(3) 標簽二值化(LabelBinarizer)
功能與OneHotEncoder一樣,但是OneHotEncoder只能對數值型變量二值化,無法直接對字符串型的類別變量編碼,而LabelBinarizer可以直接對字符型變量二值化。
示例代碼如下:
from sklearn.preprocessing import LabelBinarizer
lb=LabelBinarizer()
labelList=['yes', 'no', 'no', 'yes','no2']
# 將標簽矩陣二值化
dummY=lb.fit_transform(labelList)
print("dummY:",dummY)
# 逆過程
yesORno=lb.inverse_transform(dummY)
print("yesOrno:",yesORno)
輸出如下:
dummY: [[0 0 1]
[1 0 0]
[1 0 0]
[0 0 1]
[0 1 0]]
yesOrno: ['yes' 'no' 'no' 'yes' 'no2']
4.7 降維
在實際的機器學習項目中,我們可能還會做降維處理,主要因為數據存在以下幾個問題:
- 數據的多重共線性:特征屬性之間存在着相互關聯關系。多重共線性會導致解的空間不穩定, 從而導致模型的泛化能力弱。
- 高緯空間樣本具有稀疏性,導致模型比較難找到數據特征。
- 過多的變量會妨礙模型查找規律。
- 僅僅考慮單個變量對於目標屬性的影響可能忽略變量之間的潛在關系。
通過特征降維希望達到的目的:
- 減少特征屬性的個數
- 確保特征屬性之間是相互獨立的
常用的降維方法有:
- PCA
- SVD
- LDA
- T-sne等非線性降維
這里降維的講解,我們給大家基於iris數據集講解:
from sklearn import datasets
iris_data = datasets.load_iris()
X = iris_data.data
y = iris_data.target
def draw_result(X, y):
plt.figure()
# 提取 Iris-setosa
setosa = X[y == 0]
# 繪制點:參數 1 x 向量,y 向量
plt.scatter(setosa[:, 0], setosa[:, 1], color="red", label="Iris-setosa")
versicolor = X[y == 1]
plt.scatter(versicolor[:, 0], versicolor[:, 1], color="orange", label="Iris-versicolor")
virginica = X[y == 2]
plt.scatter(virginica[:, 0], virginica[:, 1], color="blue", label="Iris-virginica")
plt.legend()
plt.show()
draw_result(X, y)
(1) PCA(Principal Component Analysis)
關於PCA主成分分析降維算法,大家可以查閱ShowMeAI文章 圖解機器學習 | 降維算法詳解 進行詳細學習。
PCA降維的參考代碼實現如下:
import numpy as np
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
newX = pca.fit_transform(X)
draw_result(newX, y)
(2) SVD(Singular Value Decomposition)
SVD方法的主要步驟如下:
所以 \(V\) 是 \(A^{T} A\) 特征值分解的特征向量按列組成的正交矩陣, \(\Sigma^{2}\) 是 \(A^{T} A\) 特征值組成的對角矩陣,也可以看出 $A_{m \times n} $ 的奇異值 \(\sigma_{i}\) 是 $A^{T} A $ 特征值$$\lambda_{i}$$的平方根。
假如 \(A^{T} A\) 的特征向量為 \(v_{i}\),\(U\) 中對應的 \(u_{i}\) 則可以由下式求出:
也即奇異值分解的關鍵在於對 \(A^{T} A\) 進行特征值分解。
對應的代碼參考實現如下:
from sklearn.decomposition import TruncatedSVD
iris_2d = TruncatedSVD(2).fit_transform(X)
draw_result(iris_2d, y)
PCA vs SVD
PCA求解關鍵在於求解協方差矩陣 \(C=\frac{1}{m} X X^{T}\) 的特征值分解。
SVD關鍵在於 \(A^{T} A\) 的特征值分解。
很明顯二者所解決的問題非常相似,都是對一個實對稱矩陣進行特征值分解,如果取:
則有:
此時SVD與PCA等價,所以PCA問題可以轉化為SVD問題求解。
(3) LDA(Linear Discriminant Analysis)
是有監督的降維,通過最小化類內離散度與最大化類間離散度來獲得最優特征子集。
上圖解讀:LD1通過線性判定,可以很好的將呈正態分布的兩個類分開。LD2的線性判定保持了數據集的較大方差,但LD2無法提供關於類別的信息,因此LD2不是一個好的線性判定。
對應的降維參考實現代碼如下:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA(n_components=2)
iris_2d = lda.fit_transform(X, y)
draw_result(iris_2d, y)
LDA vs PCA
PCA試圖尋找到方差最大的正交的主成分分量軸LDA發現可以最優化分類的特征子空間LDA和PCA都是可用於降低數據集維度的線性轉換技巧PCA是無監督算法LDA是監督算法LDA是一種更優越的用於分類的特征提取技術
(4) T-SNE
T-SNE(t-distributed stochastic neighbor embedding)是一種非線性降維方法,參考的代碼實現如下:
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2)
iris_2d = tsne.fit_transform(X)
draw_result(iris_2d, y)
5.特征選擇
特征選擇是在建模過程中經常會用到的一個處理,也有重要意義:
- 特征冗余,部分特征相關度太高,消耗計算資源
- 存在噪聲,對模型結果有負面影響
- 部分特征容易引起過擬合
總體來說,進行特征選擇有2個主要考慮方向:
- 特征發散程度:如果一個特征不發散,例如方差接近於0,也就是說樣本在這個特征上基本上沒有差異,這個特征對於樣本的區分並沒有什么用。
- 特征與目標的相關性:特征與目標相關性高,越應當被保留,這點大家也比較容易理解。
對特征選擇的方法進行歸類,又大體可以歸納為下述3種:
- Filter:過濾法,按照發散性或者相關性對各個特征進行評分,設定閾值或者待選擇閾值的個數來選擇特征。
- Wrapper:包裝法,根據目標函數(通常是預測效果評分),每次選擇若干特征或者排除若干特征。
- Embedded:嵌入法,先使用某些機器學習的算法和模型進行訓練,得到各個特征的權值系數,根據系數從大到小選擇特征。類似於Filter方法,但是是通過訓練來確定特征的優劣。我們使用SKLearn中的feature_selection庫來進行特征選擇。
5.1 過濾式Filter
(1) 方差過濾
這是通過特征本身的方差來篩選特征的類。
比如一個特征本身的方差很小,就表示樣本在這個特征上基本沒有差異,可能特征中的大多數值都一樣,甚至整個特征的取值都相同,那這個特征對於樣本區分沒有什么作用。
我們會剔除掉方差非常小的字段特征,參考代碼實現如下:
from sklearn.feature_selection import VarianceThreshold
variancethreshold = VarianceThreshold() #實例化,默認方差為 0.方差<=0 的過濾掉
df_titanic_numerical = df_titanic[['age','sibsp','parch','fare','family_size']]
X_var = variancethreshold.fit_transform(df_titanic_numerical) #獲取刪除不合格特征后的新特征矩陣
del_list = df_titanic_numerical.columns[variancethreshold.get_support()==0].to_list() #獲得刪除
(2) 卡方過濾
卡方檢驗,專用於分類算法,捕捉相關性,追求p小於顯著性水平的特征。卡方過濾是專門針對離散型標簽(即分類問題)的相關性過濾。
p值和取到這一個統計量的概率取值其實是正相關的:p值越大,取到這個統計量的概率就越大,即越合理;p值越小,取到這個統計量的概率就越小,即越不合理,此時應該拒絕原假設,接收備擇假設。
如下為卡方過濾的參考代碼示例:
df_titanic_categorical = df_titanic[['sex', 'class', 'embarked', 'who', 'age_bin','adult_male','alone','fare_bin']]
df_titanic_numerical = df_titanic[['age','sibsp','parch','fare','family_size','pclass']]
df_titanic_categorical_one_hot = pd.get_dummies(df_titanic_categorical, columns=['sex', 'class', 'embarked', 'who', 'age_bin','adult_male','alone','fare_bin'], drop_first=True)
df_titanic_combined = pd.concat([df_titanic_numerical,df_titanic_categorical_one_hot],axis=1)
y = df_titanic['survived']
X = df_titanic_combined.iloc[:,1:]
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
chi_value, p_value = chi2(X,y)
#根據 p 值,得出 k 值
k = chi_value.shape[0] - (p_value > 0.05).sum() #要保留的特征的數量 14
#根據卡方值,選擇前幾特征,篩選后特征
X_chi = SelectKBest(chi2, k=14).fit_transform(X, y)
(3) F檢驗
F檢驗捕捉線性相關性,要求數據服從正態分布,追求P值小於顯著性水平特征。
其特征選擇的參考代碼如下:
from sklearn.feature_selection import f_classif
f_value, p_value = f_classif(X,y)
#根據 p 值,得出 k 值
k = f_value.shape[0] - (p_value > 0.05).sum()
#篩選后特征
X_classif = SelectKBest(f_classif, k=14).fit_transform(X, y)
(4) 互信息法
互信息法是用來捕捉每個特征與標簽之間的任意關系(包括線性和非線性關系)的過濾方法。
其特征選擇的參考代碼如下:
from sklearn.feature_selection import mutual_info_classif as MIC
#互信息法
mic_result = MIC(X,y) #互信息量估計
k = mic_result.shape[0] - sum(mic_result <= 0) #16
X_mic = SelectKBest(MIC, k=16).fit_transform(X, y)
5.2 包裹式Wrapper
(1) 遞歸特征刪除法
遞歸消除刪除法使用一個基模型來進行多輪訓練,每輪訓練后,消除若干權值系數的特征,再基於新的特征集進行下一輪訓練。使用feature_selection庫的RFE類來選擇特征的代碼如下:
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
#遞歸特征消除法,返回特征選擇后的數據
#參數 estimator 為基模型
#參數 n_features_to_select 為選擇的特征個數
X_ref = RFE(estimator=LogisticRegression(), n_features_to_select=10).fit_transform(X, y)
(2) 特征重要性評估
我們基於一些模型(如各類樹模型)可以得到特征重要度,進而進行篩選
from sklearn.ensemble import ExtraTreesClassifier
# 建模與獲取特征重要度
model = ExtraTreesClassifier()
model.fit(X, y)
print(model.feature_importances_)
# 特征重要度排序
feature=list(zip(X.columns,model.feature_importances_))
feature=pd.DataFrame(feature,columns=['feature','importances'])
feature.sort_values(by='importances',ascending=False).head(20)
(3) 排列重要性評估
我們還有一類方法可以評估特征重要度,進而進行篩選,叫作排列重要度。
原理:在訓練機器學習模型之后計算置換重要性。這種方法在向模型提出假設,如果在保留目標和所有其他列的同時隨機打亂一列驗證集特征數據,對預測機器學習模型的准確性的影響程度。對於一個具有高度重要性的特征,random-reshuffle會對機器學習模型預測的准確性造成更大的損害。
優點:快速計算;易於使用和理解;特征重要性度量的屬性;追求特征穩定性。
參考代碼實現如下:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import eli5
from eli5.sklearn import PermutationImportance
my_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)
perm = PermutationImportance(my_model, random_state=1).fit(val_X, val_y)
eli5.show_weights(perm, feature_names = val_X.columns.tolist())
5.3 嵌入式Embedded
(1) 基於懲罰項的特征選擇法
使用帶懲罰項的基模型,除了篩選出特征外,同時也進行了降維。
使用feature_selection
庫的SelectFromModel
類結合帶L1懲罰項的邏輯回歸模型,來選擇特征的代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
#帶 L1 和 L2 懲罰項的邏輯回歸作為基模型的特征選擇,這個設置帶 L1 懲罰項的邏輯回歸作為基模型的特征選擇
lr = LogisticRegression(solver='liblinear',penalty="l1", C=0.1)
X_sfm = SelectFromModel(lr).fit_transform(X, y)
X_sfm.shape
(891, 7
使用feature_selection庫的SelectFromModel類結合SVM模型,來選擇特征的代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.svm import LinearSVC
lsvc = LinearSVC(C=0.01,penalty='l1',dual=False).fit(X, y)
model = SelectFromModel(lsvc,prefit=True)
X_sfm_svm = model.transform(X)
X_sfm_svm.shape
(891, 7
(2) 基於樹模型
樹模型中GBDT也可用來作為基模型進行特征選擇,使用feature_selection庫的SelectFromModel類結合GBDT模型,來選擇特征的代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT 作為基模型的特征選擇
gbdt = GradientBoostingClassifier()
X_sfm_gbdt = SelectFromModel(gbdt).fit_transform(X, y)
5.4 特征選擇總結
關於特征選擇,做一個經驗總結,如下:
- ① 類別型特征變量,那么可以從SelectKBest開始,用卡方或者基於樹的選擇器來選擇變量;
- ② 定量特征變量,可以直接用線性模型和基於相關性的選擇器來選擇變量;
- ③ 二分類問題,可以考慮使用SelectFromModel和SVC;
- ④ 特征選擇前,要充分了解數據,一般需要做探索性數據分析EDA。
6.特征工程實戰建議
最后,ShowMeAI結合實際工業應用經驗,總結一些特征工程要點,如下:
6.1 數據理解
構建特征的有效性,和業務及數據分布強相關,因此建議在此步驟之前做EDA探索性數據分析來充分理解數據(可以參考ShowMeAI文章 Python機器學習綜合項目-電商銷量預估 和 Python機器學習綜合項目-電商銷量預估<進階> 了解EDA的基本過程和方法)。
6.2 數據預處理
我們可能會做的一些數據預處理與特征處理如下:
-
連續特征離散化
- 本質是限制浮點數特征的精度,異常數據有很強的魯棒性,模型也會更穩定。
- 樹模型不需要做
-
數值截斷
- 把特征值的取值限制在一定范圍內(對異常剔除有幫助)
- 可以用pandas dataframe的.clip(low,upper)方法
6.3 數據清洗
結合業務場景和數據分布,進行合理的缺失值、異常值處理。
6.4 特征構建與變換
建議不要上來就做PCA或LDA降維,最好先構建特征並對特征做篩選。
-
線性組合(linear combination)
- 適用於決策樹以及基於決策樹的ensemble(如gradient boosting,random forest),因為常見的axis-aligned split function不擅長捕獲不同特征之間的相關性;
- 不適用於SVM、線性回歸、神經網絡等。
-
類別特征與數值特征的組合
- 用N1和N2表示數值特征,用C1和C2表示類別特征,利用pandas的groupby操作,可以創造出以下幾種有意義的新特征:(其中,C2還可以是離散化了的N1)
median(N1)_by(C1) 中位數
mean(N1)_by(C1) 算術平均數
mode(N1)_by(C1) 眾數
min(N1)_by(C1) 最小值
max(N1)_by(C1) 最大值
std(N1)_by(C1) 標准差
var(N1)_by(C1) 方差
freq(C2)_by(C1) 頻數
- 統計特征+線性組合
- 統計特征可以和線性組合等基礎特征工程方法結合(僅用於決策樹),可以得到更多有意義的特征,如:
N1 - median(N1)_by(C1)
N1 - mean(N1)_by(C1)
- 基於樹模型創造新特征
- 在決策樹系列算法中(例決策樹、gbdt、隨機森林,具體可以查看ShowMeAI教程 圖解機器學習算法:從入門到精通系列教程 詳細學習理解),每一個樣本都會被映射到決策樹的葉子上。
- 我們可以把樣本經過每一棵決策樹映射后的index(自然數)或one-hot-encoding-vector(啞編碼得到的稀疏矢量)作為一項新的特征,加入到模型中。
在Scikit-Learn和XGBoost里,可以基於apply()以及decision_path()等方法實現。
6.5 模型
我們在不同類型的模型里,也會考慮不同的特征工程方法
-
樹模型
- 對特征數值幅度不敏感,可以不進行無量綱化和統計變換處理;
- 數模型特征依賴於樣本距離來進行學習,可以不進行類別特征編碼(但字符型特征不能直接作為輸入,所以需要至少要進行標簽編碼)。
- LightGBM和XGBoost都能將缺失值作為數據的一部分進行學習,所以不需要處理缺失值。其他情況需要填充缺失。
-
依賴樣本距離的模型
- 如線性回歸、SVM、深度學習等屬於這一類。
- 對於數值型特征需要進行無量綱化處理。
- 對於一些長尾分布的數據特征,可以做統計變換,使得模型能更好優化。
- 對於線性模型,特征分箱可以提升模型表達能力。
參考資料
機器學習【算法】系列教程
- 圖解機器學習 | 機器學習基礎知識
- 圖解機器學習 | 模型評估方法與准則
- 圖解機器學習 | KNN算法及其應用
- 圖解機器學習 | 邏輯回歸算法詳解
- 圖解機器學習 | 朴素貝葉斯算法詳解
- 圖解機器學習 | 決策樹模型詳解
- 圖解機器學習 | 隨機森林分類模型詳解
- 圖解機器學習 | 回歸樹模型詳解
- 圖解機器學習 | GBDT模型詳解
- 圖解機器學習 | XGBoost模型最全解析
- 圖解機器學習 | LightGBM模型詳解
- 圖解機器學習 | 支持向量機模型詳解
- 圖解機器學習 | 聚類算法詳解
- 圖解機器學習 | PCA降維算法詳解
機器學習【實戰】系列教程
- 機器學習實戰 | Python機器學習算法應用實踐
- 機器學習實戰 | SKLearn入門與簡單應用案例
- 機器學習實戰 | SKLearn最全應用指南
- 機器學習實戰 | XGBoost建模應用詳解
- 機器學習實戰 | LightGBM建模應用詳解
- 機器學習實戰 | Python機器學習綜合項目-電商銷量預估
- 機器學習實戰 | Python機器學習綜合項目-電商銷量預估<進階方案>
- 機器學習實戰 | 機器學習特征工程最全解讀
- 機器學習實戰 | 自動化特征工程工具Featuretools應用
- 機器學習實戰 | AutoML自動化機器學習建模
ShowMeAI系列教程推薦
- 大廠技術實現方案系列
- 圖解Python編程:從入門到精通系列教程
- 圖解數據分析:從入門到精通系列教程
- 圖解AI數學基礎:從入門到精通系列教程
- 圖解大數據技術:從入門到精通系列教程
- 圖解機器學習算法:從入門到精通系列教程
- 機器學習實戰:手把手教你玩轉機器學習系列
- 深度學習教程:吳恩達專項課程 · 全套筆記解讀
- 自然語言處理教程:斯坦福CS224n課程 · 課程帶學與全套筆記解讀
- 深度學習與計算機視覺教程:斯坦福CS231n · 全套筆記解讀