Python機器學習筆記 使用sklearn做特徵工程和資料探勘
特徵處理是特徵工程的核心部分,sklearn提供了較為完整的特徵處理方法,包括資料預處理,特徵選擇,降維等。首次接觸到sklearn,通常會被其豐富且方便的演算法模型庫吸引,但是這裡介紹的特徵處理庫也非常強大!
經過前人的總結,特徵工程已經形成了接近標準化的流程,如下圖所示(此圖來自此網友,若侵權,聯絡我,必刪除)
1 匯入資料
本文中使用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() # 特徵矩陣 data = iris.data # 目標向量 target = iris.target
從本地匯入資料集的程式碼如下:
# 匯入本地的iris資料集 dataframe = pd.read_csv('iris.csv',header=None) iris_data = dataframe.values # print(type(iris_data))#<class 'numpy.ndarray'> # 特徵矩陣 data = iris_data[:,0:-1] # 目標向量 target = iris_data[:,-1]
其中iris.txt的資料集如下:
5.1,3.5,1.4,0.2,Iris-setosa 4.9,3.0,1.4,0.2,Iris-setosa 4.7,3.2,1.3,0.2,Iris-setosa 4.6,3.1,1.5,0.2,Iris-setosa 5.0,3.6,1.4,0.2,Iris-setosa 5.4,3.9,1.7,0.4,Iris-setosa 4.6,3.4,1.4,0.3,Iris-setosa 5.0,3.4,1.5,0.2,Iris-setosa 4.4,2.9,1.4,0.2,Iris-setosa 4.9,3.1,1.5,0.1,Iris-setosa 5.4,3.7,1.5,0.2,Iris-setosa 4.8,3.4,1.6,0.2,Iris-setosa 4.8,3.0,1.4,0.1,Iris-setosa 4.3,3.0,1.1,0.1,Iris-setosa 5.8,4.0,1.2,0.2,Iris-setosa 5.7,4.4,1.5,0.4,Iris-setosa 5.4,3.9,1.3,0.4,Iris-setosa 5.1,3.5,1.4,0.3,Iris-setosa 5.7,3.8,1.7,0.3,Iris-setosa 5.1,3.8,1.5,0.3,Iris-setosa 5.4,3.4,1.7,0.2,Iris-setosa 5.1,3.7,1.5,0.4,Iris-setosa 4.6,3.6,1.0,0.2,Iris-setosa 5.1,3.3,1.7,0.5,Iris-setosa 4.8,3.4,1.9,0.2,Iris-setosa 5.0,3.0,1.6,0.2,Iris-setosa 5.0,3.4,1.6,0.4,Iris-setosa 5.2,3.5,1.5,0.2,Iris-setosa 5.2,3.4,1.4,0.2,Iris-setosa 4.7,3.2,1.6,0.2,Iris-setosa 4.8,3.1,1.6,0.2,Iris-setosa 5.4,3.4,1.5,0.4,Iris-setosa 5.2,4.1,1.5,0.1,Iris-setosa 5.5,4.2,1.4,0.2,Iris-setosa 4.9,3.1,1.5,0.1,Iris-setosa 5.0,3.2,1.2,0.2,Iris-setosa 5.5,3.5,1.3,0.2,Iris-setosa 4.9,3.1,1.5,0.1,Iris-setosa 4.4,3.0,1.3,0.2,Iris-setosa 5.1,3.4,1.5,0.2,Iris-setosa 5.0,3.5,1.3,0.3,Iris-setosa 4.5,2.3,1.3,0.3,Iris-setosa 4.4,3.2,1.3,0.2,Iris-setosa 5.0,3.5,1.6,0.6,Iris-setosa 5.1,3.8,1.9,0.4,Iris-setosa 4.8,3.0,1.4,0.3,Iris-setosa 5.1,3.8,1.6,0.2,Iris-setosa 4.6,3.2,1.4,0.2,Iris-setosa 5.3,3.7,1.5,0.2,Iris-setosa 5.0,3.3,1.4,0.2,Iris-setosa 7.0,3.2,4.7,1.4,Iris-versicolor 6.4,3.2,4.5,1.5,Iris-versicolor 6.9,3.1,4.9,1.5,Iris-versicolor 5.5,2.3,4.0,1.3,Iris-versicolor 6.5,2.8,4.6,1.5,Iris-versicolor 5.7,2.8,4.5,1.3,Iris-versicolor 6.3,3.3,4.7,1.6,Iris-versicolor 4.9,2.4,3.3,1.0,Iris-versicolor 6.6,2.9,4.6,1.3,Iris-versicolor 5.2,2.7,3.9,1.4,Iris-versicolor 5.0,2.0,3.5,1.0,Iris-versicolor 5.9,3.0,4.2,1.5,Iris-versicolor 6.0,2.2,4.0,1.0,Iris-versicolor 6.1,2.9,4.7,1.4,Iris-versicolor 5.6,2.9,3.6,1.3,Iris-versicolor 6.7,3.1,4.4,1.4,Iris-versicolor 5.6,3.0,4.5,1.5,Iris-versicolor 5.8,2.7,4.1,1.0,Iris-versicolor 6.2,2.2,4.5,1.5,Iris-versicolor 5.6,2.5,3.9,1.1,Iris-versicolor 5.9,3.2,4.8,1.8,Iris-versicolor 6.1,2.8,4.0,1.3,Iris-versicolor 6.3,2.5,4.9,1.5,Iris-versicolor 6.1,2.8,4.7,1.2,Iris-versicolor 6.4,2.9,4.3,1.3,Iris-versicolor 6.6,3.0,4.4,1.4,Iris-versicolor 6.8,2.8,4.8,1.4,Iris-versicolor 6.7,3.0,5.0,1.7,Iris-versicolor 6.0,2.9,4.5,1.5,Iris-versicolor 5.7,2.6,3.5,1.0,Iris-versicolor 5.5,2.4,3.8,1.1,Iris-versicolor 5.5,2.4,3.7,1.0,Iris-versicolor 5.8,2.7,3.9,1.2,Iris-versicolor 6.0,2.7,5.1,1.6,Iris-versicolor 5.4,3.0,4.5,1.5,Iris-versicolor 6.0,3.4,4.5,1.6,Iris-versicolor 6.7,3.1,4.7,1.5,Iris-versicolor 6.3,2.3,4.4,1.3,Iris-versicolor 5.6,3.0,4.1,1.3,Iris-versicolor 5.5,2.5,4.0,1.3,Iris-versicolor 5.5,2.6,4.4,1.2,Iris-versicolor 6.1,3.0,4.6,1.4,Iris-versicolor 5.8,2.6,4.0,1.2,Iris-versicolor 5.0,2.3,3.3,1.0,Iris-versicolor 5.6,2.7,4.2,1.3,Iris-versicolor 5.7,3.0,4.2,1.2,Iris-versicolor 5.7,2.9,4.2,1.3,Iris-versicolor 6.2,2.9,4.3,1.3,Iris-versicolor 5.1,2.5,3.0,1.1,Iris-versicolor 5.7,2.8,4.1,1.3,Iris-versicolor 6.3,3.3,6.0,2.5,Iris-virginica 5.8,2.7,5.1,1.9,Iris-virginica 7.1,3.0,5.9,2.1,Iris-virginica 6.3,2.9,5.6,1.8,Iris-virginica 6.5,3.0,5.8,2.2,Iris-virginica 7.6,3.0,6.6,2.1,Iris-virginica 4.9,2.5,4.5,1.7,Iris-virginica 7.3,2.9,6.3,1.8,Iris-virginica 6.7,2.5,5.8,1.8,Iris-virginica 7.2,3.6,6.1,2.5,Iris-virginica 6.5,3.2,5.1,2.0,Iris-virginica 6.4,2.7,5.3,1.9,Iris-virginica 6.8,3.0,5.5,2.1,Iris-virginica 5.7,2.5,5.0,2.0,Iris-virginica 5.8,2.8,5.1,2.4,Iris-virginica 6.4,3.2,5.3,2.3,Iris-virginica 6.5,3.0,5.5,1.8,Iris-virginica 7.7,3.8,6.7,2.2,Iris-virginica 7.7,2.6,6.9,2.3,Iris-virginica 6.0,2.2,5.0,1.5,Iris-virginica 6.9,3.2,5.7,2.3,Iris-virginica 5.6,2.8,4.9,2.0,Iris-virginica 7.7,2.8,6.7,2.0,Iris-virginica 6.3,2.7,4.9,1.8,Iris-virginica 6.7,3.3,5.7,2.1,Iris-virginica 7.2,3.2,6.0,1.8,Iris-virginica 6.2,2.8,4.8,1.8,Iris-virginica 6.1,3.0,4.9,1.8,Iris-virginica 6.4,2.8,5.6,2.1,Iris-virginica 7.2,3.0,5.8,1.6,Iris-virginica 7.4,2.8,6.1,1.9,Iris-virginica 7.9,3.8,6.4,2.0,Iris-virginica 6.4,2.8,5.6,2.2,Iris-virginica 6.3,2.8,5.1,1.5,Iris-virginica 6.1,2.6,5.6,1.4,Iris-virginica 7.7,3.0,6.1,2.3,Iris-virginica 6.3,3.4,5.6,2.4,Iris-virginica 6.4,3.1,5.5,1.8,Iris-virginica 6.0,3.0,4.8,1.8,Iris-virginica 6.9,3.1,5.4,2.1,Iris-virginica 6.7,3.1,5.6,2.4,Iris-virginica 6.9,3.1,5.1,2.3,Iris-virginica 5.8,2.7,5.1,1.9,Iris-virginica 6.8,3.2,5.9,2.3,Iris-virginica 6.7,3.3,5.7,2.5,Iris-virginica 6.7,3.0,5.2,2.3,Iris-virginica 6.3,2.5,5.0,1.9,Iris-virginica 6.5,3.0,5.2,2.0,Iris-virginica 6.2,3.4,5.4,2.3,Iris-virginica 5.9,3.0,5.1,1.8,Iris-virginica
2,資料預處理
通過特徵提取,我們能得到未經處理的特徵,這時的特徵可能有以下問題:
- 1,不屬於同一量綱:即特徵的規格不一樣,不能放在一起比較。無量綱化可以解決這一問題。
- 2,資訊亢餘:對於某些定量特徵,其包含的有效資訊為區間劃分,例如學習成績,假若只關心“及格”或者“不及格”,那麼需要將定量的考分,轉換成“1”和“0”表示及格和不及格。二值化可以解決這一問題。
- 3,定性特徵不能直接使用:某些機器學習演算法和模型只能接受定量特徵的輸入,那麼需要將定性特徵轉換為定量特徵。最簡單的方式是為每一種定性值指定一個定量值,但是這種方式過於靈活,增加了調參的工作。通常使用啞編碼的方式將定性特徵轉化為定量特徵:假設有N種定性值,則將這一個特徵擴充套件為N種特徵,當原始特徵值為第i種定性值時,第i個擴充套件特徵賦值為1,其他擴充套件特徵賦值為0,啞編碼的方式相比直接指定的方式,不用增加調參的工作,對於線性模型來說,使用啞編碼後的特徵可達到非線性的效果
- 4,存在缺失值:缺失值需要補充
- 5,資訊利用率低:不同的機器學習演算法和模型對資料中資訊的利用是不同的,之前提到線上性模型中,使用對定性特徵啞編碼可以達到非線性的效果。類似的,對於定量變數多項式化,或者進行其他的轉換,都能達到非線性的效果。
我們使用sklearn中的preprocessing庫來進行資料預處理,可以覆蓋以上問題的解決方案。
2.1 無量綱化
無量綱化使不同規格的資料轉換到同一規則。常見的無量綱化方法有標準化和區間縮放法。標準化的前提是特徵值服從正態分佈,標準化後,其轉換成標準正態分佈。區間縮放法利用了邊界值資訊,將特徵的取值區間縮放到某個特點的範圍,例如[0,1]等。
2.1.1 標準化
標準化需要計算特徵的均值和標準差,公式表達為:
使用preprocessing庫的StandardScaler類對資料進行標準化的程式碼如下:
from sklearn.preprocessing import StandardScaler #標準化,返回值為標準化後的資料 StandardScaler().fit_transform(iris.data)
StandardScler計算訓練集的平均值和標準差,以便測試資料及使用相同的變換,變換後的各維特徵有0均值,單位方差(也叫z-score規範化),計算方式是將特徵值減去均值,除以標準差。
fit 用於計算訓練資料的均值和方差,後面就會使用均值和方差來轉換訓練資料
fit_transform 不僅計算訓練資料的均值和方差,還會用計算出來的均值和方差來轉換訓練資料,從而把資料轉化成標準的正態分佈。
transform 很顯然,這只是進行轉換,只是把訓練資料轉換成標準的正態分佈。
2.1.2 區間縮放法(最小-最大規範化)
區間縮放法的思路有很多,常見的一種為利用兩個極值進行縮放,公式表達為:
使用preproccessing庫的MinMaxScaler類對資料進行區間縮放的程式碼如下:
from sklearn.preprocessing import MinMaxScaler #區間縮放,返回值為縮放到[0, 1]區間的資料 MinMaxScaler().fit_transform(iris.data)
區間縮放是對原始資料進行線性變換,變換到[0,1] 區間(當然也可以是其他固定最小最大值的區間)
2.2 對定量特徵二值化
定量特徵二值化的核心在於設定一個閾值,大於閾值的賦值為1,小於等於閾值的賦值為0,公式如下:
使用preprocessing庫的Binarizer類對資料進行二值化的程式碼如下:
from sklearn.preprocessing import Binarizer #二值化,閾值設定為3,返回值為二值化後的資料 Binarizer(threshold=3).fit_transform(iris.data)
給定閾值,將特徵轉化為0/1,最主要的是確定閾值設定。
2.3 對定性特徵啞編碼
由於IRIS資料集的特徵皆為定量特徵,故使用其目標值進行啞編碼(實際上是不需要的)。使用preprocessing庫的OneHotEncoder類對資料進行啞編碼的程式碼如下:
from sklearn.preprocessing import OneHotEncoder #啞編碼,對IRIS資料集的目標值,返回值為啞編碼後的資料 OneHotEncoder().fit_transform(iris.target.reshape((-1,1)))
One-hot編碼是使一種對離散特徵值的編碼方式,在LR模型中常用到,用於給線性模型增加非線效能力。
2.4 缺失值計算
缺失值是指粗糙資料中由於缺少資訊而造成的資料的聚類,分組,刪除或者截斷。它指的是現有資料集中某個或者某些屬性的值時不完全的。
缺失值的處理目前有兩種方法:刪除缺失值和填充缺失值。
2.4.1 刪除缺失值
如果一個樣本或者變數中所包含的缺失值超過一定的比例,比如超過樣本或者變數的一半,此時這個樣本或者變數所含有的資訊是有限的,如果我們強行對資料進行填充處理,可能會加入過大的人工資訊,導致建模效果大打折扣,這種情況下,我們一般選擇從資料中剔除整個樣本或者變數,即刪除缺失值。
2.4.2 缺失值填充
-
隨機填充法
從字面上理解就是找一個隨機數,對缺失值進行填充,這種方法沒有考慮任何的資料特性,填充後可能還是會出現異常值等情況,一般情況下不建議使用。
-
均值填充法
尋找與缺失值變數相關性最大的那個變數把資料分成幾個組,然後分別計算每個組的均值,然後把均值填入缺失的位置作為它的值,如果找不到相關性較好的變數,也可以統計變數已有資料的均值,然後把它填入缺失位置。這種方法會在一定程度上改變資料的分佈。
-
最相似填充法
在資料集中找到一個與它最相似的樣本,然後用這個樣本的值對缺失值進行填充。
與均值填充法有點類似,尋找與缺失值變數(比如x)相關性最大的那個變數(比如y),然後按照變數y的值進行排序,然後得到相應的x的排序,最後用缺失值所在位置的前一個值來代替缺失值。
-
迴歸填充法
把缺失值變數作為一個目標變數y,把缺失值變數已有部分資料作為訓練集,尋找與其高度相關的變數x建立迴歸方程,然後把缺失值變數y所在位置對應的x作為預測集,對缺失進行預測,用預測結果來代替缺失值。
-
k近鄰填充法
利用knn演算法,選擇缺失值的最近k個近鄰點,然後根據缺失值所在的點離這幾個點距離的遠近進行加權平均來估計缺失值。
2.4.3 示例——均值填充法
由於IRIS資料集沒有缺失值,故對資料集新增一個樣本,4個特徵均賦值為NaN,表示資料缺失。使用preprocessing庫的Imputer類對資料進行缺失值計算的程式碼如下:
from numpy import vstack, array, nan from sklearn.preprocessing import Imputer #缺失值計算,返回值為計算缺失值後的資料 #引數missing_value為缺失值的表示形式,預設為NaN #引數strategy為缺失值填充方式,預設為mean(均值) Imputer().fit_transform(vstack((array([nan, nan, nan, nan]), iris.data)))
2.5 資料變換
常用的資料變換有基於多項式的,基於指數函式的,基於對數函式的,4個特徵,度為2的多項式轉換公式如下:
使用preprocessing庫的PolynomialFeatures類對資料進行多項式轉換的程式碼如下:
from sklearn.preprocessing import PolynomialFeatures #多項式轉換 #引數degree為度,預設值為2 PolynomialFeatures().fit_transform(iris.data)
基於單變元函式的資料變換可以使用一個統一的方式完成,使用preprocessing庫的FunctionTransformer 對資料進行對數函式轉換的程式碼如下:
from numpy import log1p from sklearn.preprocessing import FunctionTransformer #自定義轉換函式為對數函式的資料變換 #第一個引數是單變元函式 FunctionTransformer(log1p).fit_transform(iris.data)
2.6 異常值處理
-
截斷法
資料預處理第一步,通常是對異常值的處理。“截斷”方法具體步驟為:首先,求一次過程中原始資料的上四分位值Q3,作為首尾無效資料限界點;然後從原始資料的開頭向後和尾部向前,提出所有小於Q3數值,直到碰到第一個不小於Q3的資料,則停止截斷。
首先,要得到資料的熵四分位數和下四分位數,利用np.percentile(),用法如下:
import numpy as np x = np.array([[1,2,3],[7,8,9]]) Q1 = np.percentile(x,25) # 1st quartile Q3 = np.percentile(x,75) # 3st quartile
假設資料集時x = [1,2,3,98,99,10000],顯然最後一個數10000是一個超限點,它的Q1 = 25,Q3 = 75,四分位距IQR(the interquartile range)=Q1 - Q3。若上下界分別擴大0.5倍,令k = 1.5 為high = Q3 + k*(Q3-Q1),下界為low = Q1-k*(Q3-Q1),即上界為-50下界為150,顯然10000超限,如果想調整上界下界的範圍,調整係數即可。
四分位數(Quartile)是統計學中分位數的一種,即把全部數值由小到大排列並分成四等份。 處於三個切割點位置的數值就是四分位數。 第一四分位數 (Q1)。又稱“較小四分位數”,等於該樣本中全部數值由小到大排列後第25%的數字。 第二四分位數 (Q2)。又稱“中位數”,等於該樣本中全部數值由小到大排列後第50%的數字。 第三四分位數 (Q3),又稱“較大四分位數”,等於該樣本中全部數值由小到大排列後第75%的數字。 第三四分位數與第一四分位數的差距又稱四分位距(InterQuartile Range, IQR)
對於一個矩陣df,按列迴圈找到每列資料的異常值,如果某個樣本含有n個以上的超限特徵,返回行號。
注意:在進行這一步之前,先要處理好缺失值的標籤量。
# outlier detection def detect_outliers(df,n,feature_name): ''' df: the feature dataframe; n: if outlier feature more than n features: the name of columns detected return the index ''' outlier_indices=[] for col in feature_name: Q1 = np.percentile(df[col],25) Q3 = np.percentile(df[col],75) # interquartile range(IQR) IQR = Q3 - Q1 outlier_step = 1.5 * IQR # Determine a list of indeices of ouliers for feature col outlier_list_col = df[(df[col] < Q1 - outlier_step) | (df[col] > Q3 + outlier_step)].index.tolist() # append the found oulier indices for col to the list of outlier indices outlier_indices.extend(outlier_list_col) # select observations containing more than 2 outliers outlier_indices = Counter(outlier_indices) multiple_outliers = list(k for k, v in outlier_indices.items() if v > n) return multiple_outliers
經過檢查後,假設特徵矩陣有10(列)個特徵,規範包含大於4列超過了範圍,返回行號。
ouliter_result = detect_outliers(feature, 4, feature.columns.tolist())
-
單變數異常值檢測(格拉布斯法)
首先,將變數按照其值從小到大進行順序排列x1,x2.....xn
其次,計算平均值x拔和標準差S,

同時計算偏離值,即平均值與最大值之差和平均值與最小值之差,然後確定一個可疑值,一般是偏離平均值較大的那個。
計算統計量gi(殘差與標準差的比值),i為可疑值的序列號。
再者,將gi與格拉布斯表給出的臨界值GP(n)比較,如果計算的Gi值大於表中的臨界值GP(n),則能判斷該測量資料是異常值,可以剔除。這裡臨界值GP(n)與兩個引數有關:檢出水平α和測量次數n 。
檢出水平α:如果要求嚴格,檢出水平α可以定得小一些,例如定α=0.01,那麼置信概率P=1-α=0.99;如果要求不嚴格,α可以定得大一些,例如定α=0.10,即P=0.90;通常定α=0.05,P=0.95。
-
多變數異常值檢測(基於距離計算)
基於距離的多變數異常值檢測類似與k近鄰演算法的思路,一般的思路是計算各樣本點到中心點的距離,如果距離太大,則判斷為異常值,這裡距離的度量一般使用馬氏距離(Mahalanobis Distance)。因為馬氏距離不受量綱的影響,而且在多元條件下,馬氏距離還考慮了變數之間的相關性,這使得它優於歐氏距離。
2.7 回顧
3 特徵選擇
當資料預處理完成後,我們需要選擇有意義的特徵輸入機器學習的演算法和模型進行訓練。通常來說,從兩個方面考慮來選擇特徵:
- 特徵是否發散:如果一個特徵不發散,例如方差接近於0,也就是說樣本在這個特徵上基本沒有差異,這個特徵對樣本的區分並沒有什麼用。
- 特徵與目標的相關性:這點比較明顯,與目標相關性高的特徵,應當優先選擇。除方差法外,我們介紹的其他方法均從相關性考慮。
根據特徵選擇的形式又可以將特徵選擇方法分為3種:
- Filter:過濾法,按照發散性或者相關性對各個特徵進行評分,設定閾值或者選擇閾值的個數,選擇特徵。
- Wrapper:包裝法,根據目標函式(通常是預測效果),每次選擇若干特徵,護著排除若干特徵。
- Embedded:嵌入法,先使用某些機器學習的演算法和模型進行訓練,得到各個特徵的權值係數,根據係數從大到小選擇特徵。
我們使用sklearn中的feature_selection庫來進行特徵選擇。
3.1 Filter
3.1.1 方差選擇法
使用方差選擇法,先要計算各個特徵的方差,然後根據閾值,選擇方差大於閾值的特徵。使用feature_selection庫的VarianceThreshold類來選擇特徵的程式碼如下:
from sklearn.feature_selection import VarianceThreshold #方差選擇法,返回值為特徵選擇後的資料 #引數threshold為方差的閾值 VarianceThreshold(threshold=3).fit_transform(iris.data)
3.1.2 相關係數法
使用相關係數法,先要計算各個特徵對目標值的相關係數以及相關係數的P值,用feature_selection庫的SelectKBest類結合相關係數來選擇特徵的程式碼如下:
from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr #選擇K個最好的特徵,返回選擇特徵後的資料 #第一個引數為計算評估特徵是否好的函式,該函式輸入特徵矩陣和目標向量,輸出二元組 #(評分,P值)的陣列,陣列第i項為第i個特徵的評分和P值。在此定義為計算相關係數 #引數k為選擇的特徵個數 SelectKBest(lambda X, Y: array(map(lambda x:pearsonr(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)
3.1.3 卡方檢驗
經典的卡方檢驗是檢驗定性自變數對定性因變數的相關性。假設自變數有N種取值,因變數有M種取值,考慮自變數等於i且因變數等於j的樣本頻數的觀察值與期望的差距,構建統計量:
這個統計量的含義簡而言之就是自變數對因變數的相關性,用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)
3.1.4 互資訊法
經典的互資訊也是評價定性自變數對定性因變數的相關性的,互資訊計算公式如下:
為了處理定量資料,最大資訊係數法被提出,使用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)
3.2 Wrapper
Wrapper方與特徵過濾不同,它不單看特徵和目標直接的關聯性,而是從新增這個特徵後模型最終的表現來評估特徵的好壞。而在一個特徵空間中,產生特徵子集的過程可以看成是一個搜尋問題。目前主要用的一個Wrapper方法是遞迴特徵消除法。
遞迴特徵消除的主要思想是不斷使用從特徵空間中抽取出來的特徵子集構建模型,然後選出最好的的特徵,把選出來的特徵放到一遍,然後在剩餘的特徵上重複這個過程,直到所有特徵都遍歷了。這個過程中特徵被消除的次序就是特徵的排序。這是一種尋找最優特徵子集的貪心演算法。
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)
3.3 Embedded
Embedded方法是在模型構建的同時選擇最好的特徵。最為常用的一個Embedded方法就是:正則化。正則化就是把額外的約束或者懲罰項加到已有模型的損失函式上,以防止過擬合併提高泛化能力。正則化分為L1正則化(Lasso)和L2正則化(Ridge迴歸)。
L1正則化是將所有係數的絕對值之和乘以一個係數作為懲罰項加到損失函式上,現在模型尋找最優解的過程中,需要考慮正則項的影響,即如何在正則項的約束下找到最小損失函式。同樣的L2正則化也是將一個懲罰項加到損失函式上,不過懲罰項是引數的平方和。其他還有基於樹的特徵選擇等。
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)
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懲罰項的邏輯迴歸模型,來選擇特徵的程式碼如下:
from sklearn.feature_selection import SelectFromModel #帶L1和L2懲罰項的邏輯迴歸作為基模型的特徵選擇 #引數threshold為權值係數之差的閾值 SelectFromModel(LR(threshold=0.5, C=0.1)).fit_transform(iris.data, iris.target)
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)
3.4 回顧
4 降維
當特徵選擇完成後,可以直接訓練模型了,但是可能由於特徵矩陣過大,導致計算量大,訓練時間比較長的問題,因此降低特徵矩陣維度也是必不可少的。常見的降維方法除了以上提到的基於L1懲罰項的模型以外,另外有主成分分析法(PCA)和線性判別分析(LDA),線性判別分析本身也是一種分類模型。PCA和LDA有很多的相似點,其本質是要將原始的樣本對映到維度更低的樣本空間中,但是PCA和LDA的對映目標不一樣:PCA是為了讓對映後的樣本具有最大的發散性,而LDA是為了讓對映後的樣本有最好的分類效能,所以說PCA是一種無監督的降維方法,而LDA是一種有監督的降維方法。
4.1 主成分分析法(PCA)
使用decomposition庫的PCA類選擇特徵的程式碼如下:
from sklearn.decomposition import PCA #主成分分析法,返回降維後的資料 #引數n_components為主成分數目 PCA(n_components=2).fit_transform(iris.data)
主成分分析原理及其Python實現博文:可以點選這裡
4.2 線性判別分析(LDA)
使用lda庫的LDA類選擇特徵的程式碼如下:
from sklearn.lda import LDA #線性判別分析法,返回降維後的資料 #引數n_components為降維後的維數 LDA(n_components=2).fit_transform(iris.data, iris.target)
可以點選這裡
4.3 回顧
5 使用sklearn進行資料探勘
那麼我們可以使用sklearn完成幾乎所有特徵處理的工作,而且不管是資料預處理,還是特徵選擇,抑或降維,他們都是通過某個類的方法fit_transform完成的,fit_transform要不只帶一個引數:特徵矩陣,要不帶兩個引數:特徵矩陣加目標向量。這些難道都是巧合嗎?還是故意設計成這樣?方法fit_transform中有fit這一單詞,它和訓練模型的fit方法有關聯嗎?
5.1 資料探勘的步驟
資料探勘通常包括資料採集,資料分析,特徵工程,訓練模型,模型評估等步驟。使用sklearn工具可以方便地進行特徵工程和模型訓練的工作,上面也提到了這些難道都是巧合嗎?還是故意設計成這樣?方法fit_transform中有fit這一單詞,它和訓練模型的fit方法有關聯嗎?
顯然,這不是巧合,這是sklearn的設計風格。我們能夠更加優雅的使用sklearn進行特徵工程和模型訓練工作。此時,我們從一個數據挖掘的場景入手:
我們使用sklearn進行虛線框內的工作(sklearn也可以進行文字特徵提取),通過分析sklearn原始碼,我們可以看到除訓練,預測和評估以外,處理其他工作的類都實現了3個方法:fit transform fit_transform 。從命名中可以看到,fit_transform方法是先呼叫fit然後呼叫transform,我們只需要關注fit方法和transform方法即可。
transform方法主要用來對特徵進行轉換。從可利用資訊的角度來說,轉換分為無資訊轉換和有資訊轉換。無資訊轉換是指不利用任何其他資訊進行轉換,比如指數、對數函式轉換等。有資訊轉換從是否利用目標值向量又可分為無監督轉換和有監督轉換。無監督轉換指只利用特徵的統計資訊的轉換,統計資訊包括均值、標準差、邊界等等,比如標準化、PCA法降維等。有監督轉換指既利用了特徵資訊又利用了目標值資訊的轉換,比如通過模型選擇特徵、LDA法降維等。通過總結常用的轉換類,我們得到下表:
不難看出,只有有資訊的轉換類的fit方法才實際有用,顯然fit方法的主要工作是獲取特徵資訊和目標值資訊,在這點上,fit方法和模型訓練時的fit方法就能夠聯絡在一起了:都是通過分析特徵和目標值 ,提取有價值的資訊,對於轉換類來說是某些統計量,對於模型來說可能是特徵的權值係數等。另外,只有有監督的轉換類的fit和transform方法才需要特徵和目標值兩個引數。fit方法無用不代表沒實現,而是除合法性校驗以外,其並沒有對特徵和目標值進行任何處理,Normalizer的fit方法實現如下:
def fit(self, X, y=None): """Do nothing and return the estimator unchanged This method is just there to implement the usual API and hence work in pipelines. """ X = check_array(X, accept_sparse='csr') return self
基於這些特徵處理工作都有共同的方法,那麼試想可不可以將他們組合在一起?在本文假設的場景中,我們可以看到這些工作的組合形式有兩種:流水線式和並行式。基於流水線組合的工作需要依次進行,前一個工作的輸出是後一個工作的輸入;基於並行式的工作可以同時進行,其使用同樣的輸入,所有工作完成後將各自的輸出合併之後輸出。sklearn提供了包pipeline來完成流水線式和並行式的工作。
5.1.1 資料初貌
在此,我們仍然使用IRIS資料集來進行說明,為了適應提出的場景,對元資料集需要稍微加工:
from numpy import hstack, vstack, array, median, nan from numpy.random import choice from sklearn.datasets import load_iris #特徵矩陣加工 #使用vstack增加一行含缺失值的樣本(nan, nan, nan, nan) #使用hstack增加一列表示花的顏色(0-白、1-黃、2-紅),花的顏色是隨機的,意味著顏色並不影響花的分類 iris.data = hstack((choice([0, 1, 2], size=iris.data.shape[0]+1).reshape(-1,1), vstack((iris.data, array([nan, nan, nan, nan]).reshape(1,-1))))) #目標值向量加工 #增加一個目標值,對應含缺失值的樣本,值為眾數 iris.target = hstack((iris.target, array([median(iris.target)])))
5.1.2 關鍵技術
並行處理,流水線處理,自動化調參,持久化是使用sklearn優雅的進行資料探勘的核心。並行處理和流水線處理將多個特徵處理工作,甚至包括模型訓練工作組合成一個工作(從程式碼的角度來說,即將多個物件組成了一個物件)。在組合的前提下,自動化調參技術幫我們省去了人工調參的繁瑣。訓練好的模型是儲存在記憶體中的資料,持久化能夠將這些資料儲存在檔案系統中,之後使用時候無需再進行訓練,直接從檔案系統中載入即可。
5.2 並行處理
並行處理使得多個特徵處理工作能夠並行的進行,根據對特徵矩陣的讀取方式不同,可分為整體並行處理和部分並行處理。整體並行處理,即並行處理的每個工作的輸入都是特徵矩陣的整體;部分並行處理,即可定義每個工作需要輸入的特徵矩陣的列。
5.2.1 整體並行處理
pipeline包提供了FeatureUnion類來進行整體並行處理:
from numpy import log1p from sklearn.preprocessing import FunctionTransformer from sklearn.preprocessing import Binarizer from sklearn.pipeline import FeatureUnion #新建將整體特徵矩陣進行對數函式轉換的物件 step2_1 = ('ToLog', FunctionTransformer(log1p)) #新建將整體特徵矩陣進行二值化類的物件 step2_2 = ('ToBinary', Binarizer()) #新建整體並行處理物件 #該物件也有fit和transform方法,fit和transform方法均是並行地呼叫需要並行處理的物件的fit和transform方法 #引數transformer_list為需要並行處理的物件列表,該列表為二元組列表,第一元為物件的名稱,第二元為物件 step2 = ('FeatureUnion', FeatureUnion(transformer_list=[step2_1, step2_2, step2_3]))
5.2.2 部分並行處理
整體並行處理有其缺陷,在一些場景下,我們只需要對特徵矩陣的某些列進行轉換,而不是所有列,pipeline並沒有提供相應的類(僅OneHotEncoder類實現了該功能)需要我們再FeatureUnion的基礎上進行優化:
from sklearn.pipeline import FeatureUnion, _fit_one_transformer, _fit_transform_one, _transform_one from sklearn.externals.joblib import Parallel, delayed from scipy import sparse import numpy as np #部分並行處理,繼承FeatureUnion class FeatureUnionExt(FeatureUnion): #相比FeatureUnion,多了idx_list引數,其表示每個並行工作需要讀取的特徵矩陣的列 def __init__(self, transformer_list, idx_list, n_jobs=1, transformer_weights=None): self.idx_list = idx_list FeatureUnion.__init__(self, transformer_list=map(lambda trans:(trans[0], trans[1]), transformer_list), n_jobs=n_jobs, transformer_weights=transformer_weights) #由於只部分讀取特徵矩陣,方法fit需要重構 def fit(self, X, y=None): transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list) transformers = Parallel(n_jobs=self.n_jobs)( #從特徵矩陣中提取部分輸入fit方法 delayed(_fit_one_transformer)(trans, X[:,idx], y) for name, trans, idx in transformer_idx_list) self._update_transformer_list(transformers) return self #由於只部分讀取特徵矩陣,方法fit_transform需要重構 def fit_transform(self, X, y=None, **fit_params): transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list) result = Parallel(n_jobs=self.n_jobs)( #從特徵矩陣中提取部分輸入fit_transform方法 delayed(_fit_transform_one)(trans, name, X[:,idx], y, self.transformer_weights, **fit_params) for name, trans, idx in transformer_idx_list) Xs, transformers = zip(*result) self._update_transformer_list(transformers) if any(sparse.issparse(f) for f in Xs): Xs = sparse.hstack(Xs).tocsr() else: Xs = np.hstack(Xs) return Xs #由於只部分讀取特徵矩陣,方法transform需要重構 def transform(self, X): transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list) Xs = Parallel(n_jobs=self.n_jobs)( #從特徵矩陣中提取部分輸入transform方法 delayed(_transform_one)(trans, name, X[:,idx], self.transformer_weights) for name, trans, idx in transformer_idx_list) if any(sparse.issparse(f) for f in Xs): Xs = sparse.hstack(Xs).tocsr() else: Xs = np.hstack(Xs) return Xs
在本文提出的場景中,我們對特徵矩陣的第1列(花的顏色)進行定性特徵編碼,對第2,3,4列進行對數函式轉換,對第5列進行定量特徵二值化處理。使用FeatureUnionExt類進行部分並行處理的程式碼如下:
from numpy import log1p from sklearn.preprocessing import OneHotEncoder from sklearn.preprocessing import FunctionTransformer from sklearn.preprocessing import Binarizer #新建將部分特徵矩陣進行定性特徵編碼的物件 step2_1 = ('OneHotEncoder', OneHotEncoder(sparse=False)) #新建將部分特徵矩陣進行對數函式轉換的物件 step2_2 = ('ToLog', FunctionTransformer(log1p)) #新建將部分特徵矩陣進行二值化類的物件 step2_3 = ('ToBinary', Binarizer()) #新建部分並行處理物件 #引數transformer_list為需要並行處理的物件列表,該列表為二元組列表,第一元為物件的名稱,第二元為物件 #引數idx_list為相應的需要讀取的特徵矩陣的列 step2 = ('FeatureUnionExt', FeatureUnionExt(transformer_list=[step2_1, step2_2, step2_3], idx_list=[[0], [1, 2, 3], [4]]))
5.3 流水線處理
pipeline包提供了Pipeline類來進行流水線處理,流水線上除最後一個工作以外,其他都要執行fit_transfrom方法,且上一個工作輸出作為下一個工作的輸入。最後一個工作必須實現fit方法,輸入為上一個工作的輸出;但是不限定一定有transform方法,因為流水線的最後一個工作可能是訓練!
根據文中提出的場景,結合並行處理,構造完整的流水線的程式碼如下:
from numpy import log1p from sklearn.preprocessing import Imputer from sklearn.preprocessing import OneHotEncoder from sklearn.preprocessing import FunctionTransformer from sklearn.preprocessing import Binarizer from sklearn.preprocessing import MinMaxScaler from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 from sklearn.decomposition import PCA from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline #新建計算缺失值的物件 step1 = ('Imputer', Imputer()) #新建將部分特徵矩陣進行定性特徵編碼的物件 step2_1 = ('OneHotEncoder', OneHotEncoder(sparse=False)) #新建將部分特徵矩陣進行對數函式轉換的物件 step2_2 = ('ToLog', FunctionTransformer(log1p)) #新建將部分特徵矩陣進行二值化類的物件 step2_3 = ('ToBinary', Binarizer()) #新建部分並行處理物件,返回值為每個並行工作的輸出的合併 step2 = ('FeatureUnionExt', FeatureUnionExt(transformer_list=[step2_1, step2_2, step2_3], idx_list=[[0], [1, 2, 3], [4]])) #新建無量綱化物件 step3 = ('MinMaxScaler', MinMaxScaler()) #新建卡方校驗選擇特徵的物件 step4 = ('SelectKBest', SelectKBest(chi2, k=3)) #新建PCA降維的物件 step5 = ('PCA', PCA(n_components=2)) #新建邏輯迴歸的物件,其為待訓練的模型作為流水線的最後一步 step6 = ('LogisticRegression', LogisticRegression(penalty='l2')) #新建流水線處理物件 #引數steps為需要流水線處理的物件列表,該列表為二元組列表,第一元為物件的名稱,第二元為物件 pipeline = Pipeline(steps=[step1, step2, step3, step4, step5, step6])
5.4 自動化調參
網格搜尋為自動化調參的常用技術之一,grid_search包提供了自動化調參的工具,包括GridSearchCV類。對組合好的物件進行訓練以及調參的程式碼如下:
from sklearn.grid_search import GridSearchCV #新建網格搜尋物件 #第一引數為待訓練的模型 #param_grid為待調引數組成的網格,字典格式,鍵為引數名稱(格式“物件名稱__子物件名稱__引數名稱”),值為可取的引數值列表 grid_search = GridSearchCV(pipeline, param_grid={'FeatureUnionExt__ToBinary__threshold':[1.0, 2.0, 3.0, 4.0], 'LogisticRegression__C':[0.1, 0.2, 0.4, 0.8]}) #訓練以及調參 grid_search.fit(iris.data, iris.target)
5.5 持久化
externals.joblib包提供了dump和load方法來持久化和載入記憶體資料:
#持久化資料 #第一個引數為記憶體中的物件 #第二個引數為儲存在檔案系統中的名稱 #第三個引數為壓縮級別,0為不壓縮,3為合適的壓縮級別 dump(grid_search, 'grid_search.dmp', compress=3) #從檔案系統中載入資料到記憶體中 grid_search = load('grid_search.dmp')
5.6 回顧
注意:組合和持久化都會涉及pickle技術,在Sklearn的技術文件中有說明,將lambda定義函式作為FunctionTransformer的自定義轉換函式將不能pickle化。
參考資料:http://www.cnblogs.com/jasonfreak/p/5448462.html
http://www.cnblogs.com/jasonfreak/p/5448385.html
(在此做筆記的目的是學習,並掌握特徵工程,不喜勿噴,謝謝)