特征處理是特征工程的核心部分,特征工程是數據分析中最耗時間和精力的一部分工作,它不像算法和模型那樣式確定的步驟,更多的是工程上的經驗和權衡,因此沒有統一的方法,但是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,數據預處理
對於一個項目,首先是分析項目的目的和需求,了解這個項目屬於什么問題,要達到什么效果。然后提取數據,做基本的數據清洗。第三步是特征工程,這個需要耗費很大的精力。如果特征工程做得好,那么后面選擇什么算法其實差異不大,反之,不管選擇什么算法,效果都不會有突破性的提升。第四步是跑算法,通常情況下,我將自己會的所有的能跑的算法先跑一遍,看看效果,分析一下precesion/recall和f1-score,看看有沒有什么異常(譬如有好幾個算法precision特別好,但是recall特別低,這就要從數據中找原因,或者從算法中看是不是因為算法不適合這個數據),如果沒有異常,那么就進行下一步,選擇一兩個跑的結果最好的算法進行調優。調優的方法很多,調整參數的話可以用網格搜索,隨機搜索等,調整性能的話,可以根據具體的數據和場景進行具體分析,調優后再去跑一遍算法,看有沒有提高,如果沒有,找原因,數據還是算法問題,是數據質量不好,還是特征問題,還是算法問題??一個一個排查,找解決方法,特征問題就回到第三步再進行特征工程,數據問題就回到第一步看數據清洗有沒有遺漏,異常值是否影響了算法的結果,算法問題就回到第四步,看算法流程中哪一步出了問題。如果實在不行,可以搜一下相關的論文,看看論文中有沒有解決方法。這樣反復來幾遍,就可以出結果了,寫技術文檔和分析報告,最后想產品講解我們做的東西。然后他們再提需求,不斷循環,最后代碼上線,該bug。
直觀來看,可以使用一個流程圖來表示:
為什么要進行數據清洗呢?
我們之前實踐的數據,比如iris數據集,波士頓房價數據,電影評分數據集,手寫數字數據集等等,數據質量都很高,沒有缺失值,沒有異常點,也沒有噪音。而在真實數據中,我們拿到的數據可能包含了大量的缺失值,可能包含大量的噪音,也可能因為人工錄入錯誤導致有異常點存在,對我們挖掘出有效信息造成了一定的困擾,所以我們需要通過一些方法啊,盡量提高數據的質量。
2.1,分析數據
在實際項目中,當我們確定需求后就會去找相應的數據,拿到數據后,首先要對數據進行描述性統計分析,查看哪些數據是不合理的,也可以知道數據的基本情況。如果是銷售額數據可以通過分析不同商品的銷售總額,人均消費額,人均消費次數等,同一商品的不同時間的消費額,消費頻次等等,了解數據的基本情況。此外可以通過作圖的形式來了解數據的質量,有無異常點,有無噪音等。
python中包含了大量的統計命令,其中主要的統計特征函數如下圖所示:
2.2 處理數據(無量綱化數據的處理)
通過特征提取,我們能得到未經處理的特征,這時的特征可能有以下問題:
- 1,不屬於同一量綱:即特征的規格不一樣,不能放在一起比較。無量綱化可以解決這一問題。
- 2,信息亢余:對於某些定量特征,其包含的有效信息為區間划分,例如學習成績,假若只關心“及格”或者“不及格”,那么需要將定量的考分,轉換成“1”和“0”表示及格和不及格。二值化可以解決這一問題。
- 3,定性特征不能直接使用:某些機器學習算法和模型只能接受定量特征的輸入,那么需要將定性特征轉換為定量特征。最簡單的方式是為每一種定性值指定一個定量值,但是這種方式過於靈活,增加了調參的工作。通常使用啞編碼的方式將定性特征轉化為定量特征:假設有N種定性值,則將這一個特征擴展為N種特征,當原始特征值為第i種定性值時,第i個擴展特征賦值為1,其他擴展特征賦值為0,啞編碼的方式相比直接指定的方式,不用增加調參的工作,對於線性模型來說,使用啞編碼后的特征可達到非線性的效果
- 4,存在缺失值:缺失值需要補充
- 5,信息利用率低:不同的機器學習算法和模型對數據中信息的利用是不同的,之前提到在線性模型中,使用對定性特征啞編碼可以達到非線性的效果。類似的,對於定量變量多項式化,或者進行其他的轉換,都能達到非線性的效果。
我們使用sklearn中的preprocessing庫來進行數據預處理,可以覆蓋以上問題的解決方案。
無量綱化使不同規格的數據轉換到同一規則。常見的無量綱化方法有標准化和區間縮放法。標准化的前提是特征值服從正態分布,標准化后,其轉換成標准正態分布。區間縮放法利用了邊界值信息,將特征的取值區間縮放到某個特點的范圍,例如[0,1]等。
2.2.1 標准化
標准化需要計算特征的均值和標准差,公式表達為:
使用preprocessing庫的StandardScaler類對數據進行標准化的代碼如下:
from sklearn.preprocessing import StandardScaler #標准化,返回值為標准化后的數據 StandardScaler().fit_transform(iris.data)
StandardScler計算訓練集的平均值和標准差,以便測試數據及使用相同的變換,變換后的各維特征有0均值,單位方差(也叫z-score規范化),計算方式是將特征值減去均值,除以標准差。
fit 用於計算訓練數據的均值和方差,后面就會使用均值和方差來轉換訓練數據
fit_transform 不僅計算訓練數據的均值和方差,還會用計算出來的均值和方差來轉換訓練數據,從而把數據轉化成標准的正態分布。
transform 很顯然,這只是進行轉換,只是把訓練數據轉換成標准的正態分布。
為什么要標准化?
通常情況下是為了消除量綱的影響。譬如一個百分制的變量與一個5分值的變量在一起怎么比較呢?只有通過數據標准化,都把他們標准到同一個標准時才具有可比性,一般標准化采用的是Z標准化,即均值為0,方差為1,當然也有其他標准化,比如0-1 標准化等等,可根據自己的數據分布情況和模型來選擇。
標准化適用情況
看模型是否具有伸縮不變性。
不是所有的模型都一定需要標准化,有些模型對量綱不同的數據比較敏感,譬如SVM等。當各個維度進行不均勻伸縮后,最優解與原來不等價,這樣的模型,除非原始數據的分布范圍本來就不叫接近,否則必須進行標准化,以免模型參數被分布范圍較大或較小的數據主導。但是如果模型在各個維度進行不均勻伸縮后,最優解與原來等價,例如logistic regression等,對於這樣的模型,是否標准化理論上不會改變最優解。但是,由於實際求解往往使用迭代算法,如果目標函數的形狀太“扁”,迭代算法可能收斂地很慢甚至不收斂,所以對於具有伸縮不變性的模型,最好也進行數據標准化。
2.2.2 區間縮放法(最小-最大規范化)
區間縮放法的思路有很多,常見的一種為利用兩個極值進行縮放,公式表達為:
使用preproccessing庫的MinMaxScaler類對數據進行區間縮放的代碼如下:
from sklearn.preprocessing import MinMaxScaler #區間縮放,返回值為縮放到[0, 1]區間的數據 MinMaxScaler().fit_transform(iris.data)
區間縮放是對原始數據進行線性變換,變換到[0,1] 區間(當然也可以是其他固定最小最大值的區間)
2.2.3 正則化(normalize)
機器學習中,如果參數過多,模型過於復雜,容易造成過擬合(overfit)。即模型在訓練樣本數據上表現的很好,但在實際測試樣本上表現的較差,不具有良好的泛化能力,為了避免過擬合,最常用的一種方法是使用正則化,例如L1和L2正則化。
正則化的思想是:首先求出樣本的P范數,然后該樣本的所有元素都要除以該范數,這樣使得最終每個樣本的范數都是1,規范化(Normalization)是將不同變化范圍的值映射到相同的固定范圍,常見的是[0,1],也稱為歸一化。
如下例子,將每個樣本變換成unit norm。
x=np.array([[1.,-1.,2.], [2.,0.,0.], [0.,1.,-1.]]) x_normalized=preprocessing.normalize(x,norm='l2') print(x_normalized) # 可以使用processing.Normalizer()類實現對訓練集和測試集的擬合和轉換 normalizer=preprocessing.Normalizer().fit(x) print(normalizer) normalizer.transform(x)
2.3 對定量特征二值化
定量特征二值化的核心在於設定一個閾值,大於閾值的賦值為1,小於等於閾值的賦值為0,公式如下:
使用preprocessing庫的Binarizer類對數據進行二值化的代碼如下:
from sklearn.preprocessing import Binarizer #二值化,閾值設置為3,返回值為二值化后的數據 Binarizer(threshold=3).fit_transform(iris.data)
給定閾值,將特征轉化為0/1,最主要的是確定閾值設置。
2.4 對定性特征啞編碼
由於IRIS數據集的特征皆為定量特征,故使用其目標值進行啞編碼(實際上是不需要的)。使用preprocessing庫的OneHotEncoder類對數據進行啞編碼的代碼如下:
from sklearn.preprocessing import OneHotEncoder #啞編碼,對IRIS數據集的目標值,返回值為啞編碼后的數據 OneHotEncoder().fit_transform(iris.target.reshape((-1,1)))
One-hot編碼是使一種對離散特征值的編碼方式,在LR模型中常用到,用於給線性模型增加非線性能力。
2.5 缺失值計算
缺失值是指粗糙數據中由於缺少信息而造成的數據的聚類,分組,刪除或者截斷。它指的是現有數據集中某個或者某些屬性的值時不完全的。
缺失值在實際數據中是不可避免的問題,有的人看到缺失值就直接刪除了,有的人直接賦予0值或者某一個特殊的值,那么到底該如何處理呢?對於不同的數據場景應該采取不同的策略,首先應該判斷缺失值的分布情況。
當缺失值如果占了95%以上,可以直接去掉這個維度的數據,直接刪除會對后面的算法跑的結果造成不好的影響,我們常用的方法如下:
2.5.1 刪除缺失值
如果一個樣本或者變量中所包含的缺失值超過一定的比例,比如超過樣本或者變量的一半,此時這個樣本或者變量所含有的信息是有限的,如果我們強行對數據進行填充處理,可能會加入過大的人工信息,導致建模效果大打折扣,這種情況下,我們一般選擇從數據中剔除整個樣本或者變量,即刪除缺失值。
2.5.2 缺失值填充
-
隨機填充法
從字面上理解就是找一個隨機數,對缺失值進行填充,這種方法沒有考慮任何的數據特性,填充后可能還是會出現異常值等情況。譬如將缺失值使用“Unknown”等填充,但是效果不一定好,因為算法可能會把他識別稱為一個新的類別。一般情況下不建議使用。
-
均值填充法
尋找與缺失值變量相關性最大的那個變量把數據分成幾個組,然后分別計算每個組的均值,然后把均值填入缺失的位置作為它的值,如果找不到相關性較好的變量,也可以統計變量已有數據的均值,然后把它填入缺失位置。這種方法會在一定程度上改變數據的分布。
trainData['total_acc'].fillna(trainData['total_acc'].mean(), inplace=True)
-
最相似填充法
在數據集中找到一個與它最相似的樣本,然后用這個樣本的值對缺失值進行填充。
與均值填充法有點類似,尋找與缺失值變量(比如x)相關性最大的那個變量(比如y),然后按照變量y的值進行排序,然后得到相應的x的排序,最后用缺失值所在位置的前一個值來代替缺失值。
-
回歸填充法(建模法)
把缺失值變量作為一個目標變量y,把缺失值變量已有部分數據作為訓練集,尋找與其高度相關的變量x建立回歸方程,然后把缺失值變量y所在位置對應的x作為預測集,對缺失進行預測,用預測結果來代替缺失值。
可以用回歸,使用貝葉斯形式方法的基於推理的工具或者決策樹歸納確定。例如利用數據集中其他數據的屬性,可以構造一棵判斷樹,來預測缺失值的值。
-
k近鄰填充法
利用knn算法,選擇缺失值的最近k個近鄰點,然后根據缺失值所在的點離這幾個點距離的遠近進行加權平均來估計缺失值。
-
多重插補法
通過變量之間的關系對缺失數據進行預測,利用蒙特卡洛方法生成多個完整的數據集,在對這些數據集進行分析,最后對分析結果進行匯總處理
-
中位數填充法
trainData['total_acc'].fillna(trainData['total_acc'].median(), inplace=True)
2.5.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 Imputer() 處理丟失值
各屬性必須是數值。
from sklearn.preprocessing import Imputer # 指定用何值替換丟失的值,此處為中位數 imputer = Imputer(strategy="median") # 使實例適應數據 imputer.fit(housing_num) # 結果在statistics_ 變量中 imputer.statistics_ # 替換 X = imputer.transform(housing_num) housing_tr = pd.DataFrame(X, columns=housing_num.columns, index = list(housing.index.values)) # 預覽 housing_tr.loc[sample_incomplete_rows.index.values]
2.6 數據變換
常用的數據變換有基於多項式的,基於指數函數的,基於對數函數的,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.7 異常值處理
異常值我們通常也稱為“離群點”,我們在實際項目中拿到的數據往往有不少異常數據,有時候不篩選出這些異常數據很可能讓我們后面的數據分析模型有很大的偏差。所以我們應該如何篩選出異常特征樣本呢?
-
簡單的統計分析
拿到數據可以對數據進行一個簡單的描述性統計分析,譬如最大值最小值可以用來判斷這個變量的取值是否超過了合理的范圍,如客戶的年齡為-20歲或者125歲,顯然是不合理的,為異常值。
在python中可以直接使用pandas的describe():
>>> import pandas as pd >>> data = pd.read_table("test.tsv",header = None) >>> data.describe() 0 1 count 743.000000 735.000000 mean 372.000000 1962.165986 std 214.629914 860.720997 min 1.000000 472.000000 25% 186.500000 1391.000000 50% 372.000000 1764.000000 75% 557.500000 2217.500000 max 743.000000 5906.000000
-
截斷法
數據預處理第一步,通常是對異常值的處理。“截斷”方法具體步驟為:首先,求一次過程中原始數據的上四分位值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
可能正確的代碼如下:
def data_process(data): # 百分位數是統計中使用的度量 表示小於這個值的觀察值占某個百分比 q 要計算的百分位數,在 0 ~ 100 之間 a = np.percentile(data, 75) i = 0 j = len(data) - 1 while data[i] < a or data[j] < a: if data[i] < a: i += 1 if data[j] < a: j -= 1 if i == j: break return data[i:j + 1] def clean(data): q3 = np.percentile(data,75) q1 = np.percentile(data,25) up = q3 + 1.5 * (q3 - q1) down = q1 - 1.5 * (q3 - q1) data = data[(data < up) & (data > down)] return data
假設數據集時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)。因為馬氏距離不受量綱的影響,而且在多元條件下,馬氏距離還考慮了變量之間的相關性,這使得它優於歐氏距離。
-
異常點檢測方法
主要是使用IForest 或者One Class SVM ,使用異常點檢測的機器學習算法來過濾所有的異常點。
-
3∂原則
-
箱型圖分析
-
基於模型檢測
-
基於距離
-
基於密度
-
基於聚類
2.8 處理不平衡數據
我們做分類算法訓練時,如果訓練集里的各個類別的樣本數量不是大約相同的比例,就需要處理樣本不平衡問題,也許你會說,不處理會怎么樣呢?如果不處理,那么擬合出來的模型對於訓練集中少樣本的類別泛化能力會很差。舉個例子,我們是一個二分類問題,如果訓練集里A類樣本占90%,B類樣本占10%。而測試集里A類別樣本占50%。B類樣本占50%,如果不考慮類別不平衡問題,訓練出來的模型對於類別B的預測准確率會很低,甚至低於50%。
如何解決這個問題呢?一般方法是:權重法或者采樣法
權重法是比較簡單的方法,我們可以對訓練集里的每個類別加一個權重class weight。如果該類別的樣本數多,那么它的權重就低,反之則權重就高。如果更細致點,我們還可以對每個樣本加權重sample weight ,思路和類別權重也是一樣,即樣本數多的類別樣本權重低,反之樣本權重高。sklearn中,絕大多數算法都有class weight 和sample weight可以使用。
如果權重法做了以后發現預測效果還不好,可以考慮采樣法。
采樣法也有兩種思路,一種是對類別樣本數多的樣本做子采樣,比如訓練集里A類別樣本占90%,B類樣本占10%,那么我們可以對A類的樣本子采樣,知道子采樣得到的A類樣本數和B類別現有樣本一致為止。這樣我們就只用子采樣得到的A 類樣本數和B類現有樣本一起做訓練集擬合模型。第二種思路是對類別樣本數少的樣本做過采樣,還是上面的例子,我們對B類別的樣本做過采樣,知道過采樣得到的B類別樣本數加上B類別原來樣本一起和A類樣本數一致,最后再去擬合模型。
上述兩種常用的采樣法很簡單,但是都有個問題,就是采樣后改變了訓練集的分布,可能導致泛化能力差。所以有的算法就通過其他方法來避免這個問題,比如SMOTE算法通過人工合成的方法來生成少類別的樣本。方法也很簡單,對於某個缺少樣本的類別,它會隨機找出幾個該類別的樣本,再找出最靠近這些樣本的若干個該類別樣本,組成一個候選合成集合,然后在這個集合中不停的選擇距離較近的兩個樣本,在這兩個樣本之間,比如中點,構造一個新的該類別樣本。舉個例子,比如該類別的候選合成集合有兩個樣本(x1 , y ) , (x2 , y),那么SMOTE采樣后,可以得到一個新的訓練樣本 ((x1+x2)/2 , y),通過這種方法,我們可以得到不改變訓練集分布的新樣本,讓訓練集中各個類別的樣本數趨於平衡,我們可以用imbalance-learn這個Python庫中的SMOTEENN類來做SMOTE采樣。
2.9,噪音處理
噪音,是被測量變量的隨機誤差或方差。我們在上文中提到過異常點(離群點),那么離群點和噪音是不是一回事呢?我們知道,觀測量(Measurement) = 真實數據(True Data) + 噪聲 (Noise)。離群點(Outlier)屬於觀測量,既有可能是真實數據產生的,也有可能是噪聲帶來的,但是總的來說是和大部分觀測量之間有明顯不同的觀測值。。噪音包括錯誤值或偏離期望的孤立點值,但也不能說噪聲點包含離群點,雖然大部分數據挖掘方法都將離群點視為噪聲或異常而丟棄。然而,在一些應用(例如:欺詐檢測),會針對離群點做離群點分析或異常挖掘。而且有些點在局部是屬於離群點,但從全局看是正常的。
那么對於噪音,我們應該如何處理呢?有以下幾種方法:
2.9.1,分箱法
- 用箱均值光滑:箱中每一個值被箱中的平均值替換。
- 用箱中位數平滑:箱中的每一個值被箱中的中位數替換。
- 用箱邊界平滑:箱中的最大和最小值同樣被視為邊界。箱中的每一個值被最近的邊界值替換。
一般而言,寬度越大,光滑效果越明顯。箱也可以是等寬的,其中每個箱值的區間范圍是個常量。分箱也可以作為一種離散化技術使用.
2.9.2,回歸法
可以用一個函數擬合數據來光滑數據。線性回歸涉及找出擬合兩個屬性(或變量)的“最佳”直線,使得一個屬性能夠預測另一個。多線性回歸是線性回歸的擴展,它涉及多於兩個屬性,並且數據擬合到一個多維面。使用回歸,找出適合數據的數學方程式,能夠幫助消除噪聲。
2.10 去重處理
以DataFrame數據格式為例:
#創建數據,data里包含重復數據 >>> data = pd.DataFrame({'v1':['a']*5+['b']* 4,'v2':[1,2,2,2,3,4,4,5,3]}) >>> data v1 v2 0 a 1 1 a 2 2 a 2 3 a 2 4 a 3 5 b 4 6 b 4 7 b 5 8 b 3 #DataFrame的duplicated方法返回一個布爾型Series,表示各行是否是重復行 >>> data.duplicated() 0 False 1 False 2 True 3 True 4 False 5 False 6 True 7 False 8 False dtype: bool #drop_duplicates方法用於返回一個移除了重復行的DataFrame >>> data.drop_duplicates() v1 v2 0 a 1 1 a 2 4 a 3 5 b 4 7 b 5 8 b 3 #這兩個方法默認會判斷全部列,你也可以指定部分列進行重復項判斷。假設你還有一列值,且只希望根據v1列過濾重復項: >>> data['v3']=range(9) >>> data v1 v2 v3 0 a 1 0 1 a 2 1 2 a 2 2 3 a 2 3 4 a 3 4 5 b 4 5 6 b 4 6 7 b 5 7 8 b 3 8 >>> data.drop_duplicates(['v1']) v1 v2 v3 0 a 1 0 5 b 4 5 #duplicated和drop_duplicates默認保留的是第一個出現的值組合。傳入take_last=True則保留最后一個: >>> data.drop_duplicates(['v1','v2'],take_last = True) v1 v2 v3 0 a 1 0 3 a 2 3 4 a 3 4 6 b 4 6 7 b 5 7 8 b 3 8
如果數據是列表格式,有以下幾種方法可以刪除:
list0=['b','c', 'd','b','c','a','a'] 方法1:使用set() list1=sorted(set(list0),key=list0.index) # sorted output print( list1) 方法2:使用 {}.fromkeys().keys() list2={}.fromkeys(list0).keys() print(list2) 方法3:set()+sort() list3=list(set(list0)) list3.sort(key=list0.index) print(list3) 方法4:迭代 list4=[] for i in list0: if not i in list4: list4.append(i) print(list4) 方法5:排序后比較相鄰2個元素的數據,重復的刪除 def sortlist(list0): list0.sort() last=list0[-1] for i in range(len(list0)-2,-1,-1): if list0[i]==last: list0.remove(list0[i]) else: last=list0[i] return list0 print(sortlist(list0))
2.11 回顧
3 特征選擇
當數據預處理完成后,我們需要選擇有意義的特征輸入機器學習的算法和模型進行訓練。通常來說,從兩個方面考慮來選擇特征:
- 特征是否發散(方差篩選):如果一個特征不發散,例如方差接近於0,也就是說樣本在這個特征上基本沒有差異,這個特征對樣本的區分並沒有什么用。方差越大,我們可以認為它是比較有用的。在實際應用中,我們會指定一個方差的閾值,當方差小於這個閾值的特征會被我們篩掉,sklearn中的Variable Threshold類可以很方便的完成這個工作。
- 特征與目標的相關性:這點比較明顯,與目標相關性高的特征,應當優先選擇。除方差法外,我們介紹的其他方法均從相關性考慮。
根據特征選擇的形式又可以將特征選擇方法分為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)
線性判別分析原理及其Python實現博文:可以點擊這里
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化。
知識擴展:Python忽略warning警告錯誤
Python開發中經常遇到報錯的情況,但是warning通常不影響程序的運行,而且有時候特別討厭,下面我們來說下如何忽略warning的錯誤。
在說忽略warning之前,我們先說下如何主動產生warning錯誤,這里用到warnings模塊,看如下代碼:
import warnings def fxn(): warnings.warn("deprecated", DeprecationWarning) with warnings.catch_warnings(): warnings.simplefilter("ignore") fxn()
這樣就產生了warning錯誤
那么如何來控制警告錯誤的輸出呢?很簡單
import warnings warnings.filterwarnings('ignore')
這樣就忽略了警告錯誤的輸出。
如何忽略命令行下警告輸出呢?
python -W ignore yourscript.py
參考資料:http://www.cnblogs.com/jasonfreak/p/5448462.html
http://www.cnblogs.com/jasonfreak/p/5448385.html
https://www.cnblogs.com/pinard/p/9093890.html
(在此做筆記的目的是學習,並掌握特征工程,不喜勿噴,謝謝)