JD-大資料競賽心得

英雄王不二發表於2017-05-07

特此宣告:引用白虎QQ群的吉更大神

京東競賽技術參考彙總


關鍵字
xgboost
高緯度特徵的特徵輪
交叉驗證
網格搜尋調參
單一模型
多模型融合
特徵工程
描述統計量

ML指代機器學**,DM指代資料探勘
DM流程通常分兩個階段
Step1. 資料清洗,資料格式調整
Step2. 特徵構建,模型選擇,效果評估
Step1.是整個流程中最耗時的,這點想必大家早有耳聞,DM界有句名言 garbage in ,garbage out ,可見清洗資料非常重要。從我的經驗看,這部分工作跟實際處理的業務問題關係很大,比較dirty,也沒有統一流程,所以本文重點放在Step2.
3. 前期準備
3.1. 資料變換
先把原始資料通過一定變換,變成通用的多列資料型別,作為ML模型的輸入,也就是上面的Step1。用X代表樣本及其特徵集合,y代表樣本標籤集合,整個流程如下:
 
3.2. 問題分類
根據標籤y的不同,可以把DM問題分為以下幾類:
二分類問題(這種問題在工業界最為常見,比如廣告點選率預估、推薦系統購買行為預測),此時y只有一維,取值只有兩個(比如0-1),每個樣本有唯一的標籤。比如預測廣告是否會被使用者點選;使用者是否會購買某種商品
多分類問題(比如微博使用者情感分析、使用者對理財產品偏好性分析),通常此時y有多維,每維代表一個類標籤,取值只有兩個(比如0-1),每個樣本有唯一的標籤;當然,y也可以只有一維,取值有多個,每個值代表一個類標籤。比如通過微博分析出使用者情感屬於喜怒哀樂等哪類;將理財產品的使用者群體分為偏好型/溫和型/厭惡型
多標籤問題(比如音樂的標籤劃分),y有多維,跟多分類的區別在於,樣本可以同時屬於多個標籤。作為一枚鋼琴愛好者,這裡以鋼琴作品舉例,假設標籤集合為{獨奏,協奏,浪漫主義,印象主義},最愛之一的德彪西「月光」無疑屬於{獨奏,印象主義},朗總成名作柴一則可歸為{協奏,浪漫主義},雲迪家喻戶曉的肖邦夜曲是{獨奏,浪漫主義},而中國特色的「保衛黃河」可歸為{協奏}
單迴歸問題(比如股價預測),y只有一維,取值為連續值。比如預測阿里明天的股價
多回歸問題(比如天氣預測),y有多維,取值連續。比如預測明天的氣溫、空氣溼度、降雨量
3.3. 評價指標
預測結果的好壞需要用一些指標來衡量,通常不同型別的DM問題有不同的評價指標。對於二分類問題,很多時候類別本身不均衡(比如正樣本很多負樣本極少),所以我們通常用AUC值——即ROC曲線下的面積——來評價二分類結果;在多分類或者多標籤問題中,我們通常選取評價指標為交叉熵(cross-entropy)或者log損失(log loss);對於迴歸問題,則可以選用MSE(mean square error)
3.4. 工具
我跟原部落格作者一樣,提倡使用python解決DM問題,因為python的第三方庫非常齊全,以下是常見的、用於DM問題的python庫:
pandas: 仿照了R語言的資料結構、資料操作,一般用來做資料預處理,特徵工程,其DataFrame資料格式用起來相當便利
scikit-learn: 家喻戶曉的ML庫,各種聚類、分類、迴歸模型等,也可以用來做預處理
xgboost: 陳天奇大神的傑作,改進了傳統的GBDT模型,在底層用一些trick加速模型訓練,非常值得一試,可以取代其他ML庫裡的GBDT系列模型 (很早就聽說過這個碉堡的庫,但一直沒有上手實踐,實在汗顏…後面我會結合GBDT做特徵工程,實踐下效果,釋出到公眾號)
keras: 神經網路相關的庫,可以選擇基於tensorflow或theano,趕腳很強大,我也是剛接觸
matplotlib: 作圖必備,語言風格跟MATLAB很像,很好上手
tpdm: 我沒聽過,原作者提到的,感興趣的童鞋可以瞭解下
3.5. 開發環境
這裡我補充說一下python開發環境和上面幾個庫的安裝方法。首先我跟原作者一樣,因為追求自(裝)由(逼),所以不用python IDE(比如Anaconda, Pycharm),當然,裝IDE可能省很多事情,個人建議安裝Pycharm。然後我自己的python開發環境(純屬個人**慣,僅供參考):
windows: notepad++及其外掛nppexec/explorer,結合我昨天釋出的『一個神奇的指令碼,一鍵執行各類程式』,裡面的nppexec指令碼可一鍵執行Python。以及linux風格的shell: git bash (git bash是基於msys的,跟cygwin略有不同)
mac: sublime及其外掛Package Control/anaconda,以及iTerm2,或者自帶的terminal。(sublime中import某些python庫,比如matplotlib/sklearn/tensorflow會出點bug,需要修改下環境變數啥的,遇到相關問題可以微信我,儘量幫你解決)
linux: vim(因為我一般在命令列模式下開發)。如果是介面linux,應該可以有其他選擇
另外,jupyter notebook(前身是ipython notebook)是個好東西,可以逐步執行python程式碼片段,不依賴於平臺,可在瀏覽器中開啟,非常適合學**過程中練手。
再說庫的安裝,首先強烈建議安裝64位python2.7,然後針對不同作業系統:
windows[不推薦]: 略蛋疼,64位的庫大多沒有官方版本,具體安裝方式見我之前寫過的一篇文章『在Windows下安裝64位Python及資料探勘相關庫』(後續我會完善該文,但只傳送給指定分組,具體見文末Bonus)。大多數庫的安裝都類似,但xgboost稍微複雜些,不能直接pip install,而是要裝VS來編譯其中相關檔案,再安裝,遇到問題可以微信我。另外tensroflow目前沒有windows版本
mac[推薦]: 最新的python2.7一般都自帶pip,所以裝好python後,直接在terminal中 pip install 相關庫就可以了,注意庫的依賴關係,一般先安裝numpy,scipy,matplotlib,再裝其他庫
linux[推薦]: 基本跟mac類似
4. DM問題框架
終於到了最核心的部分,原作者總結了一個他參加各類DM比賽常用的ML流程圖,真是一圖勝千言
 
這裡我擅自補充一下,這張圖看著眼花繚亂,其實就兩點,這兩點也是DM比賽中最核心的兩點:
特徵工程(包括各種離散化、組合、選擇)
模型選擇、模型融合(即ensemble)
能把這兩點做好,實屬不易,但其實在工業界,特徵工程和模型融合是否需要做到極致,是要看具體問題的。有些業務的資料維度本身就很稀少,並不足以支撐龐大的特徵體系;有些業務需要很強的可解釋性(比如金融領域),於是很多模型不能直接用;有些業務則要系統的實時性和穩定性,過於複雜的ensemble雖然能提升一點指標,但也許得不償失。
上圖當中的粉色部分是最常用的一些步驟,簡單梳理一下:先確定DM問題的型別,然後對資料集劃分,接著對常見的數值變數和類別變數做相應處理,可以進行特徵選擇,最後選擇合適的模型做預測,評估模型並輸出結果。下面將詳細展開。
4.1. 問題定義
首先搞清楚要解決的問題屬於哪一類,結合上節所講,我們一般通過觀察y標籤類來定義DM問題的型別。
4.2. 資料集劃分
在明確了問題的分類後,我們將對資料集劃分成訓練集(Training Data)和驗證集(Validation Data)(補充:很多時候還要劃分出測試集(Test Data),先用訓練集驗證集的交叉驗證來尋找模型的最優超引數,模型調優完畢後,最終用測試集來評估模型最終效果,具體參考我之前在公眾號釋出的『新手資料探勘中的幾個常見誤區』第二節)。劃分方式如下:
 
這裡我用自己本地的一個小資料集(名為toy_data.txt)做展示,獲取方式見文末Bonus,載入以上小資料集的程式碼如下:
import pandas as pd
df = pd.read_csv("toy_data.txt",sep = "\t")
df.head()
執行結果:
 
最後一個欄位Label就是我們要預測的y,在我的資料集裡取值0或1,所以是一個二分類問題。
對於分類問題,要根據標籤來劃分資料集,比如每種標籤取樣多少,這樣才能保證訓練集跟驗證集的樣本標籤分佈接近,另外取樣方式也不限於隨機取樣,可以根據實際業務問題選擇合適的取樣方式。這裡我們可以藉助scikit-learn來實現分層的K折交叉驗證,程式碼如下
X = df.ix[:,0:-1]
y = df.ix[:,-1]
from sklearn.cross_validation import StratifiedKFold
kf = StratifiedKFold(y,3) # 三折交叉驗證
用以下程式碼驗證一下訓練集和驗證集中的正樣本的佔比:
idx_train, idx_valid = next(iter(kf))
print float(sum(y[idx_train]))/len(idx_train)
print float(sum(y[idx_valid]))/len(idx_valid)
結果為0.69713 0.69565,兩者非常接近。
注意,不太推薦使用iter(kf),這裡只是為了展示標籤分佈,具體我會在本文第五節『實戰』中介紹如何高效地使用交叉驗證。
如果是迴歸問題,則不存在分類問題中類別標籤分佈不均的情況,所以我們只需採用普通的K折交叉驗證即可:
from sklearn.cross_validation import KFold
kf = KFold()
4.3. 特徵工程
毫不誇張地說,特徵工程是DM重要的一環,也是決定DM比賽的關鍵因素。縱觀DM比賽,幾年間已由追求模型是否fancy轉向無盡的特徵工程,主要得益於越來越標準化的ML模型,以及更好的計算能力。
特徵工程可以做的很複雜很龐大,但受限於本人目前的水平,這裡只結合原部落格內容講解一些最基本(也是最經典)的處理方法。
4.3.1. 處理類別變數
類別變數(categorial data)是一種常見的變數,在我之前寫的『新手資料探勘中的幾個常見誤區』 一文的第三節中討論過為何要對類別變數編碼
在toy_data當中,欄位Continent, Country, Product, Brand, TreeID, Industry, Saler都可以看做是類別變數。處理類別變數一般是先標籤化,然後再二值化編碼。標籤化的目的是將欄位的原始值(如字串、不連續的數字等)轉換成連續的整數值,再對整數值二值化編碼,如果原始值是整數,則直接二值化即可
我們拿toy_data前幾個樣本的Continent欄位舉例,對其進行編碼:
mapper = skp.DataFrameMapper([
('Continent', sklearn.preprocessing.LabelBinarizer()),
])
tempX = df[['Continent']].head()
print tempX
print mapper.fit_transform(tempX.copy())
執行結果如下
 
可以看到,原來的一列Continent欄位變成了三列,分別代表[ 'AM', 'EP', 'LA' ],取值1表明是,取值0表明否。這就是常說的one-hot編碼。如果類別變數的取值是整數,則直接用sklearn.preprocessing.OneHotEncoder()即可,把上面程式碼中LabelBinarizer()替換掉
注意我們必須將對訓練集上的變換原封不動的作用到測試集,而不能重新對測試集的資料做變換(詳見我之前寫的『新手資料探勘中的幾個常見誤區』第一節)。
4.3.2. 處理數值變數
一般而言,數值變數不用做太多處理,只需做正規化(normalization)和標準化(standardization)即可,分別對應scikit-learn中的Normalizer和StandardScaler。不過對於稀疏變數,在做標準化的時候要注意,選擇不去均值。
其實數值型變數最好也進行離散化,離散手段從基本的等距離散法、按分隔點人為指定,到聚類、輸入樹模型等,手段很多,在此不詳細展開,我會在後續文章中提及。
4.3.3. 處理文字變數
文字在實際問題中很常見,比如使用者評論、新聞摘要、視訊彈幕等等。我們用的toy_data不包含文字變數,所以這裡我參考了scikit-learn的文件,一個小的corpus作為我們的訓練資料集。
corpus = [
'This is the first document.',
'This is the second second document.',
'And the third one.',
'Is this the first document?',
]
corpus有四句話,可以看做是四個樣本。接下來我們先用一個簡單的方法處理文字變數——統計corpus中每個詞出現次數,程式碼如下:
from sklearn.feature_extraction.text import CountVectorizer
vectorizer1 = CountVectorizer(min_df=1)
temp1 = vectorizer1.fit_transform(corpus)
print vectorizer1.get_feature_names()
print temp1.toarray() # temp1是sparse型別, 轉換成ndarray方便檢視
執行結果:
 
第一行是corpus中所有詞,下面的ndarray每行代表該詞在該樣本中出現次數,比如第2行第6列的2代表second這個詞在第二句話中出現了2次。一般我們不會直接用這個結果,而是會將每行歸一化之類。
這種處理方式簡單粗暴,沒有考慮詞與詞之間的關係。我們改進一下這個方法,除了考慮單個詞之外,還考慮corpus中成對出現的詞(類似NLP裡n-gram的 bi-gram,具體請自行Google),程式碼如下
vectorizer2 = CountVectorizer(ngram_range=(1, 2))
temp2 = vectorizer2.fit_transform(corpus)
print vectorizer2.get_feature_names()
print temp2.toarray()
執行結果:
 
然而,這還不夠,像a is this這類的助詞、介詞等,詞頻將非常高(在NLP中又叫停止詞 stop word),所以需要減小他們的權重。一種做法是,不再簡單統計該詞在文件中出現的詞頻,而且還要統計 出現該詞的文件的佔比,這在NLP中叫tfidf。說的有點繞,具體到我們的例子中可以寫成如下表示式:
某單詞x的tfidf = x在一個樣本中出現的次數/出現x的文件佔比
分子即tf,分母即1/idf,有時需要用log sqrt之類的函式作用在tf或者 1/idf上,以減弱某項的影響。同樣,我們可以:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer3 = TfidfVectorizer(ngram_range=(1, 2))
temp3 = vectorizer3.fit_transform(corpus)
print vectorizer3.get_feature_names()
print temp3.toarray()
執行結果:
 
仔細觀察不難發現,is this這類的停止詞在變換後數值變小了。
!!注意!!跟處理類別變數、數值變數一樣,我們在處理文字變數時,必須將訓練集上的變換方式原封不動地作用到驗證集或測試集上,而不能重新對驗證集或者測試集做變換。比如在得到上面的vectorizer3後,我們將其作用在一個新的樣本 ['a new sentence']上,程式碼如下
print vectorizer1.transform(['a new sentence']).toarray()
我們可以看到,結果是 [[0 0 0 0 0 0 0 0 0]],因為這個樣本里的三個詞從未出現在訓練集corpus中,這是正確的結果!
為了方便將變換作用在未來的測試集,我們可以先把vectorizer3用pickle儲存到本地,用的時候再load,儲存方式如下:
import cPickle as pickle
pickle.dump(vectorizer3, open('vectorizer3.pkl','w'))
用的時候再 vectorizer = pickle.load(open('vectorizer3.pkl','r'))即可。
4.3.4. 特徵融合
ToDo 區別對待稠密特徵和稀疏特徵,
4.3.4. 特徵降維
ToDo PCA等
4.3.5. 特徵選擇
ToDo
4.4. 模型選擇和模型融合
ToDo



時間序列和資料探勘 
前言
首先我們來看下一些常見的資料探勘場景:
廣告點選率預測: 根據使用者歷史上的瀏覽、點選、停留時間等行為,預測使用者會不會點選該廣告
推薦系統: 根據使用者歷史的購買記錄、點選行為,以及商品描述文字、使用者畫像等等一些系列特徵,預測使用者未來的購買需求
命名實體識別: NLP (自然語言處理) 的經典問題,識別出一句有序的文字里,特定類別的片語
股價預測: 預測某支股票或某些股票,未來一段時間的價格,或者粗糙一點——漲跌趨勢
影象識別: 比如人臉識別、醫療影象分析
視訊情感分析: 根據一段視訊(可以看做是一系列時間上連續的影象),分析出這個視訊表達的情感傾向
我們會發現,這些問題很多都跟『時間序列』有關(除了影象識別),並且也是資料探勘比賽的常見賽題,以天貓14年的推薦大賽、支付寶15年的資金流入流出預測為例,這倆比賽有非常完整的前十強團隊答辯ppt公佈,我們會發現,處理這些問題的選手中,主要分倆流派:機器學習流派和統計學流派。14年的天貓推薦大賽是機器學習流派佔主流:做特徵、訓練分類器、模型融合,把時間資訊完全融入到特徵中去;而15年的支付寶比賽是統計學流派佔上風:用各種 AR (auto regression) 模型,從時間維度上建模,直接考慮時間上的連續性。
這些看似迥異的做法,其實都可以歸結到一個大的理論中去,而這個理論,正是本文的核心。
基礎
首先,我認為所有『有監督學習』問題,本質上都是在建立一個函式對映 f ,可以用一個式子來刻畫:
f(X)->Y
這個式子是我自己想的,但其實只要有一定機器學習基礎的人,都很容易想到這一點。這裡的 X 是樣本的特徵,Y 是樣本的標籤,X 可以是向量、矩陣甚至多維矩陣,相應的,Y 可以是單個標籤,也可以是一組標籤。我們待會兒結合例項講講 X 和 Y 在具體情形下的具體形式
於是:
按照 Y 的取值是連續的,還是離散的,可以把有監督學習問題分為『迴歸』和『分類』,可以參考我的前作『分類和迴歸的本質』,給你一個不同的視角看待經典問題。
按照 Y 是單個標籤,還是前後有關聯的序列化標籤,可以把有監督學習問題分為『時間序列』和『非時間序列』問題,而本文今天的主角,就是這個『時間序列』
上面兩種劃分方式的兩兩組合,會產生四種情況,比如:時間序列的迴歸問題,非時間序列的分類問題,等等。這四種情況基本涵蓋了所有的機器學習模型,或者說,所有實際問題,都可以通過各種方式,最後轉化成這四種情況裡的一種。
其實,嚴格來講,『時間序列』這個說法並不準確,比如在 NLP 中,一句話的每個單片語成一個序列,但他們並不代表有時間標籤,只是有前後關係,所以這類問題其實叫『事件序列(event sequences)』。我們在本文當中為了簡單起見,將這種帶有前後順序關係的序列,統稱為『時間序列』,特此宣告。
正文
接下來,我們將正式探討『時間序列』問題的常見處理手段
從統計學的角度
處理時間序列最自然的方法,莫過於 AR 系列的模型 (ARIMA, ARMAX, …), 以及我之前在一篇很水的論文裡用過的一階馬爾可夫鏈模型。這類模型的的最大特點,就是直接從預測變數 Y 的角度來考慮問題,比如現在要預測 y(T),比如T 時刻的股價,那我們就會用 {y(1),…,y(t)…, y(T-1) } 這些歷史資訊,做一階差分、二階差分、均值、方差等等等,得到各種統計量(有點特徵工程的意思),然後來預測 y(T)。這種模型有兩個顯而易見的弊端:
只用到了預測變數本身,無法利用到其他資訊如影響股價的政策因素等(不過統計學裡已經有一些模型在 handle 這類問題了)
看似在時間維度上建立模型,但其實是用以往的記錄分別來預測 y(T), y(T+1),…, 並沒有考慮這些預測變數本身的時間相關性
能處理的歷史記錄時長有限
從經典機器學習的角度
依然是預測 y(T) ,這時候我們會對利用上所有的其他變數 X,比如上面說的,影響股價的政策因素等,而且與統計學的做派不同,我們也對歷史記錄提取不同的特徵,但我們並不 care 是哪類統計量,比如什麼一階差分啥的,也不太 care 統計指標,我們只管拼命做特徵,開腦洞做特徵,用 CNN 自動做特徵,反正就是特徵工程搞得飛起,然後得到一個龐大無比的 X,用來預測 y(T)。其實不難發現,這種做法,就是我們在上一節提到的『非時間序列問題』,即把一個本來是『時間序列』的問題,轉換成非時間序列問題。這種做法的好處是通過完善的特徵工程,我們能把過去歷史資訊儘可能多地建模在 X 裡,當然弊端也很明顯,跟 AR 系列模型一樣。
割裂了 y(T), y(T+1) 之間的時間相關性
處理的歷史記錄的時長有限
從RNN等模型的角度
先總結下,前面兩種處理『時間序列』的方法,看似雜亂無章,實則可以簡潔地納入我們上一節提到的公式
f(X)->Y
具體到單個樣本的預測,就是
f( X(T) ) -> Y(T)
比如,在統計學的方法中,那些從 {y(1),…,y(t)…, y(T-1) } 歷史記錄上提取的特徵,其實就是這裡的 X(T);而在經典機器學習方法中,我們腦洞開啟所做的各類特徵,依然可以歸結為—— X(T)。
所以問題就來了,既然這兩大類方法,都割裂了 y 之間的時間相關性,那有沒有一個『大一統』模型能建立 y 之間的相關性呢? 我們轉化成數學語言就是
f( X(1), X(2), …, X(T) ) -> Y(1), Y(2), …, Y(T)
這樣,上面的 f( X(T) ) -> Y(T) 就可以看做是它的一個特例,即當樣本只包含一個時間點的時候。
答案是肯定的,這樣的模型是存在的,那就是——RNN,HMM,CRF 等等,這類模型在建模時顯示地建立了 Y 之間的關係。但一般情況下,HMM、CRF 會有馬爾可夫性假設,直白地說就是假設 Y(T) 只跟 Y(T-1) 有關係,所以相比而言,從理論上看,RNN 更完美(注意,只是理論上講,具體效果還要看實際應用),而且,RNN 理論上能處理無限時長的時間序列,即上面的 T 可以無窮大,但因為訓練時梯度在時間維度上的連乘操作,RNN 存在梯度消失和爆炸的風險,取而代之的是 GRU, LSTM 等,這又是另一個話題了,在此不表。
好,下面我們用更淺顯明白的語言,來敘述這個『大一統』框架。這時候我們不得不搬出這張神圖了:


這張圖闡釋了 RNN 所能處理的所有可能情形,綠色框是 RNN 模型,紅色是 輸入 X, 藍色是預測目標 Y。我們重點關注第一個 one to one 和 最後一個many to many。不難發現,對於非時間序列模型,他們看待『時間序列』的角度是單一的,即他們建立的是針對樣本 (x(T),y(T))的對映,就是這裡的 one to one,而在時間序列模型裡,一個樣本天然的是 (sequenceX, sequenceY ),這裡的 sequenceX 就是上面公式裡的 X(1), X(2), …, X(T),同理 sequenceY 是 Y(1), Y(2), …, Y(T),對應的就是 many to many 的情形。
不過這裡要提一下兩個重要的補充
儘管理論上 RNN 可以建模無窮時間序列,可以處理不同時間長度的樣本,不需要像其他方法一樣,只能統計一個時間視窗內的歷史資訊,但在實際工程實現上,還是要儘量統一歷史時間步長的,否則將對程式設計造成很大麻煩,這點在 Keras, Tensorflow, Theano 等框架裡都有體現。
雖然把 統計學方法 和 經典機器學習方法 都納入了這個『大一統』框架,看做是一個特例,但其實,RNN 並不需要像他們一樣,做很複雜的特徵工程,因為這些工作早已隱含在神經網路的各個非線性模組的各種對映裡(這也是為什麼說 神經網路能自動提取特徵),並嵌入到了隱層輸出 h 中,作為下一時刻的輸入,所以下一個時刻的輸入 X(T) 可以不用做那麼多特徵工程,這正是神經網路吸引人的地方。
一點發散
寫到這兒的時候,我的腦子裡突然閃過研究生期間學的『預測控制』理論。預測控制就是利用以往的系統狀態、輸出訊號,來生成未來 N 步的控制率,但區別在於,這裡的模型是已知的線性或非線性模型,所以是一種『機理建模』,而用 RNN、RF、GBDT 等,則是完全的資料驅動的黑箱建模。
總結
雖然討論的是『時間序列』問題,但其實是藉助 RNN 模型的理念,把處理所有『有監督學習』問題的思路都理清了
當處理的是非時間序列問題時(比如人臉識別),收集到的是一系列樣本 (X, Y),只要建立 f(X) -> Y 的對映。X可以做特徵工程,也可以交由 CNN 這類模型自動學出來
當樣本是時間序列時,有兩種做法:
仿照非時間序列問題的處理方法,收集一系列樣本 (X(t),Y(t))建立 f(X(t))->Y(t) 的對映,但其實是『大一統』框架的一個特烈
更通用的『大一統』框架,則是從 RNN 的角度看待這個問題,建立 f( X(1), X(2), …, X(T) ) -> Y(1), Y(2), …, Y(T) 的對映
額外補充一點:在影象處理、語音識別中,輸入特徵的維度(畫素點、語音波形)之間的相關度很大,可以交給神經網路自動提取特徵,而在推薦系統、nlp等問題中,輸入特徵的維度(user的id、性別、年齡、詞的詞形)之間的相關性並不那麼強,所以神經網路提取特徵的效果,未必比得上人工特徵。我認為這就是為何深度學習最先在語音和影象領域開啟市場的主要原因。
十大誤區:
 1
  太關注訓練(Focus on Training)
  就像體育訓練中越來越注重實戰訓練,因為單純的封閉式訓練常常讓訓練時狀態神勇,比賽時卻一塌糊塗。
  實際上,只有樣本外資料上的模型評分結果才真正有用!(否則的話,直接用參照表好了!)
  例如:
癌症檢測(Cancer detection):MD Anderson的醫生和研究人員(1993)使用神經網路來進行癌症檢測,驚奇地發現,訓練時間越長(從幾天延長至數週),對訓練集的效能改善非常輕微,但在測試集上的效能卻明顯下降。
機器學習或電腦科學研究者常常試圖讓模型在已知資料上表現最優,這樣做的結果通常會導致過度擬合(overfit)。
  解決方法:
解決這個問題的典型方法是重抽樣(Re-Sampling)。重抽樣技術包括:bootstrap、cross-validation、jackknife、leave-one-out…等等。
 
  2
  只依賴一項技術(Rely on One Technique)
  這個錯誤和第10種錯誤有相通之處,請同時參照其解決方法。沒有對比也就沒有所謂的好壞,辯證法的思想在此體現無疑。
  “當小孩子手拿一把錘子時,整個世界看起來就是一枚釘子。”要想讓工作盡善盡美,就需要一套完整的工具箱。
  不要簡單地信賴你用單個方法分析的結果,至少要和傳統方法(比如線性迴歸或線性判別分析)做個比較。
  研究結果:
  按照《神經網路》期刊的統計,在過去3年來,只有1/6的文章中做到了上述兩點。也就是說,在獨立於訓練樣本之外的測試集上進行了開集測試,並與其它廣泛採用的方法進行了對比。
  解決方法:
使用一系列好的工具和方法。(每種工具或方法可能最多帶來5%~10%的改進)。
 
  3
  提錯了問題(Ask the Wrong Question)
  一般在分類演算法中都會給出分類精度作為衡量模型好壞的標準,但在實際專案中我們卻幾乎不看這個指標。為什麼?因為那不是我們關注的目標。
  (1)專案的目標:一定要鎖定正確的目標
  例如:
欺詐偵測(關注的是正例)(長途電話上的分析):不要試圖在一般的通話中把欺詐和非欺詐行為分類出來,重點應放在如何描述正常通話的特徵,然後據此發現異常通話行為。
  (2)模型的目標:讓計算機去做你希望它做的事
  大多數研究人員會沉迷於模型的收斂性來儘量降低誤差,這樣讓他們可以獲得數學上的美感。但更應該讓計算機做的事情應該是如何改善業務,而不是僅僅側重模型計算上的精度。
  4
  只靠資料來說話(Listen (only) to the Data)
  “讓資料說話”沒有錯,關鍵是還要記得另一句話:兼聽則明,偏聽則暗!如果資料+工具就可以解決問題的話,還要人做什麼呢?
  (1)投機取巧的資料:資料本身只能幫助分析人員找到什麼是顯著的結果,但它並不能告訴你結果是對還是錯。
  (2)經過設計的實驗:某些實驗設計中摻雜了人為的成分,這樣的實驗結果也常常不可信。
 
  5
  使用了未來的資訊(Accept Leaks from the Future)
  看似不可能,卻是實際中很容易犯的錯誤,特別是你面對成千上萬個變數的時候。認真、仔細、有條理是資料探勘人員的基本要求。
預報(Forecast)示例:預報芝加哥銀行在某天的利率,使用神經網路建模,模型的準確率達到95%。但在模型中卻使用了該利率作為輸入變數。
金融業中的預報示例:使用3日的移動平均來預報,但卻把移動平均的重點設在今天。
  解決方法:
要仔細檢視那些讓結果表現得異常好的變數,這些變數有可能是不應該使用,或者不應該直接使用的。
給資料加上時間,避免被誤用。
  6
  拋棄了不該忽略的案例(Discount Pesky Cases)
  到底是“寧為雞頭,不為鳳尾”,還是“大隱隱於市,小隱隱於野”?不同的人生態度可以有同樣精彩的人生,不同的資料也可能蘊含同樣重要的價值。
  異常值可能會導致錯誤的結果(比如價格中的小數點標錯了),但也可能是問題的答案(比如臭氧洞)。所以需要仔細檢查這些異常。
  研究中最讓激動的話語不是“啊哈!”,而是“這就有點奇怪了……”
  資料中的不一致性有可能會是解決問題的線索,深挖下去也許可以解決一個大的業務問題。
  例如:
  在直郵營銷中,在對家庭地址的合併和清洗過程中發現的資料不一致,反而可能是新的營銷機會。
  解決方法:
視覺化可以幫助你分析大量的假設是否成立。
 
  7
  輕信預測(Extrapolate)
  依然是辯證法中的觀點,事物都是不斷髮展變化的。人們常常在經驗不多的時候輕易得出一些結論。即便發現了一些反例,人們也不太願意放棄原先的想法。
  維度咒語:在低維度上的直覺,放在高維度空間中,常常是毫無意義的。
  解決方法:
進化論。沒有正確的結論,只有越來越準確的結論。
  8
  試圖回答所有問題(Answer Every Inquiry)
  有點像我爬山時鼓勵自己的一句話“我不知道什麼時候能登上山峰,但我知道爬一步就離終點近一步。”
  “不知道”是一種有意義的模型結果。
  模型也許無法100%準確回答問題,但至少可以幫我們估計出現某種結果的可能性。
  9
  隨便地進行抽樣(Sample Casually)
  (1)降低抽樣水平。例如,MD直郵公司進行響應預測分析,但發現資料集中的不響應客戶佔比太高(總共一百萬直郵客戶,其中超過99%的人未對營銷做出響應)。於是建模人員做了如下抽樣:把所有響應者放入樣本集,然後在所有不響應者中進行系統抽樣,即每隔10人抽一個放入樣本集,直到樣本集達到10萬人。但模型居然得出如下規則:凡是居住在Ketchikan、Wrangell和Ward Cove Alaska的人都會響應營銷。這顯然是有問題的結論。(問題就出在這種抽樣方法上,因為原始資料集已經按照郵政編碼排序,上面這三個地區中不響應者未能被抽取到樣本集中,故此得出了這種結論)。
  解決方法:“喝前搖一搖!”先打亂原始資料集中的順序,從而保證抽樣的隨機性。
  (2)提高抽樣水平。例如,在信用評分中,因為違約客戶的佔比一般都非常低,所以在建模時常常會人為調高違約客戶的佔比(比如把這些違約客戶的權重提高5倍)。建模中發現,隨著模型越來越複雜,判別違約客戶的準確率也越來越高,但對正常客戶的誤判率也隨之升高。(問題出在資料集的劃分上。在把原始資料集劃分為訓練集和測試集時,原始資料集中違約客戶的權重已經被提高過了)
  解決方法:
先進行資料集劃分,然後再提高訓練集中違約客戶的權重。
 
  10
  太相信最佳模型(Believe the Best Model)
  還是那句老話-“沒有最好,只有更好!”
  可解釋性並不一定總是必要的。看起來並不完全正確或者可以解釋的模型,有時也會有用。
  “最佳”模型中使用的一些變數,會分散人們太多的注意力。(不可解釋性有時也是一個優點)
  一般來說,很多變數看起來彼此都很相似,而最佳模型的結構看上去也千差萬別,無跡可循。但需注意的是,結構上相似並不意味著功能上也相似。
  解決方法:
把多個模型集裝起來可能會帶來更好更穩定的結果。
【1. 特徵變換】
(特別感謝 @寒小陽 @retanoj )


誤區:
對訓練集和測試集分別做變換,比如標準化、歸一化、降維等


正解:
此誤區是如此之習以為常,以至於已寫進 libsvm 的官方文件(http://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf)的某一節中:
 
事實上測試集的特徵變換,應該使用跟訓練集同樣的因子(均值、標準差、PCA的變換矩陣w等等),我個人的直觀理解是,這樣能保證變換後的測試集樣本與訓練集樣本處於同一個樣本空間,從而基於訓練集樣本空間的 model,在測試集上也 work(更深層的可能涉及 learning theory,希望能有高人作補充)


有個簡單的例子可以輔助理解:
現有總體樣本 x={-1,1, 7,9},對應標籤y={0,1,1,1},我們可以認為分類器為:
f(x>=0)=1
f(x<0)=0
若現令訓練集為 x={-1,1},y={0,1},要預測測試集 x={7,9}的標籤值
訓練集的均值=0,所以做去均值變換後還是 x={-1,1},我們基於訓練集能訓練出上述的分類器。此時對測試集做變換,如果我們採用訓練集的均值(=0),則 測試集沒變化,用上述分類器能準確預測出 y={1,1},但如果我們對測試集去除其自身的均值(=(7+9)/2=8),我們將得到變換後的測試集 x={-1,1},採用上述分類器,將得到 y={0,1},顯然預測失準




【2. Cross Validation】
(特別感謝 @寒小陽 @麵包包包包包包)


誤區:
CV (Cross Validation) 訓練出來的最優模型,可以直接用於線上預測
正解:
CV只是確定了模型的最優超引數(就是沒法通過模型學習、只能手動調整或者grid search之類的那類引數,像svm的gamma和c,kmeans的聚類數k、LDA的alpha和beta)
一旦 model 的最優超引數通過cv確定下來之後,還要再次對 model 用整個線下資料集做 training,然後將 model 用於線上預測
具體做法可以參考下圖
 


【3. Dummy Variables】
( 特別感謝 @Matrix  @chrispy  @被嚇壞的煜兒 )


誤區:
處理類別特徵的時候(比如類別:“color”={red, green, blue}),直接對不同類別賦予不同數值(比如:1,2,3),並把“季節”特徵直接扔到模型裡


正解:
對於無序類別特徵(比如“color”),本身不存在數值上的大小關係,所以最好不要直接對其賦值,而是將類別特徵“因子化”,變成“Dummy Varaibles”(這種處理手段也叫 one-hot),從一個特徵("color")變成三個0-1特徵(“是否red”,“是否green”, “是否blue”)
 
對於有序類別特徵,(比如我在支付寶處理的購買力模型,有個特徵是“城市等級”={1,2,3,4}),也可以用同樣的方法因子化。
這麼做的原因是什麼呢?因為對於大部分模型,尤其是線性模型,對數值類特徵都比較敏感,無法挖掘非線性關係。我們不妨以 LR 為例,假如我們給  color = {green, red, blue} 分別賦予 {1,2,3},而標籤是 {冷色,暖色},顯然對於 color 而言,這是個非線性對映, 數值為 1 和 3 時是冷色,2 是暖色,但 LR 訓練的結果,只能給 color 這個特徵賦予同一個權重 w,所以無法得到這種非線性關係,但若將 color 像上面提到的那樣因子化,那我們訓練出的 LR,可以給 red 賦予正向的權重, blue 和 green 賦予負向的權重,從而得到這種單個特徵上的非線性對映。
不過對於決策樹系列的模型,也許因子化就不十分必要了,因為決策樹能學出非線性分割面,同樣對 color 分 暖色、冷色的問題,也許決策樹能學西出:
if color<=1 
y=冷色
else if 1<color<=3
y=暖色
else
y=冷色
優雅高效地資料探勘:基於Python的sklearn_pandas庫
來源:資料探勘機養成記 時間:2016-08-31 14:17:19 作者:穆文
目錄
前言
1. 關於DataFrameMapper
2. 用DataFrameMapper做特徵工程
2.2. 單列變換
2.3. 多列變換
2.3.1. 多列各自用同樣的變換
2.3.2. 多列整體變換
2.4. 對付稀疏變數
2.5. 保留指定列
2.6. 自定義列變換
2.7. 小小的總結
3. 實戰
3.1. 資料探查
3.1.1. 缺失值處理
3.1.2. 長尾特徵
3.2. 特徵工程
3.2. 交叉驗證
3.3. 預測
4. 思考
5. 參考資料
打賞
Bonus
前言
在資料探勘流程中,特徵工程是極其重要的環節,我們經常要結合實際資料,對某些型別的資料做特定變換,甚至多次變換,除了一些常見的基本變換(參考我之前寫的『資料探勘比賽通用框架』)外,還有很多非主流的奇技淫巧。所以,儘管有sklearn.pipeline這樣的流水線模式,但依然滿足不了一顆愛折騰資料的心。好在,我找到了一個小眾但好用的庫——sklearn_pandas,能相對簡潔地進行特徵工程,使其變得優雅而高效。
目前這個專案還在維護,大家有什麼想法可以到 sklearn_pandas 的 github 主頁提問題,以及獲取最新的版本。
1. 關於DataFrameMapper
sklearn_pandas 起初是為了解決這樣一個問題:在 sklearn 的舊版本中,很多常見模組(特徵變換器、分類器等)對 pandas 的DataFrame型別不支援,必須先用DataFrame自帶的 .values、.as_matrix之類的方法,將DataFrame型別轉換成 numpy 的ndarray型別,再輸入到 sklearn 的模組中,這個過程略麻煩。因此 sklearn_pandas 提供了一個方便的轉換介面,省去自己轉換資料的過程。
但當我花了幾天時間探索了 sklearn_pandas 的庫及其跟 pandas、sklearn 相應模組的聯絡後,我發現 sklearn 0.16.0 向後的版本對 DataFrame的相容性越來越好,經我實際測試,現在最新的 0.17.1 版本中, model、preprocessing等模組的大部分函式已完全支援 DataFrame 型別的輸入,所以我認為:
sklearn_pandas 的重點不再是資料型別轉換,而是通過其自創的DataFrameMapper 類,更簡潔地、把 sklearn 的 transformer靈活地運用在 DataFrame 當中,甚至可以發揮你的聰明才智,將幾乎大部分特徵變換在幾行程式碼內完成,而且一目瞭然。
sklearn_pandas 官方文件提供的例子比較少,我看了下它的原始碼,有以下重要發現
DataFrameMapper 繼承自 sklearn 的 BaseEstimator 和 TransformerMixin ,所以 DataFrameMapper 可以看做 sklearn 的 TransformerMixin 類,跟 sklearn 中的其他 Transformer 一樣,比如可以作為 Pipeline 的輸入引數
DataFrameMapper 內部機制是先將指定的 DataFrame 的列轉換成 ndarray 型別,再輸入到 sklearn 的相應 transformer中
DataFrameMapper 接受的變換型別是 sklearn 的 transformer 類,因而除了 sklearn 中常見的變換 (標準化、正規化、二值化等等)還可以用 sklearn 的 FunctionTransformer 來進行自定義操作
本文先介紹下如何用DataFrameMapper型別進行特徵工程,再將 skleanr_pandas、sklearn、pandas 這三個庫結合,應用到一個具體的資料探勘案例中。
2. 用DataFrameMapper做特徵工程
[注意]在正式進入本節前,建議先閱讀本人之前寫的『[scikit-learn]特徵二值化編碼函式的一些坑』,瞭解 sklearn 和 pandas 常見的二值化編碼函式的特性和一些注意點。
若輸入資料的一行是一個樣本,一列是一個特徵,那簡單的理解,『特徵工程』就是列變換。本節將講解如何用DataFrameMapper結合 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
我們以如下的資料為例
 
 
2.2. 單列變換
『單列』可以是 1-D array,也可以是 2-D array,為了迎合不同的 transformer,但最終輸出都是 2-D array,具體我們看以下例子
 
 
我們分別對這三列做了二值化編碼、最大最小值歸一化等,但要注意,OneHotEncoder接受的是 2-D array的輸入,其他是 1-D array,具體請參考我之前寫的『[scikit-learn]特徵二值化編碼函式的一些坑』。上面程式碼的執行結果如下
 
 
分別對應三種變換,前三列和後五列是pet和age的二值化編碼,第四列是age的最大最小值歸一化。
同樣,我們也可以將這些變換『級聯』起來(類似 sklearn 裡的pipeline):
 
 
將age列先最大最小值歸一化,再標準化,輸出結果:
array([[ 0.20851441],
[ 1.87662973],
[-0.62554324],
[-0.62554324],
[-1.4596009 ],
[-0.62554324],
[ 1.04257207],
[ 0.20851441]])
2.3. 多列變換
除了上面的單列變換,DataFrameMapper也能處理多列
2.3.1. 多列各自用同樣的變換
有時候我們要對很多列做同樣操作,比如二值化編碼、標準化歸一化等,也可以藉助於DataFrameMapper,使得執行更高效、程式碼更簡潔。
 
 
這裡同時對age和salary進行歸一化,結果如下
 
 
同樣,這些變換也可以級聯
 
 
2.3.2. 多列整體變換
多列變換時,除了分別對每列變換,我們有時還需要對某些列進行整體變換,比如 降維(PCA, LDA) 和 特徵交叉等,也可以很便捷地藉助DataFrameMapper實現
 
 
以上我們對age和salary列分別進行了 PCA 和生成二次項特徵
2.4. 對付稀疏變數
(寫完此文後發現該功能並不是很work)
sklearn 中OneHotEncoder類和某些處理文字變數的類(比如CountVectorizer)的預設輸出是 sparse型別,而其他很多函式輸出是普通的 ndarray, 這就導致資料拼接時可能出錯。為了統一輸出,DataFrameMapper提供sparse引數來設定輸出稀疏與否,預設是False。
2.5. 保留指定列
(穩定版 1.1.0 中沒有此功能,development 版本中有 )
從上面的實驗中我們可以看到,對於我們指定的列,DataFrameMapper將忠誠地執行變換,對於未指定的列,則被拋棄。
而真實場景中,對於未指定的列,我們可能也需要做相應處理,所以DataFrameMapper提供default引數用於處理這類列:
False: 全部丟棄(預設)
None: 原封不動地保留
other transformer: 將 transformer 作用到所有剩餘列上
2.6. 自定義列變換
不難發現,上面我們利用DataFrameMapper所做的列變換,大多是呼叫sklearn中現有的模組(OneHotEncoder,MinMaxEncoder, PCA 等),那如果遇到一些需要自己定義的變換,該怎麼做呢?比如常見的對長尾特徵做log(x+1)之類的變換?
對 sklearn 熟悉的同學開動一下腦筋,答案馬上就有了——那就是FunctionTransformer,該函式的具體引數細節可參考 sklearn 的官方文件,這裡簡單給個例子
 
 
以上我們將 numpy 中的函式log1p(作用等同於log(x+1))通過FunctionTransformer包裹成一個 sklearn 的transformer類,就能直接作用在不同列上啦。
動手能力強的同學還可以自己定義函式,提示一下,用 numpy 的ufunc,這裡就不贅述了,留給大家探索吧。
2.7. 小小的總結
基於以上內容,以及我對 sklearn、pandas 相關函式的瞭解,我總結了以下對比表格:
 
 
至此,DataFrameMapper 的精髓已悉數傳授,想必大家已摩拳擦掌躍躍欲試了吧。OK,接下來進入實戰!
3. 實戰
在進入實戰前,先結合本人前作——『新手資料探勘的幾個常見誤區』,簡單梳理一下資料探勘的流程:
資料集被分成訓練集、驗證集、測試集,其中訓練集驗證集進行交叉驗證,用來確定最佳超引數。在最優引數下,用整個訓練集+驗證集上進行模型訓練,最終在測試集看預測結果
我們這裡結合一個實際的業務資料集來進行流程講解。首先載入資料集
 
 
資料集欄位如下
這是一個常見的時間序列資料集,所以我們按照時間上的不同,將其劃分為訓練集(1~5月)和測試集(6月)
 
                                                                   
3.1. 資料探查
3.1.1. 缺失值處理
常見的缺失值處理手段有          
填充
丟棄
看做新類別
我們先簡單統計一下每個欄位的空值率
 
 
這組資料比較理想,只有Saler欄位是缺失的,所以我們只需要看下Saler和目標變數之間的關係
 
 
結果如下
 
 
以上結果表明空值對預測結果似乎有些影響,所以我們暫且將空值看做一類新的類別:
 
 
3.1.2. 長尾特徵
長尾分佈也是一種很常見的分佈形態,常見於數值型別的變數,最簡單的方法是用log(x+1)處理。在我們的資料集當中,Cost這個欄位便是數值型別,我們看下它的分佈:
 
 
log 變化的效果還是不錯的,變數的分佈相對均衡了。
3.2. 特徵工程
通過上面簡單的資料探查,我們基本確定了缺失值和長尾特徵的處理方法,其他類別變數我們可以做簡單的 One-hot 編碼,整個策略如下
 
 
在確定好特徵工程的策略後,我們便可以上我們的大殺器——DataFrameMapper了,把所有的變換整合到一起
 
 
3.2. 交叉驗證
特徵工程完畢後,便是交叉驗證。交叉驗證最重要的目的是為了尋找最優的超引數(詳見本人前作『新手資料探勘的幾個常見誤區』),通常我們會藉助 sklearn 中的KFold ,train_test_split, metric.score等來進行交叉驗證,這裡簡化起見,我們直接用 GridSearchCV,但要注意的是,GridSearchCV對FunctionTransformer類的支援不好,尤其有 lambda 函式時。所以為簡化起見,我們註釋掉上面使用了 lambda 函式的FunctionTransformer類(有興趣的同學可以嘗試拋棄GridSearchCV,手動進行交叉驗證)。
這裡我們選用最常見的LogisticRegression,並調整它的超引數——正則係數C和正則方式penalty(對此不熟悉的同學趕緊補下『邏輯迴歸』的基礎知識)。同時如前面所講,我們用pipeline把特徵工程和模型訓練都流程化,輸入到GridSearchCV中:
 
 
我們定義了三折交叉驗證(cv = 3),並選用準確率(scoring = ‘accuracy’)作為評估指標,執行結果如下:
 
 
最佳超引數是取 L2 正則,並且正則係數為 0.1
3.3. 預測
在得到模型的最優超引數後,我們還需要在訓練集+驗證集上進行特徵變換,並在最優超引數下訓練模型,然後將相應特徵變換和模型施加到測試集上,最後評估測試集結果。
而現在,這一系列流程被GridSearchCV大大簡化,只需兩行程式碼即可搞定:
 
 
最後結果為0.6166666666666667,即測試集上的分類準確率。
4. 思考
行文至此,洋洋灑灑千言,但依然只是完成了資料探勘中最基本的流程,所做的特徵變換和選用的模型也都非常簡單,所以還有很大的提升空間。
此處以下留兩個點,可以動手實踐,也歡迎在群裡探討(群二維碼見第6節『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)

相關文章