總結:sklearn機器學習之特徵工程

0 關於本文

​ 主要內容和結構框架由@jasonfreak–使用sklearn做單機特徵工程提供,其中夾雜了很多補充的例子,能夠讓大家更直觀的感受到各個參數的意義,有一些地方我也進行自己理解層面上的糾錯,目前有些細節和博主再進行討論,修改部分我都會以刪除來表示,讀者可以自行斟酌,能和我一塊討論便是極好的!還是多謝原作者,我這裏只是總結和補充

1 特徵工程是什麼?

  有這麼一句話在業界廣泛流傳:數據和特徵決定了機器學習的上限,而模型和算法只是逼近這個上限而已。那特徵工程到底是什麼呢?顧名思義,其本質是一項工程活動,目的是最大限度地從原始數據中提取特徵以供算法和模型使用。通過總結和歸納,人們認爲特徵工程包括以下方面:

img

特徵選擇主要有兩個目的:

  • 減少特徵數量、降維,使模型泛化能力更強,減少過擬合;
  • 增強對特徵和特徵值之間的理解。

​ 特徵處理是特徵工程的核心部分,sklearn提供了較爲完整的特徵處理方法,包括數據預處理,特徵選擇,降維等。首次接觸到sklearn,通常會被其豐富且方便的算法模型庫吸引,但是這裏介紹的特徵處理庫也十分強大!

  本文中使用sklearn中的IRIS(鳶尾花)數據集來對特徵處理功能進行說明。IRIS數據集由Fisher在1936年整理,包含4個特徵(Sepal.Length(花萼長度)、Sepal.Width(花萼寬度)、Petal.Length(花瓣長度)、Petal.Width(花瓣寬度)),特徵值都爲正浮點數,單位爲釐米。目標值爲鳶尾花的分類(Iris Setosa(山鳶尾)、Iris Versicolour(雜色鳶尾),Iris Virginica(維吉尼亞鳶尾))。導入IRIS數據集的代碼如下:

from sklearn.datasets import load_iris

#導入IRIS數據集
iris = load_iris()

#特徵矩陣
iris.data
print iris.data.shape  # (150, 4)

#目標向量
iris.target
print iris.target.shape  # (150, )

3/2 數據探索性分析(Exploratory Data Analysis,EDA)

​ 探索性分析能夠更好的瞭解數據集,檢查數據集的特徵和形狀,驗證腦海中的一些想法,對數據任務接下來的步驟有一個初步的想法。
​ 這裏我用了本地的iris數據集,方法是一樣的沒有區別,注意的就是需要看下一下數據的形式和加載過程中的header需不需要加載

這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述

2 數據預處理

通過特徵提取,我們能得到未經處理的特徵,這時的特徵可能有以下問題:

  • 不屬於同一量綱:即特徵的規格不一樣,不能夠放在一起比較。無量綱化可以解決這一問題。
  • 信息冗餘:對於某些定量特徵,其包含的有效信息爲區間劃分,例如學習成績,假若只關心“及格”或不“及格”,那麼需要將定量的考分,轉換成“1”和“0”表示及格和未及格。二值化可以解決這一問題。
  • 定性特徵不能直接使用:某些機器學習算法和模型只能接受定量特徵的輸入,那麼需要將定性特徵轉換爲定量特徵。最簡單的方式是爲每一種定性值指定一個定量值,但是這種方式過於靈活,增加了調參的工作。通常使用啞編碼的方式將定性特徵轉換爲定量特徵:假設有N種定性值,則將這一個特徵擴展爲N種特徵,當原始特徵值爲第i種定性值時,第i個擴展特徵賦值爲1,其他擴展特徵賦值爲0。啞編碼的方式相比直接指定的方式,不用增加調參的工作,對於線性模型來說,使用啞編碼後的特徵可達到非線性的效果。
  • 存在缺失值:缺失值需要補充。
  • 信息利用率低:不同的機器學習算法和模型對數據中信息的利用是不同的,之前提到在線性模型中,使用對定性特徵啞編碼可以達到非線性的效果。類似地,對定量變量多項式化,或者進行其他的轉換,都能達到非線性的效果。

      我們使用sklearn中的preproccessing庫來進行數據預處理,可以覆蓋以上問題的解決方案。

2.1 無量綱化 數據規範化

  無量綱化使不同規格的數據轉換到同一規格。常見的無量綱化方法有標準化和區間縮放法(我認爲這句話是錯誤的)。標準化的前提是特徵值服從正態分佈,標準化後,其轉換成標準正態分佈。區間縮放法利用了邊界值信息,將特徵的取值區間縮放到某個特點的範圍,例如[0, 1]等。

2.1.1 標準化 0均值標準化(Z-score standardization)

  標準化需要計算特徵的均值和標準差,公式表達爲:

img

  使用preproccessing庫的StandardScaler類對數據進行標準化的代碼如下:

from sklearn.preprocessing import StandardScaler

#標準化,返回值爲標準化後的數據
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],
#   ...


# --- 例子就是如下,他是對列也就是同一特徵下進行縮放,而不是對一個數據的不同特徵之間(行)進行縮放 ---
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

#區間縮放,返回值爲縮放到[0, 1]區間的數據
StandardScaler().fit_transform(data)

# array([[-1.22474487, -1.22474487],
#       [ 0.        ,  0.        ],
#       [ 1.22474487,  1.22474487]])

2.1.2 區間縮放法(線性函數歸一化(Min-Max scaling))

  區間縮放法的思路有多種,常見的一種爲利用兩個最值進行縮放,公式表達爲:

img

  使用preproccessing庫的MinMaxScaler類對數據進行區間縮放的代碼如下:

from sklearn.preprocessing import MinMaxScaler

#區間縮放,返回值爲縮放到[0, 1]區間的數據
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],
#  ...


# --- 例子就是如下,他是對列也就是同一特徵下進行縮放,而不是對一個數據的不同特徵之間(行)進行縮放 ---
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]]

# 區間縮放,返回值爲縮放到[0, 1]區間的數據
MinMaxScaler().fit_transform(data)
#array([[ 0. ,  0. ],
#       [ 0.5,  0.5],
#       [ 1. ,  1. ]])

2.1.3 標準化與歸一化的區別 標準化(規範化)與歸一化的區別

  (這是原作者的描述,我覺得有點問題) 簡單來說,標準化是依照特徵矩陣的列處理數據,其通過求z-score的方法,將樣本的特徵值轉換到同一量綱下。 歸一化是依照特徵矩陣的行處理數據,其目的在於樣本向量在點乘運算或其他核函數計算相似性時,擁有統一的標準,也就是說都轉化爲“單位向量”。規則爲L2的歸一化公式如下:

img

  使用preproccessing庫的Normalizer類對數據進行歸一化的代碼如下:

from sklearn.preprocessing import Normalizer

#歸一化,返回值爲歸一化後的數據
Normalizer().fit_transform(iris.data)



# --- 例子就是如下,他是對一行數據的不同特徵進行處理 ---
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

# 規範化 
Normalizer().fit_transform(data)

array([[ 0.4472136 ,  0.89442719],
       [ 0.6       ,  0.8       ],
       [ 0.6401844 ,  0.76822128]])

這裏比較搞混的一點是StandardScaler和Normalizer這個兩個概念的問題(大家翻譯上的誤差導致信息非常混亂),其實StandardScaler就是尺寸縮放問題,即使同一特徵下的數值在一定範圍內浮動,如將數值所放在0-1範圍內(MinMaxScaler), 或者將數據標準化成爲均值爲0,方差爲1的數據(Z-score);而另一個就是Normalizer,將同一行數據的不同特徵進行規範化,這樣一個數據的不同特徵具有相同的量綱或者表現力,比如說一個特徵是身高,1.7m,體重爲150斤,那麼兩個特徵之間差距太大,身高這個特徵變化根本無法起到決定作用(在體重這個變化特徵下),畢竟大家怎麼長都是一米多,但是體重差距一下子拉開20多是很正常的事

2.2 對定量特徵二值化

  定量特徵二值化的核心在於設定一個閾值,大於閾值的賦值爲1,小於等於閾值的賦值爲0,公式表達如下:

img

  使用preproccessing庫的Binarizer類對數據進行二值化的代碼如下:

from sklearn.preprocessing import Binarizer

#二值化,閾值設置爲3,返回值爲二值化後的數據
Binarizer(threshold=3).fit_transform(iris.data)

2.3 對定性特徵啞編碼

  由於IRIS數據集的特徵皆爲定量特徵,故使用其目標值進行啞編碼(實際上是不需要的)。使用preproccessing庫的OneHotEncoder類對數據進行啞編碼的代碼如下:

from sklearn.preprocessing import OneHotEncoder

#啞編碼,對IRIS數據集的目標值,返回值爲啞編碼後的數據
OneHotEncoder().fit_transform(iris.target.reshape((-1,1)))  

補充:但是從以一個角度來看,如果標籤需要被量化,這個就很有用了, 如下圖所示,將target這個標籤轉化成爲數值才能進行機器學習

這裏寫圖片描述

2.4 缺失值計算

  由於IRIS數據集沒有缺失值,故對數據集新增一個樣本,4個特徵均賦值爲NaN,表示數據缺失。使用preproccessing庫的Imputer類對數據進行缺失值計算的代碼如下:

原作者例子舉的不好,我這裏替換補充一下)

import numpy as np
from sklearn.preprocessing import Imputer
imp = Imputer(missing_values='NaN', strategy='mean', axis=0)  # 使用特徵的均值進行填充,其餘還有使用衆數填充等,只需要把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 數據變換

  常見的數據變換有基於多項式的、基於指數函數的、基於對數函數的。4個特徵,度爲2的多項式轉換公式如下:

img

  使用preproccessing庫的PolynomialFeatures類對數據進行多項式轉換的代碼如下:

from sklearn.preprocessing import PolynomialFeatures

#多項式轉換
#參數degree爲度,默認值爲2
PolynomialFeatures().fit_transform(iris.data)

  基於單變元函數的數據變換可以使用一個統一的方式完成,使用preproccessing庫的FunctionTransformer對數據進行對數函數轉換的代碼如下:

from numpy import log1p
from sklearn.preprocessing import FunctionTransformer

#自定義轉換函數爲對數函數的數據變換
#第一個參數是單變元函數
FunctionTransformer(log1p).fit_transform(iris.data)

2.6 回顧

功能 說明
StandardScaler 無量綱化 標準化(修正) 標準化,基於特徵矩陣的列,將特徵值轉換至服從標準正態分佈
MinMaxScaler 無量綱化 標準化(修正) 區間縮放,基於最大最小值,將特徵值轉換到[0, 1]區間上
Normalizer 歸一化(規範化) 基於特徵矩陣的行,將樣本向量轉換爲“單位向量”
Binarizer 二值化 基於給定閾值,將定量特徵按閾值劃分
OneHotEncoder 啞編碼 將定性數據編碼爲定量數據
Imputer 缺失值計算 計算缺失值,缺失值可填充爲均值等
PolynomialFeatures 多項式數據轉換 多項式數據轉換
FunctionTransformer 自定義單元數據轉換 使用單變元的函數來轉換數據

3 特徵選擇

這一段的主要是由特徵選擇–scikit-learn和原作者一塊組合的,我負責補充

​ 當數據預處理完成後,我們需要選擇有意義的特徵輸入機器學習的算法和模型進行訓練。通常來說,從兩個方面考慮來選擇特徵:

  • 特徵是否發散:如果一個特徵不發散,例如方差接近於0,也就是說樣本在這個特徵上基本上沒有差異,這個特徵對於樣本的區分並沒有什麼用。
  • 特徵與目標的相關性:這點比較顯見,與目標相關性高的特徵,應當優選選擇。除方差法外,本文介紹的其他方法均從相關性考慮。

     根據特徵選擇的形式又可以將特徵選擇方法分爲3種:

  • Filter:過濾法,按照發散性或者相關性對各個特徵進行評分,設定閾值或者待選擇閾值的個數,選擇特徵。

  • Wrapper:包裝法,根據目標函數(通常是預測效果評分),每次選擇若干特徵,或者排除若干特徵。
  • Embedded:嵌入法,先使用某些機器學習的算法和模型進行訓練,得到各個特徵的權值係數,根據係數從大到小選擇特徵。類似於Filter方法,但是是通過訓練來確定特徵的優劣。

      我們使用sklearn中的feature_selection庫來進行特徵選擇。

3.1 Filter

​ 過濾法主要思想是:按照發散性或者相關性對各個特徵進行對每一維的特徵“打分”,即給每一維的特徵賦予權重,這樣的權重就代表着該維特徵的重要性,設定閾值或者待選擇閾值的個數,選擇特徵。

3.1.1 移除低方差的特徵 (Removing features with low variance)

  這應該是最簡單的特徵選擇方法了:假設某特徵的特徵值只有0和1,並且在所有輸入樣本中,95%的實例的該特徵取值都是1,那就可以認爲這個特徵作用不大。如果100%都是1,那這個特徵就沒意義了。當特徵值都是離散型變量的時候這種方法才能用,如果是連續型變量,就需要將連續變量離散化之後才能用,而且實際當中,一般不太會有95%以上都取某個值的特徵存在,所以這種方法雖然簡單但是不太好用。可以把它作爲特徵選擇的預處理,先去掉那些取值變化小的特徵,然後再從接下來提到的的特徵選擇方法中選擇合適的進行進一步的特徵選擇。

使用方差選擇法,先要計算各個特徵的方差,然後根據閾值,選擇方差大於閾值的特徵。使用feature_selection庫的VarianceThreshold類來選擇特徵的代碼如下:

from sklearn.feature_selection import VarianceThreshold

#方差選擇法,返回值爲特徵選擇後的數據
#參數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)

​ 單變量特徵選擇能夠對每一個特徵進行測試,衡量該特徵和響應變量之間的關係,根據得分扔掉不好的特徵。對於迴歸和分類問題可以採用卡方檢驗等方式對特徵進行測試。
方法簡單,易於運行,易於理解,通常對於理解數據有較好的效果(但對特徵優化、提高泛化能力來說不一定有效);這種方法有許多改進的版本、變種。

3.1.2.1 卡方(Chi2)檢驗

適用於:分類問題(y離散)

​ 經典的卡方檢驗(原理及應用)是檢驗定性自變量對定性因變量的相關性。假設自變量有N種取值,因變量有M種取值,考慮自變量等於i且因變量等於j的樣本頻數的觀察值與期望的差距,構建統計量,其中A爲實際數,E爲理論值:

img

  這個統計量的含義簡而言之就是自變量對因變量的相關性。用feature_selection庫的SelectKBest類結合卡方檢驗來選擇特徵的代碼如下:

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

#選擇K個最好的特徵,返回選擇特徵後的數據
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相關係數 (Pearson Correlation)

適用於:迴歸問題(y連續)

​ 爾森相關係數是一種最簡單的,能幫助理解特徵和響應變量之間關係的方法,該方法衡量的是變量之間的線性相關性,結果的取值區間爲[-1,1],-1表示完全的負相關,+1表示完全的正相關,0表示沒有線性相關。

  Pearson Correlation速度快、易於計算,經常在拿到數據(經過清洗和特徵提取之後的)之後第一時間就執行。Scipy的 pearsonr 方法能夠同時計算 相關係數 和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) 創建100個均值爲0,方差爲1的高斯隨機數
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)的數組
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相關係數的一個明顯缺陷是,作爲特徵排序機制,他只對線性關係敏感。如果關係是非線性的,即便兩個變量具有一一對應的關係,Pearson相關性也可能會接近0。例如:

x = np.random.uniform(-1, 1, 100000)
print pearsonr(x, x**2)[0]
-0.00230804707612

  更多類似的例子參考 sample plots 。另外,如果僅僅根據相關係數這個值來判斷的話,有時候會具有很強的誤導性,如 Anscombe’s quartet ,最好把數據可視化出來,以免得出錯誤的結論。

3.1.3 互信息和最大信息係數 (Mutual information and maximal information coefficient (MIC)

  經典的互信息(互信息爲隨機變量X與Y之間的互信息I(X;Y)I(X;Y)爲單個事件之間互信息的數學期望)也是評價定性自變量對定性因變量的相關性的,互信息計算公式如下:

img

  互信息直接用於特徵選擇其實不是太方便:1、它不屬於度量方式,也沒有辦法歸一化,在不同數據及上的結果無法做比較;2、對於連續變量的計算不是很方便(X和Y都是集合,x,y都是離散的取值),通常變量需要先離散化,而互信息的結果對離散化的方式很敏感。

  最大信息係數克服了這兩個問題。它首先尋找一種最優的離散化方式,然後把互信息取值轉換成一種度量方式,取值區間在[0,1]。 minepy 提供了MIC功能。

反過頭來看 y=x^2 這個例子,MIC算出來的互信息值爲1(最大的取值)。

  爲了處理定量數據,最大信息係數法被提出,使用feature_selection庫的SelectKBest類結合最大信息係數法來選擇特徵的代碼如下:

from sklearn.feature_selection import SelectKBest
from minepy import MINE

#由於MINE的設計不是函數式的,定義mic方法將其爲函數式的,返回一個二元組,二元組的第2項設置成固定的P值0.5
def mic(x, y):
    m = MINE()
    m.compute_score(x, y)
    return (m.mic(), 0.5)

#選擇K個最好的特徵,返回特徵選擇後的數據
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 距離相關係數 (Distance Correlation)

  距離相關係數是爲了克服Pearson相關係數的弱點而生的。在x和x^2這個例子中,即便Pearson相關係數是0,我們也不能斷定這兩個變量是獨立的(有可能是非線性相關);但如果距離相關係數是0,那麼我們就可以說這兩個變量是獨立的。

  R的 energy 包裏提供了距離相關係數的實現,另外這是 Python gist 的實現。

> x = runif (1000, -1, 1)
> dcor(x, x**2)
[1] 0.4943864

  儘管有 MIC 和 距離相關係數 在了,但當變量之間的關係接近線性相關的時候,Pearson相關係數仍然是不可替代的。
  第一,Pearson相關係數計算速度快,這在處理大規模數據的時候很重要。
  第二,Pearson相關係數的取值區間是[-1,1],而MIC和距離相關係數都是[0,1]。這個特點使得Pearson相關係數能夠表徵更豐富的關係,符號表示關係的正負,絕對值能夠表示強度。當然,Pearson相關性有效的前提是兩個變量的變化關係是單調的。

3.2 Wrapper

​ 包裝法主要思想是:根據目標函數(通常是預測效果評分),每次選擇若干特徵,或者排除若干特徵。也可以將特徵子集的選擇看作是一個搜索尋優問題,生成不同的組合,對組合進行評價,再與其他的組合進行比較。這樣就將子集的選擇看作是一個是一個優化問題,這裏有很多的優化算法可以解決,尤其是一些啓發式的優化算法,如GA,PSO,DE,ABC等,詳見“優化算法—人工蜂羣算法(ABC)”,“優化算法—粒子羣算法(PSO)”。

3.2.1 遞歸特徵消除法

  遞歸消除特徵法使用一個基模型來進行多輪訓練,每輪訓練後,消除若干權值係數的特徵,再基於新的特徵集進行下一輪訓練。使用feature_selection庫的RFE類來選擇特徵的代碼如下:

from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

#遞歸特徵消除法,返回特徵選擇後的數據
#參數estimator爲基模型
#參數n_features_to_select爲選擇的特徵個數
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

​ 嵌入法主要思想是:使用某些機器學習的算法和模型進行訓練,得到各個特徵的權值係數,根據係數從大到小選擇特徵。類似於Filter方法,但是是通過訓練來確定特徵的優劣。其實是講在確定模型的過程中,挑選出那些對模型的訓練有重要意義的屬性。

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懲罰項來優化。具體操作爲:若一個特徵在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)
        #使用同樣的參數創建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
        #權值係數矩陣的行數對應目標值的種類數目
        for i in range(cntOfRow):
            for j in range(cntOfCol):
                coef = self.coef_[i][j]
                #L1邏輯迴歸的權值係數不爲0
                if coef != 0:
                    idx = [j]
                    #對應在L2邏輯迴歸中的權值係數
                    coef1 = self.l2.coef_[i][j]
                    for k in range(cntOfCol):
                        coef2 = self.l2.coef_[i][k]
                        #在L2邏輯迴歸中,權值係數之差小於設定的閾值,且在L1中對應的權值爲0
                        if abs(coef1-coef2) < self.threshold and j != k and self.coef_[i][k] == 0:
                            idx.append(k)
                    #計算這一類特徵的權值係數均值
                    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來規範化使之稀疏化
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數據的特徵進行排序
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 可選關聯繫數、卡方校驗、最大信息係數作爲得分計算的方法
RFE Wrapper 遞歸地訓練基模型,將權值係數較小的特徵從特徵集合中消除
SelectFromModel Embedded 訓練基模型,選擇權值係數較高的特徵

4 降維

  當特徵選擇完成後,可以直接訓練模型了,但是可能由於特徵矩陣過大,導致計算量大,訓練時間長的問題,因此降低特徵矩陣維度也是必不可少的。常見的降維方法除了以上提到的基於L1懲罰項的模型以外,另外還有主成分分析法(PCA)和線性判別分析(LDA),線性判別分析本身也是一個分類模型。PCA和LDA有很多的相似點,其本質是要將原始的樣本映射到維度更低的樣本空間中,但是PCA和LDA的映射目標不一樣:PCA是爲了讓映射後的樣本具有最大的發散性;而LDA是爲了讓映射後的樣本有最好的分類性能。所以說PCA是一種無監督的降維方法,而LDA是一種有監督的降維方法。

4.1 主成分分析法(PCA)

其他的一些參數的設置可以參考:scikit-learn中PCA的使用方法

  使用decomposition庫的PCA類選擇特徵的代碼如下:

from sklearn.decomposition import PCA

#主成分分析法,返回降維後的數據
#參數n_components爲主成分數目
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

#線性判別分析法,返回降維後的數據
#參數n_components爲降維後的維數
LDA(n_components=2).fit_transform(iris.data, iris.target)


# --- 例子 ---
# LDA相較於pca是有監督的,但不能用於迴歸分析
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完成幾乎所有特徵處理的工作,而且不管是數據預處理,還是特徵選擇,抑或降維,它們都是通過某個類的方法fit_transform完成的,fit_transform要不只帶一個參數:特徵矩陣,要不帶兩個參數:特徵矩陣加目標向量。這些難道都是巧合嗎?還是故意設計成這樣?方法fit_transform中有fit這一單詞,它和訓練模型的fit方法有關聯嗎?接下來,我將在《使用sklearn優雅地進行數據挖掘》中闡述其中的奧妙!

6 參考資料

  1. FAQ: What is dummy coding?
  2. IRIS(鳶尾花)數據集
  3. 卡方檢驗
  4. 乾貨:結合Scikit-learn介紹幾種常用的特徵選擇方法
  5. 機器學習中,有哪些特徵選擇的工程方法?
  6. 機器學習中的數學(4)-線性判別分析(LDA), 主成分分析(PCA)
  7. Scikit-learn Preprocessing 預處理
  8. 使用sklearn做單機特徵工程
  9. 機器學習中的特徵——特徵選擇的方法以及注意點
  10. 特徵選擇–scikit-learn
  11. 特徵選擇 (feature_selection)
  12. 【機器學習算法實現】主成分分析(PCA)——基於python+numpy
  13. scikit-learn中PCA的使用方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章