優(yōu)雅高效地數(shù)據(jù)挖掘——基于Python的sklearn
目錄
前言
1. 關于DataFrameMapper
2. 用DataFrameMapper做特征工程
2.2. 單列變換
2.3. 多列變換
2.3.1. 多列各自用同樣的變換
2.3.2. 多列整體變換
2.4. 對付稀疏變量
2.5. 保留指定列
2.6. 自定義列變換
2.7. 小小的總結(jié)
3. 實戰(zhàn)
3.1. 數(shù)據(jù)探查
3.1.1. 缺失值處理
3.1.2. 長尾特征
3.2. 特征工程
3.2. 交叉驗證
3.3. 預測
4. 思考
5. 參考資料
打賞
Bonus
前言
在 數(shù)據(jù)挖掘 流程中,特征工程是極其重要的環(huán)節(jié),我們經(jīng)常要結(jié)合實際數(shù)據(jù),對某些類型的數(shù)據(jù)做特定變換,甚至多次變換,除了一些常見的基本變換(參考我之前寫的『數(shù)據(jù)挖掘比賽通用框架』)外,還有很多非主流的奇技淫巧。所以,盡管有 sklearn.pipeline 這樣的流水線模式,但依然滿足不了一顆愛折騰數(shù)據(jù)的心。好在,我找到了一個小眾但好用的庫——sklearn_pandas,能相對簡潔地進行特征工程,使其變得優(yōu)雅而高效。
目前這個項目還在維護,大家有什么想法可以到 sklearn_pandas 的 github 主頁提問題,以及獲取最新的版本。
1. 關于 DataFrameMapper
sklearn_pandas 起初是為了解決這樣一個問題:在 sklearn 的舊版本中,很多常見模塊(特征變換器、分類器等)對 pandas 的 DataFrame 類型不支持,必須先用 DataFrame 自帶的 .values、.as_matrix 之類的方法,將 DataFrame 類型轉(zhuǎn)換成 numpy 的 ndarray 類型,再輸入到 sklearn 的模塊中,這個過程略麻煩。因此 sklearn_pandas 提供了一個方便的轉(zhuǎn)換接口,省去自己轉(zhuǎn)換數(shù)據(jù)的過程。
但當我花了幾天時間探索了 sklearn_pandas 的庫及其跟 pandas、sklearn 相應模塊的聯(lián)系后,我發(fā)現(xiàn) sklearn 0.16.0 向后的版本對 DataFrame的兼容性越來越好,經(jīng)我實際測試,現(xiàn)在最新的 0.17.1 版本中, model、preprocessing等模塊的大部分函數(shù)已完全支持 DataFrame 類型的輸入,所以我認為:
sklearn_pandas 的重點不再是數(shù)據(jù)類型轉(zhuǎn)換,而是通過其自創(chuàng)的 DataFrameMapper 類,更簡潔地、把 sklearn 的 transformer 靈活地運用在 DataFrame 當中,甚至可以發(fā)揮你的聰明才智,將幾乎大部分特征變換在幾行代碼內(nèi)完成,而且一目了然。
sklearn_pandas 官方文檔提供的例子比較少,我看了下它的源碼,有以下重要發(fā)現(xiàn)
-
DataFrameMapper 繼承自 sklearn 的 BaseEstimator 和 TransformerMixin ,所以 DataFrameMapper 可以看做 sklearn 的 TransformerMixin 類,跟 sklearn 中的其他 Transformer 一樣,比如可以作為 Pipeline 的輸入?yún)?shù)
-
DataFrameMapper 內(nèi)部機制是先將指定的 D ataFrame 的列轉(zhuǎn)換成 ndarray 類型,再輸入到 sklearn 的相應 transformer 中
-
DataFrameMapper 接受的變換類型是 sklearn 的 transformer 類,因而除了 sklearn 中常見的變換 (標準化、正規(guī)化、二值化等等)還可以用 sklearn 的 FunctionTransformer 來進行自定義操作
本文先介紹下如何用 DataFrameMapper 類型進行特征工程,再將 skleanr_pandas、sklearn、pandas 這三個庫結(jié)合,應用到一個具體的數(shù)據(jù)挖掘案例中。
2. 用 DataFrameMapper 做特征工程
[注意]在正式進入本節(jié)前,建議先閱讀本人之前寫的『[scikit-learn]特征二值化編碼函數(shù)的一些坑』,了解 sklearn 和 pandas 常見的二值化編碼函數(shù)的特性和一些注意點。
若輸入數(shù)據(jù)的一行是一個樣本,一列是一個特征,那簡單的理解,『特征工程』就是列變換。本節(jié)將講解如何用 DataFrameMapper 結(jié)合 sklearn 的 Transformer 類,來進行列變換
首先import本文將會用到的所有類(默認已裝好 scikit-learn, pandas, sklearn_pandas 等庫)
import random
import sklearn
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# frameworks for ML
from sklearn_pandas import DataFrameMapper
from sklearn.pipeline import make_pipeline
from sklearn.cross_validation import cross_val_score
from sklearn.grid_search import GridSearchCV
# transformers for category variables
from sklearn.preprocessing import LabelBinarizer
from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
# transformers for numerical variables
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import Normalizer
# transformers for combined variables
from sklearn.decomposition import PCA
from sklearn.preprocessing import PolynomialFeatures
# user-defined transformers
from sklearn.preprocessing import FunctionTransformer
# classification models
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
# evaluation
from sklearn.metrics import scorer
我們以如下的數(shù)據(jù)為例

2.2. 單列變換
『單列』可以是 1-D array,也可以是 2-D array,為了迎合不同的 transformer,但最終輸出都是 2-D array,具體我們看以下例子

我們分別對這三列做了二值化編碼、最大最小值歸一化等,但要注意,OneHotEncoder接受的是 2-D array的輸入,其他是 1-D array,具體請參考我之前寫的『[scikit-learn]特征二值化編碼函數(shù)的一些坑』。上面代碼的運行結(jié)果如下

分別對應三種變換,前三列和后五列是pet和age的二值化編碼,第四列是age的最大最小值歸一化。
同樣,我們也可以將這些變換『級聯(lián)』起來(類似 sklearn 里的 pipeline ):

將 age 列先最大最小值歸一化,再標準化,輸出結(jié)果:
array([[ 0.20851441],
[ 1.87662973],
[-0.62554324],
[-0.62554324],
[-1.4596009 ],
[-0.62554324],
[ 1.04257207],
[ 0.20851441]])
2.3. 多列變換
除了上面的單列變換, DataFrameMapper 也能處理多列
2.3.1. 多列各自用同樣的變換
有時候我們要對很多列做同樣操作,比如二值化編碼、標準化歸一化等,也可以借助于 DataFrameMapper ,使得執(zhí)行更高效、代碼更簡潔。

這里同時對 age 和 salary 進行歸一化,結(jié)果如下

同樣,這些變換也可以級聯(lián)

2.3.2. 多列整體變換
多列變換時,除了分別對每列變換,我們有時還需要對某些列進行整體變換,比如 降維(PCA, LDA) 和 特征交叉等,也可以很便捷地借助 DataFrameMapper 實現(xiàn)

以上我們對 age 和 salary 列分別進行了 PCA 和生成二次項特征
2.4. 對付稀疏變量
(寫完此文后發(fā)現(xiàn)該功能并不是很work)
sklearn 中 OneHotEncoder 類和某些處理文本變量的類(比如 CountVectorizer )的默認輸出是 sparse 類型,而其他很多函數(shù)輸出是普通的 ndarray , 這就導致數(shù)據(jù)拼接時可能出錯。為了統(tǒng)一輸出, DataFrameMapper 提供 sparse 參數(shù)來設定輸出稀疏與否,默認是 False 。
2.5. 保留指定列
(穩(wěn)定版 1.1.0 中沒有此功能,development 版本中有 )
從上面的實驗中我們可以看到,對于我們指定的列, DataFrameMapper 將忠誠地執(zhí)行變換,對于未指定的列,則被拋棄。
而真實場景中,對于未指定的列,我們可能也需要做相應處理,所以DataFrameMapper提供default參數(shù)用于處理這類列:
False : 全部丟棄(默認)
None : 原封不動地保留
other transformer : 將 transformer 作用到所有剩余列上
2.6. 自定義列變換
不難發(fā)現(xiàn),上面我們利用 DataFrameMapper 所做的列變換,大多是調(diào)用 sklearn 中現(xiàn)有的模塊( OneHotEncoder,MinMaxEncoder , PCA 等),那如果遇到一些需要自己定義的變換,該怎么做呢?比如常見的對長尾特征做 log(x+1) 之類的變換?
對 sklearn 熟悉的同學開動一下腦筋,答案馬上就有了——那就是 FunctionTransformer ,該函數(shù)的具體參數(shù)細節(jié)可參考 sklearn 的官方文檔,這里簡單給個例子

以上我們將 numpy 中的函數(shù) log1p (作用等同于 log(x+1 ))通過 FunctionTransformer 包裹成一個 sklearn 的 transformer 類,就能直接作用在不同列上啦。
動手能力強的同學還可以自己定義函數(shù),提示一下,用 numpy 的 ufunc ,這里就不贅述了,留給大家探索吧。
2.7. 小小的總結(jié)
基于以上內(nèi)容,以及我對 sklearn、pandas 相關函數(shù)的了解,我總結(jié)了以下對比表格:

?
至此, DataFrameMapper 的精髓已悉數(shù)傳授,想必大家已摩拳擦掌躍躍欲試了吧。OK,接下來進入實戰(zhàn)!
3. 實戰(zhàn)
在進入實戰(zhàn)前,先結(jié)合本人前作——『新手數(shù)據(jù)挖掘的幾個常見誤區(qū)』,簡單梳理一下數(shù)據(jù)挖掘的流程:
數(shù)據(jù)集被分成訓練集、驗證集、測試集,其中訓練集驗證集進行交叉驗證,用來確定最佳超參數(shù)。在最優(yōu)參數(shù)下,用整個訓練集+驗證集上進行模型訓練,最終在測試集看預測結(jié)果
我們這里結(jié)合一個實際的業(yè)務數(shù)據(jù)集來進行流程講解。首先加載數(shù)據(jù)集

數(shù)據(jù)集字段如下
這是一個常見的時間序列數(shù)據(jù)集,所以我們按照時間上的不同,將其劃分為訓練集(1~5月)和測試集(6月)

3.1. 數(shù)據(jù)探查
3.1.1. 缺失值處理
常見的缺失值處理手段有??????????
-
填充
-
丟棄
-
看做新類別
我們先簡單統(tǒng)計一下每個字段的空值率

這組數(shù)據(jù)比較理想,只有 Saler 字段是缺失的,所以我們只需要看下 Saler 和目標變量之間的關系

結(jié)果如下

以上結(jié)果表明空值對預測結(jié)果似乎有些影響,所以我們暫且將空值看做一類新的類別:

3.1.2. 長尾特征
長尾分布也是一種很常見的分布形態(tài),常見于數(shù)值類型的變量,最簡單的方法是用 log(x+1) 處理。在我們的數(shù)據(jù)集當中, Cost 這個字段便是數(shù)值類型,我們看下它的分布:


log 變化的效果還是不錯的,變量的分布相對均衡了。
3.2. 特征工程
通過上面簡單的數(shù)據(jù)探查,我們基本確定了缺失值和長尾特征的處理方法,其他類別變量我們可以做簡單的 One-hot 編碼,整個策略如下

在確定好特征工程的策略后,我們便可以上我們的大殺器—— DataFrameMapper 了,把所有的變換集成到一起

3.2. 交叉驗證
特征工程完畢后,便是交叉驗證。交叉驗證最重要的目的是為了尋找最優(yōu)的超參數(shù)(詳見本人前作『新手數(shù)據(jù)挖掘的幾個常見誤區(qū)』),通常我們會借助 sklearn 中的 KFold ,train_test_split, metric.score 等來進行交叉驗證,這里簡化起見,我們直接用 GridSearchCV, 但要注意的是, GridSearchCV 對 FunctionTransformer 類的支持不好,尤其有 lambda 函數(shù)時。所以為簡化起見,我們注釋掉上面使用了 lambda 函數(shù)的 FunctionTransformer 類(有興趣的同學可以嘗試拋棄 GridSearchCV ,手動進行交叉驗證)。
這里我們選用最常見的 LogisticRegression ,并調(diào)整它的超參數(shù)——正則系數(shù)C和正則方式 penalty (對此不熟悉的同學趕緊補下『邏輯回歸』的基礎知識)。同時如前面所講,我們用 pipeline 把特征工程和模型訓練都流程化,輸入到 GridSearchCV 中:

我們定義了三折交叉驗證(cv = 3),并選用準確率(scoring = ‘a(chǎn)ccuracy’)作為評估指標,運行結(jié)果如下:

最佳超參數(shù)是取 L2 正則,并且正則系數(shù)為 0.1
3.3. 預測
在得到模型的最優(yōu)超參數(shù)后,我們還需要在訓練集+驗證集上進行特征變換,并在最優(yōu)超參數(shù)下訓練模型,然后將相應特征變換和模型施加到測試集上,最后評估測試集結(jié)果。
而現(xiàn)在,這一系列流程被 GridSearchCV 大大簡化,只需兩行代碼即可搞定:

最后結(jié)果為 0.6166666666666667 ,即測試集上的分類準確率。
4. 思考
行文至此,洋洋灑灑千言,但依然只是完成了數(shù)據(jù)挖掘中最基本的流程,所做的特征變換和選用的模型也都非常簡單,所以還有很大的提升空間。
此處以下留兩個點,可以動手實踐,也歡迎在群里探討(群二維碼見第6節(jié)『Bonus』)
-
當選用的 model 不是 sklearn 中的模塊時(比如 xgboost),特征工程還可以用 sklearn_pandas 的 DataFrameMapper, 但 sklearn 中傻瓜模式的 pipeline 就無從作用了,必須自己搭建 cross validation 流程
-
bad case 也有分析的價值
-
從單模型到模型的 ensemble
5. 參考資料
-
sklearn_pandas 官方文檔、源碼及 github 上的 issues
-
pandas、scikit-learn 官方文檔
-
寒小陽的博客(http://blog.csdn.net/han_xiaoyang/article/details/49797143)
?相關鏈接
?>>>數(shù)據(jù)挖掘比賽通用框架
注:本文系數(shù)據(jù)觀轉(zhuǎn)自數(shù)據(jù)挖掘機養(yǎng)成記,作者穆文,版權(quán)著作權(quán)屬原創(chuàng)者所有。數(shù)據(jù)觀整理分享此文并非商業(yè)用途,以上內(nèi)容并不代表數(shù)據(jù)觀觀點,如涉著作權(quán)等事宜請聯(lián)系小編更正。數(shù)據(jù)觀微信公眾號(ID:cbdioreview) ,欲了解更多大數(shù)據(jù)行業(yè)相關資訊,可搜索數(shù)據(jù)觀(中國大數(shù)據(jù)產(chǎn)業(yè)觀察網(wǎng)www.cbdio.com)進入查看。

責任編輯:陳卓陽