一文搞定深度學習建模預測全流程 -Python-

本文詳細地梳理及實現了深度學習模型構建及預測的全流程,代碼示例基於 python 及神經網絡庫 keras,通過設計一個深度神經網絡模型做波士頓房價預測。主要依賴的 Python 庫有:keras、scikit-learn、pandas、tensorflow(建議可以安裝下 anaconda 包,自帶有常用的 python 庫)

一、基礎介紹

機器學習的核心是通過模型從數據中學習並利用經驗去決策。進一步的,機器學習一般可以概括爲:從數據出發,選擇某種模型,通過優化算法更新模型的參數值,使任務的指標表現變好(學習目標),最終學習到 “好” 的模型,並運用模型對數據做預測以完成任務。由此可見,機器學習方法有四個要素:數據、模型、學習目標、優化算法。具體可見系列文章:一篇白話機器學習概念

深度學習是機器學習的一個分支,它是使用多個隱藏層神經網絡模型,通過大量的向量計算,學習到數據內在規律的高階表示特徵,並利用這些特徵決策的過程。

本文基於 keras 搭建神經網絡模型去預測,keras 是 python 上常用的神經網絡庫。相比於 tensorflow、Pytorch 等庫,它對初學者很友好,開發週期較快。下圖爲 keras 要點知識的速查表(原圖文末閱讀原文後可見):

二、建模流程

深度學習的建模預測流程,與傳統機器學習整體是相同的,主要區別在於深度學習是端對端學習,可以自動提取高層次特徵,大大減少了傳統機器學習依賴的特徵工程。如下詳細梳理流程的各個節點並附相應代碼:

2.1 明確問題及數據選擇

2.1.1 明確問題

深度學習的建模預測,首先需要明確問題,即抽象爲機器 / 深度學習的預測問題:需要學習什麼樣的數據作爲輸入,目標是得到什麼樣的模型做決策作爲輸出。

以預測房價爲例,我們需要輸入:和房價有關的數據信息爲特徵 x,對應的房價爲 y 作爲監督信息。再通過神經網絡模型學習特徵 x 到房價 y 內在的映射關係。通過學習好的模型輸入需要預測數據的特徵 x,輸出模型預測 Y。對於一個良好的模型,它預測房價 Y 應該和實際 y 很接近。

2.1.2 數據選擇

深度學習是端對端學習,學習過程中會提取到高層次抽象的特徵,大大弱化特徵工程的依賴,正因爲如此,數據選擇也顯得格外重要,其決定了模型效果的上限。如果數據質量差,預測的結果自然也是很差的——業界一句名言 “garbage in garbage out”。

數據選擇是準備機器 / 深度學習原料的關鍵,需要關注的是:

①數據樣本規模:對於深度學習等複雜模型,通常樣本量越多越好。如《Revisiting Unreasonable Effectiveness of Data in Deep Learning Era 》等研究,一定規模下,深度學習性能會隨着數據量的增加而增加。然而工程實踐中,受限於硬件支持、標註標籤成本等原因,樣本的數據量通常是比較有限的,這也是機器學習的重難點。對於模型所需最少的樣本量,其實沒有固定準則,需要要結合實際樣本特徵、任務複雜度等具體情況(經驗上,對於分類任務,每個類別要上千的樣本數)。

② 數據的代表性:數據質量差、無代表性,會導致模型擬合效果差。需要明確與任務相關的數據表範圍,避免缺失代表性數據或引入大量無關數據作爲噪音。

③ 數據時間範圍:對於監督學習的特徵變量 x 及標籤 y,如與時間先後有關,則需要劃定好數據時間窗口,否則可能會導致常見的數據泄漏問題,即存在了特徵與標籤因果顛倒的情況。

以預測房價任務爲例,對數據選擇進行說明:

本節代碼

如下加載數據的代碼,使用的是 keras 自帶的波士頓房價數據集。一些常用的機器學習開源數據集可以到 kaggle.com/datasets、archive.ics.uci.edu 等網站下載。

from keras.datasets import boston_housing #導入波士頓房價數據集

(train_x, train_y)(test_x, test_y) = boston_housing.load_data()

波士頓房價數據集是統計 20 世紀 70 年代中期波士頓郊區房價等情況,有當時城鎮的犯罪率、房產稅等共計 13 個指標(特徵)以及對應的房價中位數(標籤)。

2.2 特徵工程

特徵工程就是對原始數據分析處理,轉化爲模型可用的特徵。這些特徵可以更好地向預測模型描述潛在規律,從而提高模型對未見數據的準確性。對於深度學習模型,特徵生成等加工不多,主要是一些數據的分析、預處理,然後就可以灌入神經網絡模型了。

2.2.1  探索性數據分析

選擇好數據後,可以先做探索性數據分析(EDA)去理解數據本身的內部結構及規律。如果你對數據情況不瞭解,也沒有相關的業務背景知識,不做相關的分析及預處理,直接將數據餵給模型往往效果不太好。通過探索性數據分析,可以瞭解數據分佈、缺失、異常及相關性等情況。

本節代碼

我們可以通過 EDA 數據分析庫如 pandas profiling,自動生成分析報告,可以看到這份現成的數據集是比較 "乾淨的":

import pandas as pd 
import pandas_profiling

#  特徵名稱
feature_name = ['CRIM|住房所在城鎮的人均犯罪率',
 'ZN|住房用地超過 25000 平方尺的比例',
 'INDUS|住房所在城鎮非零售商用土地的比例',
 'CHAS|有關查理斯河的虛擬變量(如果住房位於河邊則爲1,否則爲0 )',
 'NOX|一氧化氮濃度',
 'RM|每處住房的平均房間數',
 'AGE|建於 1940 年之前的業主自住房比例',
 'DIS|住房距離波士頓五大中心區域的加權距離',
 'RAD|距離住房最近的公路入口編號',
 'TAX 每 10000 美元的全額財產稅金額',
 'PTRATIO|住房所在城鎮的師生比例',
 'B|1000(Bk|0.63)^2,其中 Bk 指代城鎮中黑人的比例',
 'LSTAT|弱勢羣體人口所佔比例']

train_df = pd.DataFrame(train_x, columns=feature_name)  # 轉爲df格式

pandas_profiling.ProfileReport(train_df)

2.2.2 特徵表示

像圖像、文本字符類的數據,需要轉換爲計算機能夠處理的數值形式。圖像數據(pixel image)實際上是由一個像素組成的矩陣所構成的,而每一個像素點又是由 RGB 顏色通道中分別代表 R、G、B 的一個三維向量表示,所以圖像實際上可以用 RGB 三維矩陣(3-channel matrix)的表示(第一個維度:高度,第二個維度:寬度,第三個維度:RGB 通道),最終再重塑爲一列向量(reshaped image vector)方便輸入模型。

文本類(類別型)的數據可以用多維數組表示,包括:① ONEHOT(獨熱編碼)表示:它是用單獨一個位置的 0 或 1 來表示每個變量值,這樣就可以將每個不同的字符取值用唯一的多維數組來表示,將文字轉化爲數值。如字符類的性別信息就可以轉換爲 “是否爲男”、“是否爲女”、“未知” 等特徵。

②word2vetor 分佈式表示:它基本的思想是通過神經網絡模型學習每個單詞與鄰近詞的關係,從而將單詞表示成低維稠密向量。通過這樣的分佈式表示可以學習到單詞的語義信息,直觀來看語義相似的單詞其對應的向量距離相近。

本節代碼

數據集已是數值類數據,本節不做處理。

2.2.2 特徵清洗

本節代碼

從數據分析報告可見,波士頓房價數據集無異常、缺失值情況,本節不做處理。

2.2.3  特徵生成

特徵生成作用在於彌補基礎特徵對樣本信息的表達有限,增加特徵的非線性表達能力,提升模型效果。它是根據基礎特徵的含義進行某種處理(聚合 / 轉換之類),常用方法如人工設計、自動化特徵衍生(如 featuretools 工具):深度神經網絡會自動學習到高層次特徵,常見的深度學習的任務,圖像類、文本類任務通常很少再做特徵生成。而對於數值類的任務,加工出顯著特徵對加速模型的學習是有幫助的,可以做嘗試。

本節代碼

特徵已經比較全面,本節不再做處理,可自行驗證特徵生成的效果。

2.2.4  特徵選擇

特徵選擇用於篩選出顯著特徵、摒棄非顯著特徵。這樣做主要可以減少特徵(避免維度災難),提高訓練速度,降低運算開銷;減少干擾噪聲,降低過擬合風險,提升模型效果。常用的特徵選擇方法有:過濾法(如特徵缺失率、單值率、相關係數)、包裝法(如 RFE 遞歸特徵消除、雙向搜索)、嵌入法(如帶 L1 正則項的模型、樹模型自帶特徵選擇)。

本節代碼

模型使用 L1 正則項方法,本節不再做處理,可自行驗證其他方法。

2.3 模型訓練

神經網絡模型的訓練主要有 3 個步驟:

2.3.1 模型結構

常見的神經網絡模型結構有全連接神經網絡 (FCN)、RNN(常用於文本 / 時間系列任務)、CNN(常用於圖像任務)等等。

神經網絡由輸入層、隱藏層與輸出層構成。不同的層數、神經元(計算單元)數目的模型性能也會有差異。

對於模型結構的神經元個數 ,輸入層、輸出層的神經元個數通常是確定的,主要需要考慮的是隱藏層的深度及寬度,在忽略網絡退化問題的前提下,通常隱藏層的神經元的越多,模型有更多的容量(capcity)去達到更好的擬合效果(也更容易過擬合)。搜索合適的網絡深度及寬度,常用有人工經驗調參、隨機 / 網格搜索、貝葉斯優化等方法。經驗上的做法,可以參照下同類任務效果良好的神經網絡模型的結構,結合實際的任務,再做些微調。

2.3.2 激活函數

根據萬能近似原理,簡單來說,神經網絡有 “夠深的網絡層” 以及“至少一層帶激活函數的隱藏層”,既可以擬合任意的函數。可見激活函數的重要性,它起着特徵空間的非線性轉換。對於激活函數選擇的經驗性做法:

2.3.3 權重初始化

權重參數初始化可以加速模型收斂速度,影響模型結果。常用的初始化方法有:

2.3.4 批標準化

batch normalization(BN)批標準化,是神經網絡模型常用的一種優化方法。它的原理很簡單,即是對原來的數值進行標準化處理:batch normalization 在保留輸入信息的同時,消除了層與層間的分佈差異,具有加快收斂,同時有類似引入噪聲正則化的效果。它可應用於網絡的輸入層或隱藏層,當用於輸入層,就是線性模型常用的特徵標準化處理。

2.3.5 正則化

正則化是在以(可能)增加經驗損失爲代價,以降低泛化誤差爲目的,抑制過擬合,提高模型泛化能力的方法。經驗上,對於複雜任務,深度學習模型偏好帶有正則化的較複雜模型,以達到較好的學習效果。常見的正則化策略有:dropout,L1、L2、earlystop 方法。

2.3.6 選擇學習目標

機器 / 深度學習通過學習到 “好” 的模型去決策,“好”即是機器 / 深度學習的學習目標,通常也就是預測值與目標值之間的誤差儘可能的低。衡量這種誤差的函數稱爲代價函數 (Cost Function)或者損失函數(Loss Function),更具體地說,機器 / 深度學習的目標是極大化降低損失函數。

對於不同的任務,往往也需要用不同損失函數衡量,經典的損失函數包括迴歸任務的均方誤差損失函數及二分類任務的交叉熵損失函數等。

衡量模型迴歸預測的誤差情況,一個簡單思路是用各個樣本 i 的預測值 f(x;w) 減去實際值 y 求平方後的平均值,這也就是經典的均方誤差(Mean Squared Error)損失函數。通過極小化降低均方誤差損失函數,可以使得模型預測值與實際值數值差異儘量小。

衡量二分類預測模型的誤差情況,常用交叉熵損失函數,使得模型預測分佈儘可能與實際數據經驗分佈一致(最大似然估計)。另外,還有一些針對優化難點而設計的損失函數,如 Huber Loss 主要用於解決迴歸問題中,存在奇點數據帶偏模型訓練的問題。Focal Loss 主要解決分類問題中類別不均衡導致的模型訓偏問題。

2.3.7 選擇優化算法

當我們機器 / 深度學習的學習目標是極大化降低(某個)損失函數,那麼如何實現這個目標呢?通常機器學習模型的損失函數較複雜,很難直接求損失函數最小的公式解。幸運的是,我們可以通過優化算法(如梯度下降、隨機梯度下降、Adam 等)有限次迭代優化模型參數,以儘可能降低損失函數的值,得到較優的參數值。

對於大多數任務而言,通常可以直接先試下 Adam、SGD,然後可以繼續在具體任務上驗證不同優化器效果。

2.3.8 模型訓練及超參數調試

訓練模型前,常用的 HoldOut 驗證法(此外還有留一法、k 折交叉驗證等方法),把數據集分爲訓練集和測試集,並可再對訓練集進一步細分爲訓練集和驗證集,以方便評估模型的性能。① 訓練集(training set):用於運行學習算法,訓練模型。② 開發驗證集(development set)用於調整模型超參數、EarlyStopping、選擇特徵等,以選擇出合適模型。③ 測試集(test set)只用於評估已選擇模型的性能,但不會據此改變學習算法或參數。

神經網絡模型的超參數是比較多的:數據方面超參數 如驗證集比例、batch size 等;模型方面 如單層神經元數、網絡深度、選擇激活函數類型、dropout 率等;學習目標方面 如選擇損失函數類型,正則項懲罰係數等;優化算法方面 如選擇梯度算法類型、初始學習率等。

另外,有像 Keras Tuner 分佈式超參數調試框架 (文檔見:keras.io/keras_tuner),集成了常用調參方法,還比較實用的。

本節代碼

import numpy as np
import  matplotlib.pyplot as plt
%matplotlib inline
from tensorflow import random
from keras import regularizers
from keras.layers import Dense,Dropout,BatchNormalization
from keras.models import Sequential, Model
from keras.callbacks import EarlyStopping
from sklearn.metrics import  mean_squared_error

np.random.seed(1) # 固定隨機種子,使每次運行結果固定
random.set_seed(1)


# 創建模型結構:輸入層的特徵維數爲13;1層k個神經元的relu隱藏層;線性的輸出層;

for k in [5,20,50]:  # 網格搜索超參數:神經元數k
    
    model = Sequential()

    model.add(BatchNormalization(input_dim=13))  # 輸入層 批標準化 

    model.add(Dense(k,  
                    kernel_initializer='random_uniform',   # 均勻初始化
                    activation='relu',                     # relu激活函數
                    kernel_regularizer=regularizers.l1_l2(l1=0.01, l2=0.01),  # L1及L2 正則項
                    use_bias=True))   # 隱藏層

    model.add(Dropout(0.1)) # dropout法

    model.add(Dense(1,use_bias=True))  # 輸出層
    model.compile(optimizer='adam'loss='mse')
# 訓練模型
    history = model.fit(train_x, 
                        train_y, 
                        epochs=500,              # 訓練迭代次數
                        batch_size=50,           # 每epoch採樣的batch大小
                        validation_split=0.1,   # 從訓練集再拆分驗證集,作爲早停的衡量指標
                        callbacks=[EarlyStopping(monitor='val_loss'patience=20)],    #早停法
                        verbose=False)  # 不輸出過程  

    
    print("驗證集最優結果:",min(history.history['val_loss']))
    model.summary()   #打印模型概述信息
    # 模型評估:擬合效果
    plt.plot(history.history['loss'],c='blue')    # 藍色線訓練集損失
    plt.plot(history.history['val_loss'],c='red') # 紅色線驗證集損失
    plt.show()

最後,這裏簡單採用 for 循環,實現類似網格搜索調整超參數,驗證了隱藏層的不同神經元數目(超參數 k)的效果。由驗證結果來看,神經元數目爲 50 時,損失可以達到 10 的較優效果(可以繼續嘗試模型增加深度、寬度,達到過擬合的邊界應該有更好的效果)。

注:本節使用的優化方法較多(炫技 ing),單純是爲展示一遍各種深度學習的優化 tricks。模型並不是優化方法越多越好,效果還是要實際問題具體驗證。

2.4 模型評估及優化

機器學習學習的目標是極大化降低損失函數,但這不僅僅是學習過程中對訓練數據有良好的預測能力(極低的訓練損失),根本上還在於要對新數據(測試集)能有很好的預測能力(泛化能力)。

評估模型的預測誤差常用損失函數的大小來判斷,如迴歸預測的均方損失。但除此之外,對於一些任務,用損失函數作爲評估指標並不直觀,所以像分類任務的評估還常用 f1-score,可以直接展現各種類別正確分類情況。

查準率 P:是指分類器預測爲 Positive 的正確樣本(TP)的個數佔所有預測爲 Positive 樣本個數(TP+FP)的比例;查全率 R:是指分類器預測爲 Positive 的正確樣本(TP)的個數佔所有的實際爲 Positive 樣本個數(TP+FN)的比例。F1-score 是查準率 P、查全率 R 的調和平均:

注:如分類任務的 f1-score 等指標只能用於評估模型最終效果,因爲作爲學習目標時它們無法被高效地優化,訓練優化時常用交叉熵作爲其替代的分類損失函數 (surrogate loss function)。

評估模型擬合(學習)效果,常用欠擬合、擬合良好、過擬合來表述,通常,擬合良好的模型有更好泛化能力,在未知數據(測試集)有更好的效果。

我們可以通過訓練誤差及驗證集誤差評估模型的擬合程度。從整體訓練過程來看,欠擬合時訓練誤差和驗證集誤差均較高,隨着訓練時間及模型複雜度的增加而下降。在到達一個擬合最優的臨界點之後,訓練誤差下降,驗證集誤差上升,這個時候模型就進入了過擬合區域。

實踐中通常欠擬合不是問題,可以通過使用強特徵及較複雜的模型提高學習的準確度。而解決過擬合,即如何減少泛化誤差,提高泛化能力,通常纔是優化模型效果的重點,常用的方法在於提高數據的質量、數量以及採用適當的正則化策略。

本節代碼

# 模型評估:擬合效果
import  matplotlib.pyplot as plt

plt.plot(history.history['loss'],c='blue')    # 藍色線訓練集損失
plt.plot(history.history['val_loss'],c='red') # 紅色線驗證集損失

從訓練集及驗證集的損失來看,訓練集、驗證集損失都比較低,模型沒有過擬合現象。

# 模型評估:測試集預測結果
pred_y = model.predict(test_x)[:,0]

print("正確標籤:",test_y)
print("模型預測:",pred_y )

print("實際與預測值的差異:",mean_squared_error(test_y,pred_y ))

#繪圖表示
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 設置圖形大小
plt.figure(figsize=(8, 4)dpi=80)
plt.plot(range(len(test_y)), test_y, ls='-.',lw=2,c='r',label='真實值')
plt.plot(range(len(pred_y)), pred_y, ls='-',lw=2,c='b',label='預測值')

# 繪製網格
plt.grid(alpha=0.4, linestyle=':')
plt.legend()
plt.xlabel('number') #設置x軸的標籤文本
plt.ylabel('房價') #設置y軸的標籤文本

# 展示
plt.show()

評估測試集的預測結果,其 mse 損失爲 19.7,觀察測試集的實際值與預測值兩者的數值曲線是比較一致的!模型預測效果較好。

2.5 模型預測結果及解釋性

決策應用是機器學習最終目的,對模型預測信息加以分析解釋,並應用於實際的工作領域。

對於實際工作需要注意的是,工程上是結果導向,模型在線上運行的效果直接決定模型的成敗,不僅僅包括其準確程度、誤差等情況,還包括其運行的速度(時間複雜度)、資源消耗程度(空間複雜度)、穩定性的綜合考慮。

對於神經網絡模型預測的分析解釋,我們有時需要知道學習的內容,決策的過程是怎麼樣的(模型的可解釋性)。一個可以解釋的 AI 模型(Explainable AI, 簡稱 XAI)意味着運作的透明,便於人類對於對 AI 決策的監督及接納,以保證算法的公平性、安全性及隱私性,從而創造更加安全可靠的應用。深度學習可解釋性常用方法有:LIME、LRP、SHAP 等方法。

本節代碼

如下通過 SHAP 方法,對模型預測單個樣本的結果做出解釋,可見在這個樣本的預測中,CRIM 犯罪率爲 0.006、RM 平均房間數爲 6.575 對於房價是負相關的。LSTAT 弱勢羣體人口所佔比例爲 4.98 對於房價的貢獻是正相關的...,在綜合這些因素後模型給出最終預測值。

import shap 
import tensorflow as tf  # tf版本<2.0

# 模型解釋性
background = test_x[np.random.choice(test_x.shape[0],100, replace=False)]
explainer = shap.DeepExplainer(model,background)
shap_values = explainer.shap_values(test_x)  # 傳入特徵矩陣X,計算SHAP值
# 可視化第一個樣本預測的解釋  
shap.force_plot(explainer.expected_value, shap_values[0,:], test_x.iloc[0,:])

推薦關注「算法愛好者」,修煉編程內功

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ve6K2-6zc2rio6TiylIbeA