0 關于本文
? 主要內容和結構框架由@jasonfreak--使用sklearn做單機特征工程提供,其中夾雜了很多補充的例子,能夠讓大家更直觀的感受到各個參數(shù)的意義,有一些地方我也進行自己理解層面上的糾錯,目前有些細節(jié)和博主再進行討論,修改部分我都會以刪除來表示,讀者可以自行斟酌,能和我一塊討論便是極好的!還是多謝原作者,我這里只是總結和補充
1 特征工程是什么?
有這么一句話在業(yè)界廣泛流傳:數(shù)據(jù)和特征決定了機器學習的上限,而模型和算法只是逼近這個上限而已。那特征工程到底是什么呢?顧名思義,其本質是一項工程活動,目的是最大限度地從原始數(shù)據(jù)中提取特征以供算法和模型使用。通過總結和歸納,人們認為特征工程包括以下方面:
特征選擇主要有兩個目的:
- 減少特征數(shù)量、降維,使模型泛化能力更強,減少過擬合;
- 增強對特征和特征值之間的理解。
? 特征處理是特征工程的核心部分,sklearn提供了較為完整的特征處理方法,包括數(shù)據(jù)預處理,特征選擇,降維等。首次接觸到sklearn,通常會被其豐富且方便的算法模型庫吸引,但是這里介紹的特征處理庫也十分強大!
本文中使用sklearn中的IRIS(鳶尾花)數(shù)據(jù)集來對特征處理功能進行說明。IRIS數(shù)據(jù)集由Fisher在1936年整理,包含4個特征(Sepal.Length(花萼長度)、Sepal.Width(花萼寬度)、Petal.Length(花瓣長度)、Petal.Width(花瓣寬度)),特征值都為正浮點數(shù),單位為厘米。目標值為鳶尾花的分類(Iris Setosa(山鳶尾)、Iris Versicolour(雜色鳶尾),Iris Virginica(維吉尼亞鳶尾))。導入IRIS數(shù)據(jù)集的代碼如下:
from sklearn.datasets import load_iris
#導入IRIS數(shù)據(jù)集
iris = load_iris()
#特征矩陣
iris.data
print iris.data.shape # (150, 4)
#目標向量
iris.target
print iris.target.shape # (150, )
3/2 數(shù)據(jù)探索性分析(Exploratory Data Analysis,EDA)
? 探索性分析能夠更好的了解數(shù)據(jù)集,檢查數(shù)據(jù)集的特征和形狀,驗證腦海中的一些想法,對數(shù)據(jù)任務接下來的步驟有一個初步的想法。
? 這里我用了本地的iris數(shù)據(jù)集,方法是一樣的沒有區(qū)別,注意的就是需要看下一下數(shù)據(jù)的形式和加載過程中的header需不需要加載
2 數(shù)據(jù)預處理
通過特征提取,我們能得到未經(jīng)處理的特征,這時的特征可能有以下問題:
- 不屬于同一量綱:即特征的規(guī)格不一樣,不能夠放在一起比較。無量綱化可以解決這一問題。
- 信息冗余:對于某些定量特征,其包含的有效信息為區(qū)間劃分,例如學習成績,假若只關心“及格”或不“及格”,那么需要將定量的考分,轉換成“1”和“0”表示及格和未及格。二值化可以解決這一問題。
- 定性特征不能直接使用:某些機器學習算法和模型只能接受定量特征的輸入,那么需要將定性特征轉換為定量特征。最簡單的方式是為每一種定性值指定一個定量值,但是這種方式過于靈活,增加了調參的工作。通常使用啞編碼的方式將定性特征轉換為定量特征:假設有N種定性值,則將這一個特征擴展為N種特征,當原始特征值為第i種定性值時,第i個擴展特征賦值為1,其他擴展特征賦值為0。啞編碼的方式相比直接指定的方式,不用增加調參的工作,對于線性模型來說,使用啞編碼后的特征可達到非線性的效果。
- 存在缺失值:缺失值需要補充。
- 信息利用率低:不同的機器學習算法和模型對數(shù)據(jù)中信息的利用是不同的,之前提到在線性模型中,使用對定性特征啞編碼可以達到非線性的效果。類似地,對定量變量多項式化,或者進行其他的轉換,都能達到非線性的效果。
我們使用sklearn中的preproccessing庫來進行數(shù)據(jù)預處理,可以覆蓋以上問題的解決方案。
2.1 無量綱化 數(shù)據(jù)規(guī)范化
無量綱化使不同規(guī)格的數(shù)據(jù)轉換到同一規(guī)格。常見的無量綱化方法有標準化和區(qū)間縮放法(我認為這句話是錯誤的)。標準化的前提是特征值服從正態(tài)分布,標準化后,其轉換成標準正態(tài)分布。區(qū)間縮放法利用了邊界值信息,將特征的取值區(qū)間縮放到某個特點的范圍,例如[0, 1]等。
2.1.1 標準化 0均值標準化(Z-score standardization)
標準化需要計算特征的均值和標準差,公式表達為:
使用preproccessing庫的StandardScaler類對數(shù)據(jù)進行標準化的代碼如下:
from sklearn.preprocessing import StandardScaler
#標準化,返回值為標準化后的數(shù)據(jù)
StandardScaler().fit_transform(iris.data)
#array([[ -9.00681170e-01, 1.03205722e+00, -1.34127240e+00,
# -1.31297673e+00],
# [ -1.14301691e+00, -1.24957601e-01, -1.34127240e+00,
# -1.31297673e+00],
# ...
# --- 例子就是如下,他是對列也就是同一特征下進行縮放,而不是對一個數(shù)據(jù)的不同特征之間(行)進行縮放 ---
from sklearn.preprocessing import StandardScaler
data = np.array([[1,2],[3,4],[5,6]]).reshape(3,2)
print data
# [[1 2]
# [3 4]
# [5 6]]
print np.mean(data,axis=0) # 計算每一列均值 [ 3. 4.]
print np.std(data,axis=0) # 計算每一列標準差 [ 1.63299316 1.63299316]
print (data[0][0]-np.mean(data,axis=0)[0])/np.std(data,axis=0)[0] # 計算第一個元素的標準化后的值 -1.22474487139
#區(qū)間縮放,返回值為縮放到[0, 1]區(qū)間的數(shù)據(jù)
StandardScaler().fit_transform(data)
# array([[-1.22474487, -1.22474487],
# [ 0. , 0. ],
# [ 1.22474487, 1.22474487]])
2.1.2 區(qū)間縮放法(線性函數(shù)歸一化(Min-Max scaling))
區(qū)間縮放法的思路有多種,常見的一種為利用兩個最值進行縮放,公式表達為:
使用preproccessing庫的MinMaxScaler類對數(shù)據(jù)進行區(qū)間縮放的代碼如下:
from sklearn.preprocessing import MinMaxScaler
#區(qū)間縮放,返回值為縮放到[0, 1]區(qū)間的數(shù)據(jù)
MinMaxScaler().fit_transform(iris.data)
# array([[ 0.22222222, 0.625 , 0.06779661, 0.04166667],
# [ 0.16666667, 0.41666667, 0.06779661, 0.04166667],
# [ 0.11111111, 0.5 , 0.05084746, 0.04166667],
# ...
# --- 例子就是如下,他是對列也就是同一特征下進行縮放,而不是對一個數(shù)據(jù)的不同特征之間(行)進行縮放 ---
from sklearn.preprocessing import MinMaxScaler
data = np.array([[1,2],[3,4],[5,6]]).reshape(3,2)
print data
# [[1 2]
# [3 4]
# [5 6]]
# 區(qū)間縮放,返回值為縮放到[0, 1]區(qū)間的數(shù)據(jù)
MinMaxScaler().fit_transform(data)
#array([[ 0. , 0. ],
# [ 0.5, 0.5],
# [ 1. , 1. ]])
2.1.3 標準化與歸一化的區(qū)別 標準化(規(guī)范化)與歸一化的區(qū)別
(這是原作者的描述,我覺得有點問題) 簡單來說,標準化是依照特征矩陣的列處理數(shù)據(jù),其通過求z-score的方法,將樣本的特征值轉換到同一量綱下。 歸一化是依照特征矩陣的行處理數(shù)據(jù),其目的在于樣本向量在點乘運算或其他核函數(shù)計算相似性時,擁有統(tǒng)一的標準,也就是說都轉化為“單位向量”。規(guī)則為L2的歸一化公式如下:
使用preproccessing庫的Normalizer類對數(shù)據(jù)進行歸一化的代碼如下:
from sklearn.preprocessing import Normalizer
#歸一化,返回值為歸一化后的數(shù)據(jù)
Normalizer().fit_transform(iris.data)
# --- 例子就是如下,他是對一行數(shù)據(jù)的不同特征進行處理 ---
from sklearn.preprocessing import Normalizer
import math
data = np.array([[1,2],[3,4],[5,6]]).reshape(3,2)
print data
# [[1 2]
# [3 4]
# [5 6]]
print data[0][0]/math.sqrt((data[0][0])**2 + (data[0][1])**2) # 計算第一個元素L2正則化后的值 0.4472135955
# 規(guī)范化
Normalizer().fit_transform(data)
# array([[-1.22474487, -1.22474487],
# [ 0. , 0. ],
# [ 1.22474487, 1.22474487]])
<font color='red'>這里比較搞混的一點是StandardScaler和Normalizer這個兩個概念的問題(大家翻譯上的誤差導致信息非常混亂),其實StandardScaler就是尺寸縮放問題,即使同一特征下的數(shù)值在一定范圍內浮動,如將數(shù)值所放在0-1范圍內(MinMaxScaler), 或者將數(shù)據(jù)標準化成為均值為0,方差為1的數(shù)據(jù)(Z-score);而另一個就是Normalizer,將同一行數(shù)據(jù)的不同特征進行規(guī)范化,這樣一個數(shù)據(jù)的不同特征具有相同的量綱或者表現(xiàn)力,比如說一個特征是身高,1.7m,體重為150斤,那么兩個特征之間差距太大,身高這個特征變化根本無法起到?jīng)Q定作用(在體重這個變化特征下),畢竟大家怎么長都是一米多,但是體重差距一下子拉開20多是很正常的事</font>
2.2 對定量特征二值化
定量特征二值化的核心在于設定一個閾值,大于閾值的賦值為1,小于等于閾值的賦值為0,公式表達如下:
使用preproccessing庫的Binarizer類對數(shù)據(jù)進行二值化的代碼如下:
from sklearn.preprocessing import Binarizer
#二值化,閾值設置為3,返回值為二值化后的數(shù)據(jù)
Binarizer(threshold=3).fit_transform(iris.data)
2.3 對定性特征啞編碼
由于IRIS數(shù)據(jù)集的特征皆為定量特征,故使用其目標值進行啞編碼(實際上是不需要的)。使用preproccessing庫的OneHotEncoder類對數(shù)據(jù)進行啞編碼的代碼如下:
from sklearn.preprocessing import OneHotEncoder
#啞編碼,對IRIS數(shù)據(jù)集的目標值,返回值為啞編碼后的數(shù)據(jù)
OneHotEncoder().fit_transform(iris.target.reshape((-1,1)))
補充:但是從以一個角度來看,如果標簽需要被量化,這個就很有用了, 如下圖所示,將target這個標簽轉化成為數(shù)值才能進行機器學習
2.4 缺失值計算
由于IRIS數(shù)據(jù)集沒有缺失值,故對數(shù)據(jù)集新增一個樣本,4個特征均賦值為NaN,表示數(shù)據(jù)缺失。使用preproccessing庫的Imputer類對數(shù)據(jù)進行缺失值計算的代碼如下:
(原作者例子舉的不好,我這里替換補充一下)
import numpy as np
from sklearn.preprocessing import Imputer
imp = Imputer(missing_values='NaN', strategy='mean', axis=0) # 使用特征的均值進行填充,其余還有使用眾數(shù)填充等,只需要把mean改成median即可
data = np.array([np.nan, 2, 6, np.nan, 7, 6]).reshape(3,2)
print data
# [[ nan 2.]
# [ 6. nan]
# [ 7. 6.]]
print imp.fit_transform(data)
# [[ 6.5 2. ]
# [ 6. 4. ]
# [ 7. 6. ]]
2.5 數(shù)據(jù)變換
常見的數(shù)據(jù)變換有基于多項式的、基于指數(shù)函數(shù)的、基于對數(shù)函數(shù)的。4個特征,度為2的多項式轉換公式如下:
使用preproccessing庫的PolynomialFeatures類對數(shù)據(jù)進行多項式轉換的代碼如下:
from sklearn.preprocessing import PolynomialFeatures
#多項式轉換
#參數(shù)degree為度,默認值為2
PolynomialFeatures().fit_transform(iris.data)
基于單變元函數(shù)的數(shù)據(jù)變換可以使用一個統(tǒng)一的方式完成,使用preproccessing庫的FunctionTransformer對數(shù)據(jù)進行對數(shù)函數(shù)轉換的代碼如下:
from numpy import log1p
from sklearn.preprocessing import FunctionTransformer
#自定義轉換函數(shù)為對數(shù)函數(shù)的數(shù)據(jù)變換
#第一個參數(shù)是單變元函數(shù)
FunctionTransformer(log1p).fit_transform(iris.data)
2.6 回顧
類 | 功能 | 說明 |
---|---|---|
StandardScaler |
|
標準化,基于特征矩陣的列,將特征值轉換至服從標準正態(tài)分布 |
MinMaxScaler |
|
區(qū)間縮放,基于最大最小值,將特征值轉換到[0, 1]區(qū)間上 |
Normalizer | 歸一化(規(guī)范化) | 基于特征矩陣的行,將樣本向量轉換為“單位向量” |
Binarizer | 二值化 | 基于給定閾值,將定量特征按閾值劃分 |
OneHotEncoder | 啞編碼 | 將定性數(shù)據(jù)編碼為定量數(shù)據(jù) |
Imputer | 缺失值計算 | 計算缺失值,缺失值可填充為均值等 |
PolynomialFeatures | 多項式數(shù)據(jù)轉換 | 多項式數(shù)據(jù)轉換 |
FunctionTransformer | 自定義單元數(shù)據(jù)轉換 | 使用單變元的函數(shù)來轉換數(shù)據(jù) |
3 特征選擇
這一段的主要是由特征選擇--scikit-learn和原作者一塊組合的,我負責補充
? 當數(shù)據(jù)預處理完成后,我們需要選擇有意義的特征輸入機器學習的算法和模型進行訓練。通常來說,從兩個方面考慮來選擇特征:
- 特征是否發(fā)散:如果一個特征不發(fā)散,例如方差接近于0,也就是說樣本在這個特征上基本上沒有差異,這個特征對于樣本的區(qū)分并沒有什么用。
- 特征與目標的相關性:這點比較顯見,與目標相關性高的特征,應當優(yōu)選選擇。除方差法外,本文介紹的其他方法均從相關性考慮。
根據(jù)特征選擇的形式又可以將特征選擇方法分為3種:
- Filter:過濾法,按照發(fā)散性或者相關性對各個特征進行評分,設定閾值或者待選擇閾值的個數(shù),選擇特征。
- Wrapper:包裝法,根據(jù)目標函數(shù)(通常是預測效果評分),每次選擇若干特征,或者排除若干特征。
- Embedded:嵌入法,先使用某些機器學習的算法和模型進行訓練,得到各個特征的權值系數(shù),根據(jù)系數(shù)從大到小選擇特征。類似于Filter方法,但是是通過訓練來確定特征的優(yōu)劣。
我們使用sklearn中的feature_selection庫來進行特征選擇。
3.1 Filter
? 過濾法主要思想是:按照發(fā)散性或者相關性對各個特征進行對每一維的特征“打分”,即給每一維的特征賦予權重,這樣的權重就代表著該維特征的重要性,設定閾值或者待選擇閾值的個數(shù),選擇特征。
3.1.1 移除低方差的特征 (Removing features with low variance)
這應該是最簡單的特征選擇方法了:假設某特征的特征值只有0和1,并且在所有輸入樣本中,95%的實例的該特征取值都是1,那就可以認為這個特征作用不大。如果100%都是1,那這個特征就沒意義了。當特征值都是離散型變量的時候這種方法才能用,如果是連續(xù)型變量,就需要將連續(xù)變量離散化之后才能用,而且實際當中,一般不太會有95%以上都取某個值的特征存在,所以這種方法雖然簡單但是不太好用。可以把它作為特征選擇的預處理,先去掉那些取值變化小的特征,然后再從接下來提到的的特征選擇方法中選擇合適的進行進一步的特征選擇。
使用方差選擇法,先要計算各個特征的方差,然后根據(jù)閾值,選擇方差大于閾值的特征。使用feature_selection庫的VarianceThreshold類來選擇特征的代碼如下:
from sklearn.feature_selection import VarianceThreshold
#方差選擇法,返回值為特征選擇后的數(shù)據(jù)
#參數(shù)threshold為方差的閾值
VarianceThreshold(threshold=3).fit_transform(iris.data)
# --- 例子 ,比如第一列的特征,只有一個為1的,那這個特征幾乎是無用的,可以砍掉 ---
from sklearn.feature_selection import VarianceThreshold
X = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]])
print X
# [[0 0 1]
# [0 1 0]
# [1 0 0]
# [0 1 1]
# [0 1 0]
# [0 1 1]]
sel = VarianceThreshold(threshold=(0.8 * (1 - 0.8)))
sel.fit_transform(X)
# array([[0, 1],
# [1, 0],
# [0, 0],
# [1, 1],
# [1, 0],
# [1, 1]])
3.1.2 單變量特征選擇 (Univariate feature selection)
? 單變量特征選擇能夠對每一個特征進行測試,衡量該特征和響應變量之間的關系,根據(jù)得分扔掉不好的特征。對于回歸和分類問題可以采用卡方檢驗等方式對特征進行測試。
方法簡單,易于運行,易于理解,通常對于理解數(shù)據(jù)有較好的效果(但對特征優(yōu)化、提高泛化能力來說不一定有效);這種方法有許多改進的版本、變種。
3.1.2.1 卡方(Chi2)檢驗
? 適用于:分類問題(y離散)
? 經(jīng)典的卡方檢驗(原理及應用)是檢驗定性自變量對定性因變量的相關性。假設自變量有N種取值,因變量有M種取值,考慮自變量等于i且因變量等于j的樣本頻數(shù)的觀察值與期望的差距,構建統(tǒng)計量,其中A為實際數(shù),E為理論值:
這個統(tǒng)計量的含義簡而言之就是自變量對因變量的相關性。用feature_selection庫的SelectKBest類結合卡方檢驗來選擇特征的代碼如下:
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
#選擇K個最好的特征,返回選擇特征后的數(shù)據(jù)
SelectKBest(chi2, k=2).fit_transform(iris.data, iris.target)
# --- 例子 ---
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
iris = load_iris()
X, y = iris.data, iris.target
X.shape
#(150, 4)
X_new = SelectKBest(chi2, k=2).fit_transform(X, y)
X_new.shape
#(150, 2) 直接砍掉了兩個特征
3.1.2.2 Pearson相關系數(shù) (Pearson Correlation)
? 適用于:回歸問題(y連續(xù))
? 爾森相關系數(shù)是一種最簡單的,能幫助理解特征和響應變量之間關系的方法,該方法衡量的是變量之間的線性相關性,結果的取值區(qū)間為[-1,1],-1表示完全的負相關,+1表示完全的正相關,0表示沒有線性相關。
Pearson Correlation速度快、易于計算,經(jīng)常在拿到數(shù)據(jù)(經(jīng)過清洗和特征提取之后的)之后第一時間就執(zhí)行。Scipy的 pearsonr 方法能夠同時計算 相關系數(shù) 和p-value.
import numpy as np
from scipy.stats import pearsonr
np.random.seed(0)
size = 300
x = np.random.normal(0, 1, size)
# pearsonr(x, y)的輸入為特征矩陣和目標向量
# np.random.normal(0, 1, 100) 創(chuàng)建100個均值為0,方差為1的高斯隨機數(shù)
print("Lower noise", pearsonr(x, x + np.random.normal(0, 1, size)))
print("Higher noise", pearsonr(x, x + np.random.normal(0, 10, size)))
# 輸出為二元組(sorce, p-value)的數(shù)組
Lower noise (0.71824836862138386, 7.3240173129992273e-49)
Higher noise (0.057964292079338148, 0.31700993885324746)
這個例子中,我們比較了變量在加入噪音之前和之后的差異。當噪音比較小的時候,相關性很強,p-value很低。至于什么p-vlaue請看喵喵的回答,個人覺得很清晰
Scikit-learn提供的 f_regrssion 方法能夠批量計算特征的f_score和p-value,非常方便,參考sklearn的 pipeline
Pearson相關系數(shù)的一個明顯缺陷是,作為特征排序機制,他只對線性關系敏感。如果關系是非線性的,即便兩個變量具有一一對應的關系,Pearson相關性也可能會接近0。例如:
x = np.random.uniform(-1, 1, 100000)
print pearsonr(x, x**2)[0]
-0.00230804707612
更多類似的例子參考 sample plots 。另外,如果僅僅根據(jù)相關系數(shù)這個值來判斷的話,有時候會具有很強的誤導性,如 Anscombe’s quartet ,最好把數(shù)據(jù)可視化出來,以免得出錯誤的結論。
3.1.3 互信息和最大信息系數(shù) (Mutual information and maximal information coefficient (MIC)
經(jīng)典的互信息(互信息為隨機變量X與Y之間的互信息I(X;Y)I(X;Y)為單個事件之間互信息的數(shù)學期望)也是評價定性自變量對定性因變量的相關性的,互信息計算公式如下:
互信息直接用于特征選擇其實不是太方便:1、它不屬于度量方式,也沒有辦法歸一化,在不同數(shù)據(jù)及上的結果無法做比較;2、對于連續(xù)變量的計算不是很方便(X和Y都是集合,x,y都是離散的取值),通常變量需要先離散化,而互信息的結果對離散化的方式很敏感。
最大信息系數(shù)克服了這兩個問題。它首先尋找一種最優(yōu)的離散化方式,然后把互信息取值轉換成一種度量方式,取值區(qū)間在[0,1]。 minepy 提供了MIC功能。
反過頭來看 y=x^2 這個例子,MIC算出來的互信息值為1(最大的取值)。
為了處理定量數(shù)據(jù),最大信息系數(shù)法被提出,使用feature_selection庫的SelectKBest類結合最大信息系數(shù)法來選擇特征的代碼如下:
from sklearn.feature_selection import SelectKBest
from minepy import MINE
#由于MINE的設計不是函數(shù)式的,定義mic方法將其為函數(shù)式的,返回一個二元組,二元組的第2項設置成固定的P值0.5
def mic(x, y):
m = MINE()
m.compute_score(x, y)
return (m.mic(), 0.5)
#選擇K個最好的特征,返回特征選擇后的數(shù)據(jù)
SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)
# --- 例子 ---
from minepy import MINE
m = MINE()
x = np.random.uniform(-1, 1, 10000)
m.compute_score(x, x**2)
print(m.mic())
# 1.0
3.1.4 距離相關系數(shù) (Distance Correlation)
距離相關系數(shù)是為了克服Pearson相關系數(shù)的弱點而生的。在x和x^2這個例子中,即便Pearson相關系數(shù)是0,我們也不能斷定這兩個變量是獨立的(有可能是非線性相關);但如果距離相關系數(shù)是0,那么我們就可以說這兩個變量是獨立的。
R的 energy 包里提供了距離相關系數(shù)的實現(xiàn),另外這是 Python gist 的實現(xiàn)。
> x = runif (1000, -1, 1)
> dcor(x, x**2)
[1] 0.4943864
盡管有 MIC 和 距離相關系數(shù) 在了,但當變量之間的關系接近線性相關的時候,Pearson相關系數(shù)仍然是不可替代的。
第一,Pearson相關系數(shù)計算速度快,這在處理大規(guī)模數(shù)據(jù)的時候很重要。
第二,Pearson相關系數(shù)的取值區(qū)間是[-1,1],而MIC和距離相關系數(shù)都是[0,1]。這個特點使得Pearson相關系數(shù)能夠表征更豐富的關系,符號表示關系的正負,絕對值能夠表示強度。當然,Pearson相關性有效的前提是兩個變量的變化關系是單調的。
3.2 Wrapper
? 包裝法主要思想是:根據(jù)目標函數(shù)(通常是預測效果評分),每次選擇若干特征,或者排除若干特征。也可以將特征子集的選擇看作是一個搜索尋優(yōu)問題,生成不同的組合,對組合進行評價,再與其他的組合進行比較。這樣就將子集的選擇看作是一個是一個優(yōu)化問題,這里有很多的優(yōu)化算法可以解決,尤其是一些啟發(fā)式的優(yōu)化算法,如GA,PSO,DE,ABC等,詳見“優(yōu)化算法—人工蜂群算法(ABC)”,“優(yōu)化算法—粒子群算法(PSO)”。
3.2.1 遞歸特征消除法
遞歸消除特征法使用一個基模型來進行多輪訓練,每輪訓練后,消除若干權值系數(shù)的特征,再基于新的特征集進行下一輪訓練。使用feature_selection庫的RFE類來選擇特征的代碼如下:
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
#遞歸特征消除法,返回特征選擇后的數(shù)據(jù)
#參數(shù)estimator為基模型
#參數(shù)n_features_to_select為選擇的特征個數(shù)
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)
# --- 例子,使用不同的基算法對特征的評估效果不一,注意選擇---
from sklearn.svm import SVC
from sklearn.datasets import load_digits
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
iris = load_iris()
x, y = iris.data, iris.target
svc = SVC(kernel="linear", C=1)
rfe = RFE(estimator=svc, n_features_to_select=2, step=1)
rfe.fit(x, y)
ranking = rfe.ranking_
print ranking
# [3 2 1 1] 說明第三維度特征和第四維度特征排名前2
# 采用邏輯回歸
ref2 = RFE(estimator=LogisticRegression(), n_features_to_select=2).fit(iris.data, iris.target)
print ref2.ranking_
# [3 1 2 1] 這里則選擇認為第二維和第四維特征重要
3.3 Embedded
? 嵌入法主要思想是:使用某些機器學習的算法和模型進行訓練,得到各個特征的權值系數(shù),根據(jù)系數(shù)從大到小選擇特征。類似于Filter方法,但是是通過訓練來確定特征的優(yōu)劣。其實是講在確定模型的過程中,挑選出那些對模型的訓練有重要意義的屬性。
3.3.1 基于懲罰項的特征選擇法
使用帶懲罰項的基模型,除了篩選出特征外,同時也進行了降維。使用feature_selection庫的SelectFromModel類結合帶L1懲罰項的邏輯回歸模型,來選擇特征的代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
#帶L1懲罰項的邏輯回歸作為基模型的特征選擇
SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(iris.data, iris.target)
# --- 例子,這里用支持向量機了 ---
from sklearn.svm import LinearSVC
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectFromModel
iris = load_iris()
X, y = iris.data, iris.target
X.shape
# (150, 4)
lsvc = LinearSVC(C=0.01, penalty="l1", dual=False).fit(X,y) # 這里的懲罰項是L1,別看做是11,這里L是小寫
model = SelectFromModel(lsvc, prefit=True)
X_new = model.transform(X)
X_new[:3,:] # 選取的前三行看一下
#array([[ 5.1, 3.5, 1.4],
# [ 4.9, 3. , 1.4],
# [ 4.7, 3.2, 1.3]])
L1懲罰項降維的原理在于保留多個對目標值具有同等相關性的特征中的一個,所以沒選到的特征不代表不重要。故,可結合L2懲罰項來優(yōu)化。具體操作為:若一個特征在L1中的權值為1,選擇在L2中權值差別不大且在L1中權值為0的特征構成同類集合,將這一集合中的特征平分L1中的權值,故需要構建一個新的邏輯回歸模型:
from sklearn.linear_model import LogisticRegression
class LR(LogisticRegression):
def __init__(self, threshold=0.01, dual=False, tol=1e-4, C=1.0,
fit_intercept=True, intercept_scaling=1, class_weight=None,
random_state=None, solver='liblinear', max_iter=100,
multi_class='ovr', verbose=0, warm_start=False, n_jobs=1):
#權值相近的閾值
self.threshold = threshold
LogisticRegression.__init__(self, penalty='l1', dual=dual, tol=tol, C=C,
fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight=class_weight,
random_state=random_state, solver=solver, max_iter=max_iter,
multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs)
#使用同樣的參數(shù)創(chuàng)建L2邏輯回歸
self.l2 = LogisticRegression(penalty='l2', dual=dual, tol=tol, C=C, fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight = class_weight, random_state=random_state, solver=solver, max_iter=max_iter, multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs)
def fit(self, X, y, sample_weight=None):
#訓練L1邏輯回歸
super(LR, self).fit(X, y, sample_weight=sample_weight)
self.coef_old_ = self.coef_.copy()
#訓練L2邏輯回歸
self.l2.fit(X, y, sample_weight=sample_weight)
cntOfRow, cntOfCol = self.coef_.shape
#權值系數(shù)矩陣的行數(shù)對應目標值的種類數(shù)目
for i in range(cntOfRow):
for j in range(cntOfCol):
coef = self.coef_[i][j]
#L1邏輯回歸的權值系數(shù)不為0
if coef != 0:
idx = [j]
#對應在L2邏輯回歸中的權值系數(shù)
coef1 = self.l2.coef_[i][j]
for k in range(cntOfCol):
coef2 = self.l2.coef_[i][k]
#在L2邏輯回歸中,權值系數(shù)之差小于設定的閾值,且在L1中對應的權值為0
if abs(coef1-coef2) < self.threshold and j != k and self.coef_[i][k] == 0:
idx.append(k)
#計算這一類特征的權值系數(shù)均值
mean = coef / len(idx)
self.coef_[i][idx] = mean
return self
使用feature_selection庫的SelectFromModel類結合帶L1以及L2懲罰項,設定閾值來進行特征的篩選的邏輯回歸模型,來選擇特征的代碼如下:
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_boston
from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LassoCV
# Load the boston dataset.
boston = load_boston()
X, y = boston.data, boston.target
# We use the base estimator LassoCV since the L1 norm promotes sparsity of features.
# 使用LassoCV來規(guī)范化使之稀疏化
clf = LassoCV()
# Set a minimum threshold of 0.25
sfm = SelectFromModel(clf, threshold=0.25)
n_features = sfm.fit_transform(X,y).shape[1] # 初始滿足閾值后留下的特征
# Reset the threshold till the number of features equals two.
# Note that the attribute can be set directly instead of repeatedly
# fitting the metatransformer.
# 開始不斷的進行閾值攀升,直到有特征不滿足被砍掉
while n_features > 2:
sfm.threshold += 0.1
X_transform = sfm.transform(X)
n_features = X_transform.shape[1]
# Plot the selected two features from X.
plt.title(
"Features selected from Boston using SelectFromModel with "
"threshold %0.3f." % sfm.threshold)
feature1 = X_transform[:, 0]
feature2 = X_transform[:, 1]
plt.plot(feature1, feature2, 'r.')
plt.xlabel("Feature number 1")
plt.ylabel("Feature number 2")
plt.ylim([np.min(feature2), np.max(feature2)]) # ylim 設置y軸的范圍
plt.show()
3.3.2 基于樹模型的特征選擇法
樹模型中GBDT也可用來作為基模型進行特征選擇,使用feature_selection庫的SelectFromModel類結合GBDT模型,來選擇特征的代碼如下:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT作為基模型的特征選擇
SelectFromModel(GradientBoostingClassifier()).fit_transform(iris.data, iris.target)
# --- 例子:這里用了隨機森林了 ---
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectFromModel
iris = load_iris()
X, y = iris.data, iris.target
X.shape
# (150, 4)
clf = RandomForestClassifier()
clf = clf.fit(X, y)
print clf.feature_importances_ # 顯示每一個特征的重要性指標,越大說明越重要,可以看出,第三第四兩個特征比較重要
# [ 0.04505659 0.01056346 0.45428591 0.49009404]
model = SelectFromModel(clf, prefit=True)
X_new = model.transform(X)
X_new.shape
# (150, 2)
# 使用feature_importances_對boston數(shù)據(jù)的特征進行排序
from sklearn.ensemble import RandomForestRegressor
x, y = boston.data, boston.target
feature_name = np.array(["%d %s"%(b,a) for a,b in zip(boston.feature_names,range(len(boston.feature_names)))])
rf = RandomForestRegressor(n_estimators=100, random_state=101).fit(x,y)
importance = np.mean([tree.feature_importances_ for tree in rf.estimators_], axis=0)
std = np.std([ tree.feature_importances_ for tree in rf.estimators_],axis=0)
indices = np.argsort(importance)
range_ = range(len(importance))
plt.figure()
plt.title("random forset importance")
plt.barh(range_, importance[indices],color='r',xerr=std[indices],alpha=0.4,align='center')
plt.yticks(range(len(importance)),feature_name[indices])
plt.ylim([-1,len(importance)])
plt.xlim([0.0,0.65])
plt.show()
3.4 回顧
類 | 所屬方式 | 說明 |
---|---|---|
VarianceThreshold | Filter | 方差選擇法 |
SelectKBest | Filter | 可選關聯(lián)系數(shù)、卡方校驗、最大信息系數(shù)作為得分計算的方法 |
RFE | Wrapper | 遞歸地訓練基模型,將權值系數(shù)較小的特征從特征集合中消除 |
SelectFromModel | Embedded | 訓練基模型,選擇權值系數(shù)較高的特征 |
4 降維
當特征選擇完成后,可以直接訓練模型了,但是可能由于特征矩陣過大,導致計算量大,訓練時間長的問題,因此降低特征矩陣維度也是必不可少的。常見的降維方法除了以上提到的基于L1懲罰項的模型以外,另外還有主成分分析法(PCA)和線性判別分析(LDA),線性判別分析本身也是一個分類模型。PCA和LDA有很多的相似點,其本質是要將原始的樣本映射到維度更低的樣本空間中,但是PCA和LDA的映射目標不一樣:PCA是為了讓映射后的樣本具有最大的發(fā)散性;而LDA是為了讓映射后的樣本有最好的分類性能。所以說PCA是一種無監(jiān)督的降維方法,而LDA是一種有監(jiān)督的降維方法。
4.1 主成分分析法(PCA)
其他的一些參數(shù)的設置可以參考:scikit-learn中PCA的使用方法
使用decomposition庫的PCA類選擇特征的代碼如下:
from sklearn.decomposition import PCA
#主成分分析法,返回降維后的數(shù)據(jù)
#參數(shù)n_components為主成分數(shù)目
PCA(n_components=2).fit_transform(iris.data)
# --- 例子 ---
from sklearn.decomposition import PCA,KernelPCA
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
iris_data = load_iris()
category = pd.Categorical(iris_data.target) # 將標簽進行量化,就是說本來都是字符串啊,但是最后計算的時候都需要量化成1,2,3類等
pca_2c = PCA(n_components=2) # 使用PCA降到2維
#pca_2c = KernelPCA(n_components=2)
x_pca_2c = pca_2c.fit_transform(iris_data.data)
x_pca_2c.shape
plt.scatter(x_pca_2c[:,0],x_pca_2c[:,1],c=category.codes)
plt.show()
4.2 線性判別分析法(LDA)
使用lda庫的LDA類選擇特征的代碼如下:
from sklearn.lda import LDA
#線性判別分析法,返回降維后的數(shù)據(jù)
#參數(shù)n_components為降維后的維數(shù)
LDA(n_components=2).fit_transform(iris.data, iris.target)
# --- 例子 ---
# LDA相較于pca是有監(jiān)督的,但不能用于回歸分析
from sklearn.lda import LDA
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
iris_data = load_iris()
category = pd.Categorical(iris_data.target) # 將標簽進行量化,就是說本來都是字符串啊,但是最后計算的時候都需要量化成1,2,3類等
lda_2c = LDA(n_components=2)
x_pca_2c = lda_2c.fit_transform(iris_data.data,iris_data.target)
x_pca_2c.shape
plt.scatter(x_pca_2c[:,0],x_pca_2c[:,1],c=category.codes)
plt.show()
4.3 回顧
庫 | 類 | 說明 |
---|---|---|
decomposition | PCA | 主成分分析法 |
lda | LDA | 線性判別分析法 |
5 總結
再讓我們回歸一下本文開始的特征工程的思維導圖,我們可以使用sklearn完成幾乎所有特征處理的工作,而且不管是數(shù)據(jù)預處理,還是特征選擇,抑或降維,它們都是通過某個類的方法fit_transform完成的,fit_transform要不只帶一個參數(shù):特征矩陣,要不帶兩個參數(shù):特征矩陣加目標向量。這些難道都是巧合嗎?還是故意設計成這樣?方法fit_transform中有fit這一單詞,它和訓練模型的fit方法有關聯(lián)嗎?接下來,我將在《使用sklearn優(yōu)雅地進行數(shù)據(jù)挖掘》中闡述其中的奧妙!
6 參考資料
- FAQ: What is dummy coding?
- IRIS(鳶尾花)數(shù)據(jù)集
- 卡方檢驗
- 干貨:結合Scikit-learn介紹幾種常用的特征選擇方法
- 機器學習中,有哪些特征選擇的工程方法?
- 機器學習中的數(shù)學(4)-線性判別分析(LDA), 主成分分析(PCA)
- Scikit-learn Preprocessing 預處理
- 使用sklearn做單機特征工程
- 機器學習中的特征——特征選擇的方法以及注意點
- 特征選擇--scikit-learn
- 特征選擇 (feature_selection)
- 【機器學習算法實現(xiàn)】主成分分析(PCA)——基于python+numpy
- scikit-learn中PCA的使用方法