第3章 探索資料
本章會介紹一些技術,幫助你對一個銀行營銷電話的資料進行分類。你將學習以下主題:
·測試並比較模型
·樸素貝葉斯分類器
·將邏輯迴歸作為通用分類器使用
·將支援向量機用作分類引擎
·使用決策樹進行分類
·使用隨機森林預測訂閱者
·使用神經網路對呼叫進行分類
3.1導論
本章中,我們會對一個銀行的營銷電話進行分類,看看一個呼叫會不會帶來一個信用卡業務。本文引用《A Data-Driven Approach to Predict the Success of Bank Telemarketing》中的資料,這本書可在http://archive.ics.uci.edu/ml/datasets/Bank+Marketing上找到。
為了能在我們的模型中使用,我們轉換了資料。資料字典在/Data/Chapter03/bank_contacts_data_dict.txt。
3.2測試並比較模型
獨立安裝 pandas
>pip install pandas
獨立安裝 scikit-learn
>pip install scikit-learn
pandas讓計算你所用模型的效能指標變得極其容易。我們使用下面的程式碼衡量模型的能力(Codes資料夾根目錄下的helper.py檔案):
1 import sklearn.metrics as mt 2 3 def printModelSummary(actual, predicted): 4 ''' 5 Method to print out model summaries 6 ''' 7 print('Overall accuracy of the model is {0:.2f} percent'\ 8 .format( 9 (actual == predicted).sum() / \ 10 len(actual) * 100)) 11 print('Classification report: \n', 12 mt.classification_report(actual, predicted)) 13 print('Confusion matrix: \n', 14 mt.confusion_matrix(actual, predicted)) 15 print('ROC: ', mt.roc_auc_score(actual, predicted)) 16
原理
首先,我們從scikit-learn匯入了metrics模組。然後,我們輸出模型總體上的精確度。這是統計我們的模型與實際分類一致的次數((actual==predicted).sum()),再除以測試樣本的規模(len(actual)*100)得到的。這告訴了我們總體上成功的比例。如果獨立變數的分佈不對稱,那麼這個維度不能用來評估你的模型。
.confusion_matrix方法以一種很清晰的方式衡量我們的模型——矩陣的每一行代表著模型弄錯(或預測對)的次數:
/* Consusion matrix: {[10154 1816] [559,946]} */
第一行,很明顯一共是10154+1816=11970的實際觀測值,其屬於0這一類。其中,10154是正確預測的數目(我們把這種情況稱作true negative,因為這代表著電話並沒有帶來信用卡業務),1816是錯誤預測的數目(我們把這種情況稱作false positive,這類電話被我們的模型當成了“帶來了信用卡業務”,實際上並沒有)。
第二行展示了模型成功預測產出的次數:559次錯誤(稱作false negative,電話帶來了信用卡業務,而模型認為沒有),946次正確(稱作true positive,電話帶來了信用卡業務,與模型的結論一致)。
這些數值可用於計算一些指標。使用.classification_report(...)方法生成這些指標:
精確率(Precision)衡量的是,當樣本不是正類時,模型不將樣本預測為正類的能力。這是true positive(946)與所有預測為正類的數量(1816+946)的比值:這裡是0.34,相當低。也可以為負類計算相對應的值:10154/(10154+559)=0.95。總體的精確率是個體精確率的加權平均,權重就是支援度。支援度(Support)是樣本中各分類的實際數目。
召回率(Recall)可被看作模型找出所有正類的能力。這是真陽性對真陽性與假陰性之和的比值:946/(946+559)=0.63。同樣地,可以用真陰性比上真陰性和假陽性之和,得到類別0的召回率。總體的召回率是各類別召回率的加權平均,權重是支援度。
綜合評價指標(F1-score)是精確率和召回率的調和平均。即精確率和召回率乘積的兩倍比上它們的和。用這一個指標來衡量模型表現的好壞。
用於評估模型表現的最後一個指標是ROC曲線(Receiver Operating Characteristic)。ROC曲線將模型的表現視覺化(對分類的比例不敏感)。換句話說,這條曲線相當於以更多假陽性的代價換取更多真陽性的權衡。我們對ROC曲線下方的面積更感興趣。一般認為0.9到1的模型是優秀的,而0.5和0.6這種的則沒什麼價值——這種模型和拋硬幣做決定沒什麼差別。
在helper.py檔案中,我們放了一些後續章節常用的輔助過程。使用最多的應該就是timeit(...)裝飾器了。這個裝飾器可用來測量程式的執行時間:
def timeit(method): ''' A decorator to time how long it takes to estimate the models 一個用於估算模組所需時間的裝飾器 ''' def timed(*args, **kw): start = time.time() result = method(*args, **kw) end = time.time() print('The method {0} took {1:2.2f} sec to run.' \ .format(method.__name__, end-start)) return result return timed
這個裝飾器返回的是timed(...)方法,這個方法測量的是一個方法執行過程的結束與開始的時間戳之差。要使用一個裝飾器,我們將@timeit(或者@hlp.timeit)放在要測量的函式前面。timeit(...)方法唯一的引數,就是傳遞給timed(...)這個內部方法的method。timed(...)方法啟動計時器,執行傳遞進來的函式,再列印出用了多久。
Python中的函式,也是可以傳遞的物件,和其他物件(比如int)沒什麼差別。所以,我們可以將方法作為引數傳給另一個函式,並在內部使用。
關於(Python 3.7的)裝飾器,可參考http://thecodeship.com/patterns/guide-to-python-function-decorators/。
參考
強烈推薦Scikit文件中關於分類器各種指標的部分:
http://scikit-learn.org/stable/modules/model_evaluation.html。
3.3樸素貝葉斯分類器
獨立安裝 pandas
>pip install pandas
獨立安裝 scikit-learn
>pip install scikit-learn
樸素貝葉斯分類器是最簡單的分類技術之一。其理論基礎是貝葉斯定理,在一個事件發生的情況下,求另一事件發生的機率:
P(A|B)=P(B|A)P(A)/P(B)
也就是說,我們有電話及通話方的各種特徵(B),要計算電話帶來信用卡業務的機率(A)。這就等價於計算:觀測到的申請信用卡的頻率P(A),乘以過去同意辦理信用卡的通話方與電話具有這些特徵的頻率P(B|A),比上資料集中這些通話方與電話的頻率P(B)。
本節示例需要裝好pandas和scikit-learn。我們還要使用helper.py檔案,所以你需要NumPy和time模組。
helper.py檔案放在上一級目錄,我們需要將上一層目錄加到Python的環境變數中,Python會到這些路徑下尋找程式碼:
# this is needed to load helper from the parent folder import sys sys.path.append('..') # the rest of the imports import helper as hlp import pandas as pd import sklearn.naive_bayes as nb
使用pandas構造樸素貝葉斯分類器只花兩行程式碼。但到那一步之前得做點長一些的準備(classification_naiveBayes.py檔案):
1 # the file name of the dataset 2 r_filename = '../../Data/Chapter03/bank_contacts.csv' 3 4 5 # the file name of the dataset 6 r_filename = '../../Data/Chapter03/bank_contacts.csv' 7 8 # read the data 9 csv_read = pd.read_csv(r_filename) 10 11 # split the data into training(訓練集) and testing(測試集) 12 train_x, train_y, \ 13 test_x, test_y, \ 14 labels = hlp.split_data( 15 csv_read, y = 'credit_application') 16 17 # train the model 訓練模型 18 classifier = fitNaiveBayes((train_x, train_y)) 19 20 # classify the unseen data 21 predicted = classifier.predict(test_x) 22 23 # print out the results 24 hlp.printModelSummary(test_y, predicted) 25 26 @hlp.timeit 27 def fitNaiveBayes(data): 28 ''' 29 Build the Naive Bayes classifier 30 構造樸素貝葉斯分類器 31 ''' 32 # create the classifier object 構造一個分類 器物件 33 naiveBayes_classifier = nb.GaussianNB() 34 35 # fit the model 擬合模型 36 return naiveBayes_classifier.fit(data[0], data[1])
說明:
我們先從CSV檔案讀取資料,這一步大家應該很熟悉了(參見本書1.2節)。接著將資料拆成訓練集和測試集,維持自變數(x)和因變數(y)的分離(參見本書2.8節)。
helper.py指令碼的.split_data(...)方法將資料集拆成兩個子集:預設情況下,2/3的資料用於訓練,1/3的資料用於測試。這個方法是前一章方法略微複雜些的變體。有兩個必需的引數(按順序)是:data和y(因變數)。data引數應是pandas的DataFrame型別,y應傳這個DataFrame中某列的名字。你可以用x引數傳入一個列名的列表,作為指定的自變數(預設是選取除y外的所有列)(本章後面會詳細講這塊)。你也可以給test_size引數傳入一個0到1之間的數,以指定測試資料佔的比例。
拆分完資料集,我們就可以構造分類器了。當前技巧和下一個技巧中,我們將用@hlp.timeit裝飾器給不同的模型計時。
我們使用Scikit的.GaussianNB()方法構建樸素貝葉斯分類器。這個方法不用傳引數。使用.fit(...)方法擬合模型;要傳的第一個引數是自變數的集合,第二個引數是因變數的向量。一旦擬合,我們就向主程式返回分類器。模型就構建成功。
下面我們要測試模型。.predict(...)方法處理test_x資料集,將樣本分為兩個桶:一個是帶來信用卡申請的電話,一個是沒帶來申請的電話。
Scikit中不同模型遵循同樣的命名模式;這樣用起來就很方便。後續用到的其他模型都有.fit(...)和.predict(...)方法,這樣不用修改程式碼,也能很方便地測試不同的模型。
我們用之前介紹的printModelSummary(...)方法測試模型的表現(參考本書3.2節)。fitNaiveBayes(...)在平均情況下,(在我的機器上)花費0.03秒。
在你的機器上,程式執行的時間很可能不同。我們只將這個時間用作與後續介紹的方法進行比較的基準(你也該如此)。
樸素貝葉斯在很多機器學習的應用中都有運用。比如,如何分類文字:http://sebastianraschka.com/Articles/2014_naive_bayes_1.html。
3.4將邏輯迴歸作為通用分類器使用
獨立安裝 pandas
獨立安裝 StatsModels
>pip install StatsModels
/* Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Installing collected packages: patsy, StatsModels Successfully installed StatsModels-0.10.2 patsy-0.5.1 FINISHED */
邏輯迴歸可能是第二廣泛的迴歸模型(排線上性迴歸之後)。不過它也可用於解決分類問題。
要實踐本技巧,你需要裝好pandas和StatsModels。如果你使用的是Anaconda發行版Python,那麼這兩個模組都已預裝。我們引入StatsModels的兩個部分:
import statsmodels.api as sm import statsmodels.genmod.families.links as fm
前一個允許我們選取模型,後一個允許我們指定連結函式。
類似前一招的模式,我們先匯入需要的模組,讀入資料,拆分資料集。然後呼叫fitLogisticRegression(...)方法來預測模型(classification_logistic.py檔案):
@hlp.timeit def fitLogisticRegression(data): ''' Build the logistic regression classifier 構建邏輯迴歸分類器 ''' # create the classifier object 建立分類器物件 logistic_classifier = sm.GLM(data[1], data[0], family=sm.families.Binomial(link=fm.logit)) # fit the data擬合資料 return logistic_classifier.fit()
StatsModels模組提供了.GLM(...)方法。GLM代表廣義線性模型(Generalized Linear Model)。廣義線性模型是一族模型,可為其他分佈生成一個線性迴歸(誤差項的正態性假設之下)。模型可以表述為:
這裡g是一個連結函式,Xβ是線性等式的集合,E(Y)是因變數的期望(把它看成一個非正態分佈的平均值)。GLM用連結函式將模型與響應變數的分佈關聯起來。
這裡有所有連結函式的列表:http://statsmodels.sourceforge.net/devel/glm.html#links。
StatsModels的.GLM(...)方法允許我們指定很多分佈。在我們的例子中,應用了二項分佈變數,這種變數只有兩種取值:1代表帶來信用卡申請的電話,0代表沒帶來信用卡申請的電話。這樣我們就可以應用.families.Binomial(...)方法。實際上,二項分佈族預設的連結函式就是logit,所以我們其實不用顯式指定;我們這樣做,只是為了在你要用其他連結函式時,知道怎麼做。
StatsModels中實現的所有模型族列表:http://statsmodels.sourceforge.net/devel/glm.html#families。
我們更熟悉的也許是sigmoid函式,logit函式是其反函式(參見下圖):
如你所見,邏輯迴歸的輸出只能在0和1之間變化,我們可以將其作為電話轉化為信用卡申請的機率;一旦高於50%,我們就認為更有可能帶來信用卡申請,將其歸為1,否則就歸為0。下面的程式碼可以獲得這個效果:
# classify the unseen data 對隱藏的資料分類 predicted = classifier.predict(test_x) # assign the class指定類別 predicted = [1 if elem > 0.5 else 0 for elem in predicted] # print out the results列印結果 hlp.printModelSummary(test_y, predicted)
我們使用列表表示式將最終的分類賦給測試資料集。然後,我們用.printModel Summary(...)方法列出模型的效能表現。邏輯迴歸分類器的表現要比樸素貝葉斯的好,但這是建立在更高的計算代價之上的。
與樸素貝葉斯分類器相比,我們付出了高估false positive的代價,得到的是更好的歸類true positive的能力。
StatsModels的GLM分類器允許我們列印出關於迴歸的更詳細的結果,以及係數值和它們的統計表現。在分類器上呼叫的.summary()方法會列印類似下圖(縮略版)的結果:
Generalized Linear Model Regression Results ============================================================================== Dep. Variable: credit_application No. Observations: 27753 Model: GLM Df Residuals: 27701 Model Family: Binomial Df Model: 51 Link Function: logit Scale: 1.0000 Method: IRLS Log-Likelihood: nan Date: Sun, 15 Mar 2020 Deviance: nan Time: 16:56:57 Pearson chi2: 1.90e+19 No. Iterations: 100 Covariance Type: nonrobust ================================================================================================ coef std err z P>|z| [0.025 0.975] ------------------------------------------------------------------------------------------------ n_age -1.937e+13 4.09e+06 -4.74e+06 0.000 -1.94e+13 -1.94e+13 n_duration 1.091e+16 7.63e+06 1.43e+09 0.000 1.09e+16 1.09e+16 n_pdays -8.708e+14 7.45e+06 -1.17e+08 0.000 -8.71e+14 -8.71e+14 n_previous -6.625e+12 1.39e+07 -4.76e+05 0.000 -6.63e+12 -6.63e+12 n_emp_var_rate -3.034e+15 1.91e+07 -1.59e+08 0.000 -3.03e+15 -3.03e+15 n_cons_price_idx 7.766e+15 1.7e+07 4.57e+08 0.000 7.77e+15 7.77e+15 n_cons_conf_idx 2.743e+15 5.39e+06 5.09e+08 0.000 2.74e+15 2.74e+15 n_euribor3m -7.989e+15 1.43e+07 -5.57e+08 0.000 -7.99e+15 -7.99e+15 n_nr_employed 9.976e+15 2.09e+07 4.76e+08 0.000 9.98e+15 9.98e+15 job_admin -3.148e+14 1.23e+06 -2.56e+08 0.000 -3.15e+14 -3.15e+14 job_blue_collar -3.598e+14 1.34e+06 -2.68e+08 0.000 -3.6e+14 -3.6e+14 job_entrepreneur -3.638e+14 2.19e+06 -1.66e+08 0.000 -3.64e+14 -3.64e+14 job_housemaid -3.058e+14 2.5e+06 -1.22e+08 0.000 -3.06e+14 -3.06e+14 job_management -3.439e+14 1.74e+06 -1.98e+08 0.000 -3.44e+14 -3.44e+14 job_retired -2.012e+14 2.29e+06 -8.78e+07 0.000 -2.01e+14 -2.01e+14 job_self_employed -3.907e+14 2.2e+06 -1.78e+08 0.000 -3.91e+14 -3.91e+14 job_services -4.108e+14 1.58e+06 -2.6e+08 0.000 -4.11e+14 -4.11e+14 job_student -2.723e+14 2.86e+06 -9.53e+07 0.000 -2.72e+14 -2.72e+14 job_technician -2.846e+14 1.41e+06 -2.01e+08 0.000 -2.85e+14 -2.85e+14 job_unemployed -2.482e+14 2.47e+06 -1.01e+08 0.000 -2.48e+14 -2.48e+14 job_unknown -4.352e+14 4.24e+06 -1.03e+08 0.000 -4.35e+14 -4.35e+14 marital_divorced -9.426e+14 2.92e+06 -3.23e+08 0.000 -9.43e+14 -9.43e+14 marital_married -9.485e+14 2.78e+06 -3.42e+08 0.000 -9.48e+14 -9.48e+14 marital_single -9.31e+14 2.81e+06 -3.32e+08 0.000 -9.31e+14 -9.31e+14 marital_unknown -1.109e+15 7.48e+06 -1.48e+08 0.000 -1.11e+15 -1.11e+15 edu_basic_4y -3.349e+14 2.62e+06 -1.28e+08 0.000 -3.35e+14 -3.35e+14 edu_basic_6y -2.782e+14 2.78e+06 -1e+08 0.000 -2.78e+14 -2.78e+14 edu_basic_9y -3.374e+14 2.52e+06 -1.34e+08 0.000 -3.37e+14 -3.37e+14 edu_high_school -2.794e+14 2.47e+06 -1.13e+08 0.000 -2.79e+14 -2.79e+14 edu_illiterate -1.898e+15 1.59e+07 -1.19e+08 0.000 -1.9e+15 -1.9e+15 edu_professional_course -3.051e+14 2.59e+06 -1.18e+08 0.000 -3.05e+14 -3.05e+14 edu_university_degree -2.503e+14 2.46e+06 -1.02e+08 0.000 -2.5e+14 -2.5e+14 edu_unknown -2.477e+14 2.92e+06 -8.47e+07 0.000 -2.48e+14 -2.48e+14 default_unknown -1.301e+14 1.07e+06 -1.22e+08 0.000 -1.3e+14 -1.3e+14 default_yes -9.129e+14 4.75e+07 -1.92e+07 0.000 -9.13e+14 -9.13e+14 housing_unknown 1.694e+13 1.34e+06 1.26e+07 0.000 1.69e+13 1.69e+13 housing_yes -3.679e+13 8.24e+05 -4.47e+07 0.000 -3.68e+13 -3.68e+13 loan_unknown 1.694e+13 1.34e+06 1.26e+07 0.000 1.69e+13 1.69e+13 loan_yes -1.265e+13 1.13e+06 -1.12e+07 0.000 -1.27e+13 -1.27e+13 contact_cellular -1.92e+15 3.79e+06 -5.07e+08 0.000 -1.92e+15 -1.92e+15 contact_telephone -2.011e+15 4.2e+06 -4.79e+08 0.000 -2.01e+15 -2.01e+15 prev_ctc_outcome_failure -9.593e+14 4.53e+06 -2.12e+08 0.000 -9.59e+14 -9.59e+14 prev_ctc_outcome_nonexistent -7.454e+14 4.15e+06 -1.79e+08 0.000 -7.45e+14 -7.45e+14 prev_ctc_outcome_success -2.226e+15 4.54e+06 -4.9e+08 0.000 -2.23e+15 -2.23e+15 month_apr -9.509e+13 2.48e+06 -3.83e+07 0.000 -9.51e+13 -9.51e+13 month_aug 3.182e+14 2.51e+06 1.27e+08 0.000 3.18e+14 3.18e+14 month_dec -1.189e+15 5.67e+06 -2.1e+08 0.000 -1.19e+15 -1.19e+15 month_jul -4.792e+14 2.27e+06 -2.12e+08 0.000 -4.79e+14 -4.79e+14 month_jun -2.196e+15 4.82e+06 -4.56e+08 0.000 -2.2e+15 -2.2e+15 month_mar -9.509e+14 3.41e+06 -2.79e+08 0.000 -9.51e+14 -9.51e+14 month_may -4.898e+14 1.5e+06 -3.26e+08 0.000 -4.9e+14 -4.9e+14 month_nov 4.175e+14 1.96e+06 2.13e+08 0.000 4.17e+14 4.17e+14 month_oct 2.752e+14 3.11e+06 8.84e+07 0.000 2.75e+14 2.75e+14 month_sep 4.585e+14 3.8e+06 1.21e+08 0.000 4.58e+14 4.58e+14 dow_fri -8.033e+14 1.8e+06 -4.45e+08 0.000 -8.03e+14 -8.03e+14 dow_mon -8.761e+14 1.74e+06 -5.02e+08 0.000 -8.76e+14 -8.76e+14 dow_thu -7.73e+14 1.78e+06 -4.34e+08 0.000 -7.73e+14 -7.73e+14 dow_tue -7.526e+14 1.74e+06 -4.33e+08 0.000 -7.53e+14 -7.53e+14 dow_wed -7.26e+14 1.75e+06 -4.14e+08 0.000 -7.26e+14 -7.26e+14 ================================================================================================
顯而易見,n_age變數是不顯著的,從模型中捨棄也不會丟失多少精度,而n_duration變數,既顯著,又對消費者是否申請信用卡有重大影響。這樣的資訊有助於確定你的模型,並在移除無關(且可能引入誤差的)變數後讓模型更好。
你也可以使用Scikit的方法來估計一個邏輯迴歸分類器(classification_logistic_alternative.py檔案):
/* The method fitLogisticRegression took 0.69 sec to run. Overall accuracy of the model is 91.15 percent Classification report: precision recall f1-score support 0.0 0.93 0.98 0.95 12030 1.0 0.68 0.40 0.50 1522 accuracy 0.91 13552 macro avg 0.80 0.69 0.73 13552 weighted avg 0.90 0.91 0.90 13552 Confusion matrix: [[11743 287] [ 913 609]] ROC: 0.6881371909691387 {'n_age': 0.21385522376961028, 'n_duration': 19.191139443277162, 'n_pdays': -0.8673162838021525, 'n_previous': -0.18411649033655933, 'n_emp_var_rate': -4.665790648993948, 'n_cons_price_idx': 2.6226190540263103, 'n_cons_conf_idx': 0.1321457450143668, 'n_euribor3m': 1.2577893320833486, 'n_nr_employed': -1.0950955374646227, 'job_admin': 0.015606031742936122, 'job_blue_collar': -0.22590019775314962, 'job_entrepreneur': -0.07105552322436609, 'job_housemaid': -0.166748843709672, 'job_management': 0.0016511122208341445, 'job_retired': 0.31613911985033627, 'job_self_employed': -0.10302101859015086, 'job_services': -0.14037817943629885, 'job_student': 0.17802965024700726, 'job_technician': -0.017165213063450657, 'job_unemployed': 0.018250921784332092, 'job_unknown': 0.004532611196154809, 'marital_divorced': -0.0283747974255571, 'marital_married': 0.0557151595182598, 'marital_single': 0.13716177614661754, 'marital_unknown': -0.3545616669747635, 'edu_basic_4y': -0.23092212023892777, 'edu_basic_6y': -0.156627551097012, 'edu_basic_9y': -0.22916859893223016, 'edu_high_school': -0.17797495084025308, 'edu_illiterate': 0.7469642970378965, 'edu_professional_course': -0.06316024234627139, 'edu_university_degree': -0.03984420682156178, 'edu_unknown': -0.039326155497083, 'default_unknown': -0.30134284129316585, 'default_yes': 0.0, 'housing_unknown': -0.11542935562620459, 'housing_yes': -0.027745897790976075, 'loan_unknown': -0.11542935562620459, 'loan_yes': 0.03692190860121382, 'contact_cellular': 0.15231778645440006, 'contact_telephone': -0.3423773151898108, 'prev_ctc_outcome_failure': -0.5210489740812024, 'prev_ctc_outcome_nonexistent': -0.12607757606633352, 'prev_ctc_outcome_success': 0.4570670214120743, 'month_apr': -0.1322878780570286, 'month_aug': 0.39290453068094094, 'month_dec': -0.09699979367892911, 'month_jul': -0.017157002315930734, 'month_jun': -0.22287078365578353, 'month_mar': 1.2715744551548396, 'month_may': -0.6823130145064639, 'month_nov': -0.4443492486046949, 'month_oct': -0.08951220175145218, 'month_sep': -0.1690485920009346, 'dow_fri': -0.06432307409320341, 'dow_mon': -0.22305048792318713, 'dow_thu': 0.0069370229422389225, 'dow_tue': -0.004280105768027797, 'dow_wed': 0.09465711610671972} */
儘管這個方法比StatsModels的要快,但這是付出了代價的,它不能評估哪些係數是統計顯著的,或哪些不是;.LogisticRegression(...)分類器不能生成這些統計資料。
這個模型的結果和StatsModels中的類似,比起樸素貝葉斯分類器,都是用召回率換精確率。
下一章節我們會介紹mlpy,一個機器學習模組。其也能用於預計線性模型:http://mlpy.sourceforge.net/docs/3.5/liblinear.html。
3.5將支援向量機用作分類引擎
支援向量機(Support Vector Machines,SVM)是一族可用於分類和迴歸問題的強大模型。與前面的模型不同,其透過所謂的核技巧,將輸入向量隱式地對映到高維的特徵空間,可以處理高度非線性的問題。更寬泛的SVM的解釋參見:http://www.statsoft.com/Textbook/Support-Vector-Machines。
要執行後續的技巧,你需要mlpy(Machine Learning PYthon)。mlpy不在Anaconda中,所以我們需要手動安裝它。mlpy要求預裝GSL(GNU Scientific Library);有些系統可能已經裝了GSL,所以我推薦先裝mlpy。訪問http://sourceforge.net/projects/mlpy/files/,下載最新資源(mlpy-<version>.tar.gz)。
注意這個地址只有32位,且只支援python3.3。
試著用pip直接安裝:
pip install mlpy
/* Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting mlpy ......... ERROR: Command errored out with exit status 1: 'd:\tools\python37\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'d:\\Temp\\pip-install-hkwwru7x\\mlpy\\setup.py'"'"'; __file__='"'"'d:\\Temp\\pip-install-hkwwru7x\\mlpy\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'd:\Temp\pip-record-f_wkvunv\install-record.txt' --single-version-externally-managed --compile Check the logs for full command output. Running setup.py install for mlpy: finished with status 'error' FINISHED */
解決方案:從非官方下載64位mlpy-3.5.0-cp37-cp37m-win_amd64.whl檔案,python3.7命令列下手工安裝即可。
下載地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#mlpy
注意,本章SVM程式碼還是執行32位能夠透過。64位不行
pip install gsl
/* Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting gsl Downloading https://pypi.tuna.tsinghua.edu.cn/packages/ee/1e/14d4c4657bc7130b900c65033861ee0db508d2d9206f4223f5da7c855acc/gsl-0.0.3-py3-none-any.whl Installing collected packages: gsl Successfully installed gsl-0.0.3 FINISHED */
用mlpy構造SVM分類器很簡單。你只要指定SVM的型別和它的核(classification_svm.py檔案):
def fitRBFSVM(data): ''' Build the linear SVM classifier 構建線性SVM分類器 ''' # create the classifier object 構建分類器物件 svm = ml.LibSvm(svm_type='c_svc', kernel_type='linear', C=20.0) # fit the data應用資料 svm.learn(data[0],data[1]) #return the classifier返回分類器 return svm
原理:首先,我們載入mlpy。構建模型從呼叫.LibSvm(...)方法開始。我們指定的第一個引數是svm_type。c_svc指定的是C支援向量機分類器;C引數補償了誤差項。你也可以使用nu_svc引數,nu引數控制了你的樣本中(最多)允許多少錯誤分類,以及你的觀察值中(最少)有多少能成為支援向量。
顧名思義,kernel_type指定的是核的型別。我們可以選擇線性、多項式、RBF(Radial Basis Functions,徑向基函式)或S函式。根據你選擇的核,模型會試著應用直線、多段線或者非線性的形狀,來給我們的類別做最好的區分。
線性核適用於線性可分的問題,RBF可用於尋找類別之間高度非線性的界限。參考這裡的展示:http://www.cs.cornell.edu/courses/cs578/2003fa/slides_sigir03_tutorial-modified.v3.pdf。
指定了模型之後,我們讓它從資料中學習(.learn(...))支援向量。要使用一個RBF版本的模型,我們可以指定svm物件:
# create the classifier object 構建分類器物件 svm = ml.LibSvm(svm_type='c_svc', kernel_type='rbf', gamma=0.001, C=20.0)
這裡的gamma引數指定了單個支援向量可影響的範圍。你可以在這裡看看gamma和C引數的關係:http://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html。
目前為止,介紹的分類方法中,SVM最慢。效能也不如邏輯迴歸分類器。這可不是說邏輯迴歸就是分類問題的萬能解藥了。對於這個特定的資料集,邏輯迴歸的效能比SVM好;對於其他非線性問題,SVM也許更適合,也許會有優於邏輯迴歸分類器的效能。這也能從我們的SVM測試中觀察到:線性核SVM的表現優於RBF核SVM:
Tips:
/* Exception ignored in: 'libsvm.array1d_to_node' ValueError: Buffer dtype mismatch, expected 'int_t' but got 'long long' 64位可以安裝,一直編譯不過去,真是見鬼了,懷疑是64位whl有問題,待求證 */
解決方案:從非官方下載32位mlpy-3.5.0-cp37-cp37m-win32.whl檔案,python官網下載32位安裝檔案python-3.7.5.exe
/* D:\tools\Python37_32\Scripts>dir 驅動器 D 中的卷是 Work 卷的序列號是 9E78-6D56 D:\tools\Python37_32\Scripts 的目錄 2019-12-16 10:46 <DIR> . 2019-12-16 10:46 <DIR> .. 2019-12-16 10:43 93,042 easy_install-3.7.exe 2019-12-16 10:43 93,042 easy_install.exe 2019-12-16 10:46 93,025 f2py.exe 2019-12-16 10:45 93,029 pip.exe 2019-12-16 10:45 93,029 pip3.7.exe 2019-12-16 10:45 93,029 pip3.exe 6 個檔案 558,196 位元組 2 個目錄 183,223,492,608 可用位元組 D:\tools\Python37_32\Scripts>pip3.7 install mlpy-3.5.0-cp37-cp37m-win32.whl Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Processing d:\tools\python37_32\scripts\mlpy-3.5.0-cp37-cp37m-win32.whl Installing collected packages: mlpy Successfully installed mlpy-3.5.0 D:\tools\Python37_32\Scripts> */
/* The method fitLinearSVM took 139.84 sec to run. The method fitRBFSVM took 19.29 sec to run. Overall accuracy of the model is 90.77 percent Classification report: precision recall f1-score support 0.0 0.92 0.98 0.95 11967 1.0 0.64 0.32 0.43 1445 accuracy 0.91 13412 macro avg 0.78 0.65 0.69 13412 weighted avg 0.89 0.91 0.89 13412 Confusion matrix: [[11707 260] [ 978 467]] ROC: 0.6507284883487261 Overall accuracy of the model is 90.08 percent Classification report: precision recall f1-score support 0.0 0.91 0.99 0.95 11967 1.0 0.63 0.20 0.30 1445 accuracy 0.90 13412 macro avg 0.77 0.59 0.62 13412 weighted avg 0.88 0.90 0.88 13412 Confusion matrix: [[11797 170] [ 1161 284]] ROC: 0.5911670299783458 */
Scikit-learn也提供了預測SVM的方法(classification_svm_alternative.py檔案):
def fitSVM(data): ''' Build the SVM classifier ''' # create the classifier object svm = sv.SVC(kernel='linear', C=20.0) # fit the data return svm.fit(data[0],data[1])
這個例子中用了線性核,當然,你也可以使用RBF(實際上,.SVC(...)方法預設用的就是RBF)。得到的結果類似,但是要比mlpy快。如果需要的話,你也可以列出所有支援向量的資訊;這些資訊存在分類器的.support_vectors屬性裡:
/* The method fitSVM took 72.45 sec to run. Overall accuracy of the model is 90.16 percent Classification report: precision recall f1-score support 0.0 0.92 0.98 0.95 11952 1.0 0.65 0.32 0.43 1564 accuracy 0.90 13516 macro avg 0.79 0.65 0.69 13516 weighted avg 0.89 0.90 0.89 13516 Confusion matrix: [[11690 262] [ 1068 496]] ROC: 0.6476072662345889 [[0.41975309 0.16510777 1. ... 0. 0. 0. ] [0.27160494 0.41337942 1. ... 0. 0. 0. ] [0.45679012 0.17141114 1. ... 0. 0. 0. ] ... [0.55555556 0.06689711 1. ... 1. 0. 0. ] [0.55555556 0.04229362 0.001001 ... 1. 0. 0. ] [0.69135802 0.06791379 1. ... 0. 0. 0. ]] */
3.6使用決策樹進行分類
決策樹廣泛用於解決分類問題。顧名思義,決策樹是一個樹狀結構,由根節點向外蔓延出分支。在每個分支(決策)節點,剩餘的資料以是否滿足一個具體的決策標準為依據,劃分為兩組。這個過程重複進行,直到不能進行進一步的劃分,或者終止(葉子)節點所有的樣本都從屬於同一個類(這時方差最小)。
要實踐本技巧,你需要裝好pandas和Scikit-learn。另一種方法需要mlpy。
Scikit-learn提供了DecisionTreeClassifier(...)類,我們可以用來預測決策樹分類器(classification_decisionTree.py檔案):
def fitDecisionTree(data): ''' Build a decision tree classifier ''' # create the classifier object tree = sk.DecisionTreeClassifier(min_samples_split=1000) # fit the data return tree.fit(data[0],data[1])
首先,我們匯入sklearn.tree模組,這個模組提供DecisionTreeClassifier(...)類供我們使用。然後我們從CSV檔案讀入資料,將資料集拆分成訓練集和測試集。我們決定只是用所有變數的一個子集。將我們想用的變數列表作為x引數傳入我們的.split\_data(...)方法:
train_x, train_y, \ test_x, test_y, \ labels = hlp.split_data( csv_read, y = 'credit_application', x = ['n_duration','n_nr_employed', 'prev_ctc_outcome_success','n_euribor3m', 'n_cons_conf_idx','n_age','month_oct', 'n_cons_price_idx','edu_university_degree','n_pdays', 'dow_mon','job_student','job_technician', 'job_housemaid','edu_basic_6y'] )
注意,我們的.split_data(...)方法不僅返回訓練子集和測試子集,也返回各變數的標籤。
現在有了訓練子集和測試子集,我們可以應用決策樹了:
# train the model classifier = fitDecisionTree((train_x, train_y))
DecisionTreeClassifier(...)類有多種操作方式。這裡,我們僅僅指定每個決策節點的觀測值不能少於1000個。
DecisionTreeClassifier(...)類的全部引數列表,參見http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html。
將模型應用(.fit(...)方法)到資料上之後,我們可以將test_x資料集中的觀測值歸類,列印出下面的結果:
/* The method fitDecisionTree took 0.06 sec to run. Overall accuracy of the model is 91.26 percent Classification report: precision recall f1-score support 0.0 0.94 0.96 0.95 12202 1.0 0.64 0.55 0.59 1585 accuracy 0.91 13787 macro avg 0.79 0.75 0.77 13787 weighted avg 0.91 0.91 0.91 13787 Confusion matrix: [[11710 492] [ 713 872]] ROC: 0.7549182349482967 0. n_duration: 0.5034721037471864 1. n_nr_employed: 0.3442687149792282 2. prev_ctc_outcome_success: 0.0 3. n_euribor3m: 0.041463681111437736 4. n_cons_conf_idx: 0.03422990519127428 5. n_age: 0.01335144575508925 6. month_oct: 0.009871520358454011 7. n_cons_price_idx: 0.005585809042490998 8. edu_university_degree: 0.0031636326509486726 9. n_pdays: 0.044546262351931966 10. dow_mon: 0.0 11. job_student: 2.661076258790501e-05 12. job_technician: 0.0 13. job_housemaid: 1.5845999849280513e-05 14. edu_basic_6y: 4.468049521049593e-06 */
目前為止,這是我們用過的最好的模型,精確率和召回率都很好。樸素貝葉斯雖然有類似的ROC值,其精確率卻差得多(參見樸素貝葉斯分類器)。而預測決策樹分類器只比樸素貝葉斯分類器稍慢。
Scikit也提供了一個很有用的.export_graphviz(...)方法,讓你可以以.dot格式儲存模型;.dot格式是GraphViz的本地格式:
# and export to a .dot file sk.export_graphviz(classifier, out_file='../../Data/Chapter03/decisionTree/tree.dot')
.dot檔案本質上是一個文字檔案,不便於閱讀。不過,我們可以將其視覺化。你可以使用GraphViz本身,或者,在Linux或Mac OS X上,你隨時可以使用dot工具。
GraphViz是一個開源視覺化工具。下載地址:http://www.graphviz.org/Download.php。
要為我們的樹生成一個PDF檔案,只需執行下面這行命令:
/* dot -Tpdf tree.dot -o tree.pdf */
dot命令可以多種格式輸出這棵樹:PNG(-.jpg)或者SVG(Scalable Vector Graphics(可縮放向量圖形),-Tsvg)等等。
完整的格式列表,參考http://www.graphviz.org/content/output-formats。
-o引數指定輸出檔案的名字(本例中,tree.pdf)。輸出如下所示(簡略版):
上圖中,每個節點都帶有決策樹的重要資訊:
左邊是決策節點,右邊是最終的葉子節點。X[1]指定了我們樣本中的變數。要追蹤我們首先拆分的是哪個變數,我們可以列印出變數名,它們在資料集中的位置,以及它們的重要性:
# print out the importance of features for counter, (nm, label) \ in enumerate( zip(labels, classifier.feature_importances_) ): print("{0}. {1}: {2}".format(counter, nm,label))
前面的程式碼使用了enumerate(...)方法,這個方法返回兩個元素:計數器和元組(nm,label)。zip(...)方法傳入我們的標籤物件和決策樹分類器的.feature_importances_屬性,建立出一個實體,這個實體將標籤物件中元素與.feature_importances_中的元素根據位置一一對應,即,標籤中的第一個元素對應.feature_importances_中第一個元素。我們的指令碼輸出如下(簡略版):
可以看到,我們的X[1]變數實際上是n_nr_employed。所以,如果n_nr_employed小於等於0.4690,我們就會循著樹前往左邊的下一個決策節點;否則,我們會往右邊走。
決策節點的gini屬性指的是基尼不純度指標。它衡量的是一個隨機選擇的元素被分錯的可能性:越接近0,你就對觀測值沒有被分錯越有信心。
要學習如何計算基尼不純度指標,參考http://people.revoledu.com/kardi/tutorial/Decision Tree/how-to-measure-impurity.htm。
決策節點中,samples屬性指的是拆分了多少樣本。即有多少樣本落到葉子節點的每個類上。
更多
mlpy框架也能幫我們預測一個決策樹分類器(classification_decisionTree_alternative.py檔案):
import mlpy as ml @hlp.timeit def fitDecisionTree(data): ''' Build a decision tree classifier ''' # create the classifier object tree = ml.ClassTree(minsize=1000) # fit the data tree.learn(data[0],data[1]) # return the classifier return tree
如同Scikit的DecisionTreeClassifier,我們也指定ClassTree(...)這個類的minsize引數。與Scikit相比,ClassTree(...)這個類可供折騰的選項更少,但也能生成一個同樣有效的決策樹分類器:
/* The method fitDecisionTree took 0.71 sec to run. Overall accuracy of the model is 91.22 percent D:\tools\Python37\lib\site-packages\sklearn\metrics\_classification.py:1268: UndefinedMetricWarning: Recall and F-score are ill-defined and being set to 0.0 in labels with no true samples. Use `zero_division` parameter to control this behavior. _warn_prf(average, modifier, msg_start, len(result)) Classification report: precision recall f1-score support -1.0 0.00 0.00 0.00 0 0.0 0.94 0.96 0.95 11942 1.0 0.65 0.56 0.60 1608 accuracy 0.91 13550 macro avg 0.53 0.51 0.52 13550 weighted avg 0.91 0.91 0.91 13550 Confusion matrix: [[ 0 0 0] [ 2 11455 485] [ 0 703 905]] ROC: 0.7611356006769036 */
3.7使用隨機森林預測訂閱者
隨機森林屬於整合模型。整合模型秉承的思想就是人多力量大;將多個弱模型(決策樹)聯合起來,得到一個反映其眾數的預測。參考https://www.stat.berkeley.edu/~breiman/RandomForests/cc_home.htm。
要實踐這個技巧,你需要裝好pandas和scikit-learn。
如同前一個例子,scikit提供了構建隨機森林分類器的簡單方式(classification_random Forest.py檔案):
import sklearn.ensemble as en import sklearn.tree as sk @hlp.timeit def fitRandomForest(data): ''' Build a random forest classifier ''' # create the classifier object forest = en.RandomForestClassifier(n_jobs=-1, min_samples_split=100, n_estimators=10, class_weight="auto") # fit the data return forest.fit(data[0],data[1])
原理:首先,我們匯入scikit-learn中的必要模組,以使用RandomForestClassifier(...)類。讀入資料集,並將其拆成訓練樣本和測試樣本後,我們預測RandomForestClassifier(...)。
RandomForestClassifier(...)有多個引數,更多資訊參考http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html。
我們先指定n_jobs。這是指在估算和預測階段Python應該並行跑多少個任務。如果你的資料集很大,有很多觀測值和特徵,整合模型中要估算數以千計的樹,這個引數會有顯著的影響。然而在我們的例子中,在-1(任務數對應處理器的核數)和1(單一任務)之間切換不會帶來多大的差異。
和決策樹分類器一樣,min_samples_split引數控制著拆分時決策節點中觀測值的最小數目。n_estimators指定了構建多少個弱模型;我們的例子中指定了10個,不過如果需要的話,指定幾千個也是可以的。class_weight引數控制著每個類的權重。當你的輸入資料中,各個類的頻率高度偏斜時(就像我們的例子),這個引數就派上用場了。這個引數設成auto,意味著將類的權重設為頻率的倒數。你可以自己指定class_weight:這個引數接受{class:weight}形式的字典。
.fit(...)方法可以接受一個sample_weight引數(我們的程式碼中沒有體現)。這個指定了每個觀測值的權重;所以,傳入的值得是一個向量(列表),向量的長度要等於資料集的行數。如果有些觀測值因為各種原因不可信,這個引數就很有用了;模型中不用丟棄這些觀測值,你只需要指定小一些的權重就可以了。
估算RandomForestClassifier(...)的時間由多個因素決定:資料集的大小,分類器的數目,和指定的任務數目。我們的例子中,由配置決定了,其和決策樹分類器相比不會太久。目前為止,這個模型在召回率和ROC兩個指標上得分最高。精確率不如其他模型;這個模型生成的false positive比true positive要多:
/* The method fitRandomForest took 0.30 sec to run. Overall accuracy of the model is 86.00 percent Classification report: precision recall f1-score support 0.0 0.99 0.85 0.92 12058 1.0 0.43 0.92 0.59 1465 accuracy 0.86 13523 macro avg 0.71 0.88 0.75 13523 weighted avg 0.93 0.86 0.88 13523 Confusion matrix: [[10288 1770] [ 123 1342]] ROC: 0.8846252215542966 0. n_duration: 0.4894029629785369 1. n_nr_employed: 0.1316263044033158 2. prev_ctc_outcome_success: 0.04115542029423514 3. n_euribor3m: 0.07878501119397965 4. n_cons_conf_idx: 0.08192032521143604 5. n_age: 0.027459554226413115 6. month_oct: 0.012822570798719032 7. n_cons_price_idx: 0.06255811847948148 8. edu_university_degree: 0.005049798469728383 9. n_pdays: 0.05688625400786597 10. dow_mon: 0.0034728676038600532 11. job_student: 0.003466901827077513 12. job_technician: 0.0023592908732427884 13. job_housemaid: 0.0017758360204634734 14. edu_basic_6y: 0.0012587836116446092 */
Tips:
/* Traceback (most recent call last): File "D:\Java2018\practicalDataAnalysis\Codes\Chapter03\classification_randomForest.py", line 45, in <module> classifier = fitRandomForest((train_x, train_y)) File "D:\Java2018\practicalDataAnalysis\helper.py", line 13, in timed result = method(*args, **kw) File "D:\Java2018\practicalDataAnalysis\Codes\Chapter03\classification_randomForest.py", line 22, in fitRandomForest return forest.fit(data[0],data[1]) File "D:\tools\Python37\lib\site-packages\sklearn\ensemble\_forest.py", line 321, in fit y, expanded_class_weight = self._validate_y_class_weight(y) File "D:\tools\Python37\lib\site-packages\sklearn\ensemble\_forest.py", line 567, in _validate_y_class_weight % self.class_weight) ValueError: Valid presets for class_weight include "balanced" and "balanced_subsample".Given "auto". */
解決方案:參照官方文件https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html修改為balanced即可。
我們將所有的樹輸出到/Data/Chapter03/randomForest資料夾。在這個資料夾中,你會發現有個convertToPdf.sh指令碼,自動將.dot格式轉成.pdf格式。這個指令碼在任何類UNIX環境(Linux、Mac OS X和Cygwin等)下都應該能正常工作。在這個資料夾下輸入下面的命令執行指令碼:
./convertToPdf.sh
指令碼很簡單:
#/bin/bash for f in *.dot; do echo Processing $f; dot -Tpdf $f -o ${f%.*}.pdf; done
接下來,for迴圈會遍歷所有的.dot檔案;每個檔案的名字都存在f變數中。bash指令碼里,我們用$f訪問f變數中存的值。echo命令將我們當前處理的檔名字列印到螢幕上。然後,我們用已經熟悉了的dot命令將.dot檔案轉成PDF文件。注意一下我們是怎麼去掉.dot副檔名,只提取出檔名,加上新格式的.pdf副檔名的。
梯度提升分類器雖然不是隨機森林分類器,但也屬於同一族模型。梯度提升樹的原理和隨機森林很類似——都是將弱模型聯結起來預測分類。不同點在於,隨機森林隨機地訓練樹,而梯度提升嘗試的是最佳化樹的線性組合。
scikit提供了GradientBoostingClassifier(...)類,以應用梯度提升樹(classifier_gradient Boosting.py檔案):
import sklearn.ensemble as en @hlp.timeit def fitGradientBoosting(data): ''' Build a gradient boosting classier ''' # create the classifier object gradBoost = en.GradientBoostingClassifier( min_samples_split=100, n_estimators=500) # fit the data return gradBoost.fit(data[0],data[1])
GradientBoostingClassifier(...)的引數集合類似於RandomForestClassifier(...);我們也為一次拆分指定最少的樣本數,並將弱模型的數目指定為10。
對我們的資料來說,梯度提升分類器比起隨機森林,在召回率和ROC兩個指標上表現較差,但是在精確率指標上表現更好:
/* The method fitGradientBoosting took 11.92 sec to run. Overall accuracy of the model is 91.35 percent Classification report: precision recall f1-score support 0.0 0.94 0.97 0.95 11984 1.0 0.65 0.51 0.57 1525 accuracy 0.91 13509 macro avg 0.79 0.74 0.76 13509 weighted avg 0.91 0.91 0.91 13509 Confusion matrix: [[11567 417] [ 752 773]] ROC: 0.7360444253540239 0. n_duration: 0.46196008952421486 1. n_nr_employed: 0.2697341442143176 2. prev_ctc_outcome_success: 0.006951174326559933 3. n_euribor3m: 0.10057168252401916 4. n_cons_conf_idx: 0.04414534932562641 5. n_age: 0.025543351750548025 6. month_oct: 0.0182558540256949 7. n_cons_price_idx: 0.019076218075925366 8. edu_university_degree: 0.0012984467423719616 9. n_pdays: 0.0478102506589297 10. dow_mon: 0.002763907902594316 11. job_student: 0.00040155661288408193 12. job_technician: 0.0005351738521636878 13. job_housemaid: 0.00048743439032264315 14. edu_basic_6y: 0.0004653660738274407 */
3.8使用神經網路對呼叫進行分類
人工神經網路(Artificial Neural Networks,ANN)是模仿生物大腦功能的機器學習模型。神經網路的一個基本單元是一個叫作神經元的結構。一個神經元有一個或多個輸入和一個細胞體——神經元中的一個部分,彙總輸入訊號,經由啟用(遷移)函式決定是否(以及如何)傳播至輸出。人工神經元可以實現多種遷移函式。從一些基本函式,比如階躍函式,僅當滿足某個臨界值才會傳送訊號;到不對訊號做任何改動的線型函式;再到tanh、sigmoid或RBF等非線性函式。
神經元網路將神經元組織成層。輸入層介於訓練資料集和神經網路之間,它需要和x變數同樣數目的輸入神經元。隱藏層有很多神經元。這些神經元的輸入端可以與前一層的一個、多個或所有神經元相連;訊號到達下一層之前會由這些聯結加上權重,要麼放大,要麼阻尼。輸出層神經元的數目要與輸出變數的層數相等。在我們的例子中,由於依賴變數有兩層:某人是否申請信用卡,所以我們用了兩個神經元。下面是兩個隱藏層的神經網路的一個典型佈局:
神經元網路的訓練,其實是神經元之間聯結權重的變更,與每個神經元啟用函式引數的調整。最流行的監督學習正規化是誤差反向傳播訓練方法。這個演算法計算網路的輸出與目標變數之間的誤差,然後將誤差反向傳播到前一層,根據特定神經元對輸出的影響,調整聯結和神經元的引數。
要實踐這個技巧,你需要安裝pandas和PyBrain。要安裝PyBrain,執行這些命令:
>pip install pybrain
/* Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting pybrain Downloading https://pypi.tuna.tsinghua.edu.cn/packages/be/42/b40b64b7163d360425692db2f15a8c1d8fe4a18f1c5626cf7fcb5f5d5fb6/PyBrain-0.3.tar.gz (262kB) Building wheels for collected packages: pybrain Building wheel for pybrain (setup.py): started Building wheel for pybrain (setup.py): finished with status 'done' Created wheel for pybrain: filename=PyBrain-0.3-cp37-none-any.whl size=399050 sha256=71115d497b1d6adccc940fbf8d9157d24de10e540ab5aa7c78bbec87f3e31dd8 Stored in directory: C:\Users\tony zhang\AppData\Local\pip\Cache\wheels\40\1f\6f\76fd7bd538d813b220f1f373a57e61bd4611757ce3a3a1b2fb Successfully built pybrain Installing collected packages: pybrain Successfully installed pybrain-0.3 FINISHED */
用PyBrain估算一個簡單的神經元網路相當輕鬆:
import sklearn.naive_bayes as nb @hlp.timeit def fitNaiveBayes(data): ''' Build the Naive Bayes classifier 構造神經元分類器 ''' # create the classifier object 構造一個分類 器物件 naiveBayes_classifier = nb.GaussianNB() # fit the model 擬合模型 return naiveBayes_classifier.fit(data[0], data[1])
首先,我們從PyBrain載入所有必要的模組。.structure讓我們可以訪問多種啟用函式(參考http://pybrain.org/docs/api/structure/modules.html)。.supervised.trainers模組為我們的網路提供了監督方法(http://pybrain.org/docs/api/supervised/trainers.html)。最後一個,.tools.shortcuts,允許我們快速構建網路。
在這個例子中,我們構建一個簡單的單隱藏層網路。不過,在做這個之前,我們要先準備好資料集:
def prepareANNDataset(data, prob=None): ''' Method to prepare the dataset for ANN training and testing ''' # we only import this when preparing ANN dataset import pybrain.datasets as dt # supplementary method to convert list to tuple def extract(row): return tuple(row) # get the number of inputs and outputs inputs = len(data[0].columns) outputs = len(data[1].axes) + 1 if prob == 'regression': outputs -= 1 # create dataset object dataset = dt.SupervisedDataSet(inputs, outputs) # convert dataframes to lists of tuples x = list(data[0].apply(extract, axis=1)) if prob == 'regression': y = [(item) for item in data[1]] else: y = [(item,abs(item - 1)) for item in data[1]] # and add samples to the ANN dataset for x_item, y_item in zip(x,y): dataset.addSample(x_item, y_item) return dataset
我們假設,輸入資料是兩個元素組成的元組:第一個元素是帶有所有自變數的DataFrame,第二個元素是帶有因變數的pandas序列結構。
你可以將序列看成一個DataFrame的單列。
在我們的方法中,我們首先匯入pybrain.datasets。我們採用這種方式,是為了當我們不需要在指令碼中使用這方法時,不用將模組裝載到記憶體中。
然後我們決定網路有多少輸入和輸出。輸入的數量就是我們輸入資料集的列數。而輸出的數目,如同前面所說,是我們因變數的層數。我們用.SupervisedDataSet(...)建立了ANN資料集的骨架。x物件中有所有的輸入觀測值,y物件中有我們的目標變數;這兩個結構是元組構成的列表。要建立x,我們用extract(...)方法將資料(以列表傳入)轉換成元組;要構建訓練網路的資料集,這一步是必要的。我們使用DataFrame的.apply(...)方法,將.extract(...)方法應用到DataFrame的每一行上。
extract(...)方法只可由prepareANNDataset(...)方法中的物件訪問;你不能在printModel Summary(...)這種方法中使用。
y物件中也有一個元組列表。y中的元組以這種取反的方式建立:如果第一個元素是0,那麼另一個元素就是1;要達到這樣的效果,我們使用一個簡單的數學小技巧,(item,abs(item-1)),即,如果客戶不申請信用卡,那麼我們的目標變數就是0,減去1(得到-1)並取絕對值(得到1)。本質上,我們是給“客戶不申請信用卡”這個事件設定了一個為真的標誌變數。
這樣過一遍之後,我們可以使用.addSample(...)方法,將觀測值新增到最終的資料集中。.addSample(...)方法接受的引數是輸入和目標變數構成的元組。
既然準備好了資料集,我們便可以訓練網路了。我們的.fitANN(...)方法輸入資料集,先決定輸入和目標神經元的數目;我們使用SupervisedDataSet輸入和目標物件的.shape屬性來獲取列的數目。
然後建立真正的ANN。我們使用PyBrain中內建的一個快捷方法:.buildNetwork(...)方法。第一個匿名引數是輸入層神經元的數目,第二個是隱藏層神經元的數目,第三個,在我們的例子中,是輸出層神經元的數目。
buildNetwork方法可接受任意數目的隱藏層。這個方法將最後一個匿名引數作為輸出層神經元的數目。
我們也指定隱藏層和輸出層的啟用函式:hiddenclass引數為隱藏層指定了TanhLayer,outclass引數指定了SoftmaxLayer。tanh函式將輸入壓縮到0和1之間的範圍,曲線形狀和S函式相似。
選取啟用函式時,tanh優於S函式,原因超出了本書範圍。可以參考這篇論文:http://yann.lecun.com/exdb/publis/pdf/lecun-98b.pdf。
最後一個引數是偏差。設為true時,細胞體中的求和函式會包括一個訓練時調整的常數項。想想線性函式的形式:y=AX+b,A是輸入的權重向量,X是輸入變數的向量,b就是偏差。
既然定義好了網路,我們便需要指定訓練的機制。我們使用反向傳播演算法訓練網路。.BackpropTrainer(...)方法接受我們新建立的網路作為第一個引數。而我們之前建立的資料集是第二個引數。我們還指定了兩個屬性:詳細模式以追蹤訓練的進度,並且關閉了批次學習。關閉批次學習讓訓練處於線上模式;線上模式在每一次觀測之後都更新權重和神經元的引數。與此相反,批次學習在每次訓練迴圈(迭代)後才將更新應用到網路結構上。
訓練迴圈,即迭代,是將訓練資料集中所有觀測值在網路中過一遍的週期。
現在就是要訓練網路了。在新建立的訓練者物件上,我們呼叫.trainUntilConvergence(...)方法。
你可以用.train()方法訓練一個迭代,也可以用.trainEpochs(...)方法訓練多個迭代。更多細節參考http://pybrain.org/docs/api/supervised/trainers.html。
這個方法一直執行到收斂為止,此時,再來一次迭代也不會給訓練集或驗證集帶來更好的結果,或者達到了maxEpochs數目。我們可以給validationProportion賦值0.25,這意味著我們用訓練資料集的四分之一來驗證我們的模型。
驗證資料集是訓練資料集的一個子集,不會用來訓練網路。ANN訓練的首要目標是將網路的輸出和目標變數之間的誤差最小化。然而,這樣可能導致這種場景,模型完美適應每一個訓練觀測值(也就是說,網路的誤差為0),但是不能很好地泛化(參考https://clgiles.ist.psu.edu/papers/AAAI-97.overfitting.hard_to_do.pdf)。所以,為了避免過擬合,網路追蹤與驗證資料集之間的誤差;當誤差開始增大時,訓練終止。
在我們的訓練方案中,我們將continueEpochs設為3,這樣當訓練者看到與驗證資料集之間的誤差開始上升後,它還會繼續迭代3次才終止。這是考慮到網路找到了一個區域性的最小值,再經一兩個迭代後,誤差會在上升後再次回落。
訓練好網路之後,我們現在可以預測歸類了:
# train the model 訓練模型 classifier = fitNaiveBayes((train_x, train_y)) # classify the unseen data predicted = classifier.predict(test_x)
.activateOnDataset(...)方法輸入測試資料集,生成一個預測;對測試資料集中的每個觀測值,網路啟用並生成一個結果。預測的物件現在有兩個值的輸出;我們想找出最小值的下標,作為我們的歸類。我們使用.argmin(...)方法得到這個結果。
由於結構比之前介紹的模型都要複雜,ANN的估算要多花些時間。在我們的例子中,與之前介紹的SVM模型相比,神經元網路表現更好,但是顯著地慢:
另外,之前介紹的模型,我們可以分析係數,但對ANN來說卻不容易。除非是一個很簡單的網路,否則網路的引數難於解釋。神經網路經常以黑盒形式使用:給它一個輸入,吐給你一個輸出,但你沒法評估它是怎麼做的。
我不是暗示你總是使用更簡單的模型——我的觀點遠非如此。有些領域,設計顯式的模型會比設計和使用ANN要複雜得多,神經元網路在這些領域已經獲得巨大的成功。比如,語音識別和影像識別的模型,如果採用顯式的方式,要理解模型的每個元件以及元件如何影響輸出,這會極度複雜。如果這些顯式資訊並不是必需的,ANN就很好用。
有了PyBrain,我們可以構建更復雜的網路。這個例子中,我們構建了兩層隱藏層的ANN:
# create the classifier object ann = pb.buildNetwork(inputs_cnt, inputs_cnt * 2, inputs_cnt / 2, target_cnt, hiddenclass=st.SigmoidLayer, outclass=st.SoftmaxLayer, bias=True
這個構建的網路有兩層隱藏層:第一個有20個隱藏神經元,第二個有5個。
建立和估算一個更復雜的模型,花費的投資並不會白費——與簡單的相比,估算的時間將近有兩倍,表現還更差
/* The method fitANN took 794.88 sec to run. Overall accuracy of the model is 91.17 percent Classification report: precision recall f1-score support 0.0 0.94 0.96 0.95 11968 1.0 0.63 0.52 0.57 1526 accuracy 0.91 13494 macro avg 0.79 0.74 0.76 13494 weighted avg 0.91 0.91 0.91 13494 Confusion matrix: [[11513 455] [ 736 790]] ROC: 0.7398376338650557 */
要解釋神經元網路多種結構的細節,遠遠超出了本書的範圍。在本技巧的介紹中,我們試著勾勒出大致結構,這樣你會對模型原理有一個更好的理解。要是有讀者對人工神經元網路感興趣,又有數學功底,原作者強烈推薦閱讀Simon O.Harkin的《Neural Networks and Learning Machines》,http://www.amazon.com/Neural-Networks-Learning-Machines-Edition/dp/0131471392。
第3 章完。
隨書原始碼官方下載:
http://www.hzcourse.com/web/refbook/detail/7821/92