一步步帶你實戰資料科學專案流程全攻略(下)

A Step-by-Step Guide For Data Science Project II.

學.誌|Chris Kang
21 min readJan 27, 2021
Photo by Clay Banks on Unsplash

下篇其實比上篇難寫的多,特別在特徵工程(Feature Engineering)甚至都可以寫成一本書。因此本文仍然抱持著以新手能夠瞭解完整的框架為目的,來完成這系列的文章。

本文會銜接上篇,含括後三個範疇:

  1. 定義問題/敘述
  2. 區分與整理 Train/Test 的資料集
  3. 進行資料的 EDA
  4. 進行假說設計/設定
  5. 清洗/整理資料
  6. 模型建置與預測
  7. 視覺化/報告撰寫/結果分析

尚未閱讀上篇的讀者,可以先到 如何快速上手資料科學專案流程(上) 來閱讀過會更容易理解整體的脈絡。

對於看待本文的心態,可以以瞭解整體的架構來代替一步一步的 Code 撰寫,因為每一個專案需要應用到的技術都不同,因此更需要以經驗來累積實戰的技巧。那麼話不多說,就讓我們繼續吧!

清洗/整理資料(Data wrangling)

上面的資料角力(Data Wrangling)其實是筆者最近才看到的用詞,但基本上不脫離 ETL (Extraction, Transform, Loading) 的範疇,只是更潮一些。

特別在處理與特徵相關的資料時,我們更習慣稱這個過程為特徵工程(Feature Engineering)

因為特徵工程含括的範圍實在太大,因此筆者直接推薦在我成長的過程中視為聖經拜讀的書 機器學習:特徵工程 。裡面幾乎含括大多數在特徵工程時會使用到的工具,簡單至如分箱法、複雜至文本處理的 TF-IDF,甚至 PCA、Autoencoder 等皆有,簡直包羅萬象。

整個特徵工程通常可以分成三個階段:

  1. 檢查資料與確認缺失
  2. 選擇模型或方法
  3. 進行資料加工

為了清晰地說明特徵工程的過程,我以我過去在 Kaggle 競賽的 Code 作為示範,會讓大家更容易瞭解這整個過程。

1. 檢查資料與確認缺失

在這個過程中,通常都會結合前面資料視覺化和 EDA 來檢視。這裡筆者就提供幾個在實務上很容易遇到的問題,以及可以如何應對。

對於一筆經過簡單 EDA 的資料,我們通常會檢查這幾件事:

  1. 是否有缺失值(Missing value)
  2. 是否有極值或異常值(Outliner)
  3. 資料分布是否符合常態(Normal Distribution)
  4. 資料是否需要清洗或變換(Clean Data)
  1. 是否有缺失值(Missing value)

這個問題通常都會使用 df.info() 來檢視是否有 NaN 空值。或者進一步使用下面的 Code 來一次檢驗。

我刻意把資料存進去 List 裡面,讓資料可以直接列出總共有哪一些欄位有遺失值,以及個別遺失值的比例有多少。個人認為這段 Code 真的很好用。

2.是否有極值或異常值(Outliner)

在察看極值或異常值時,通常會以兩個簡單的工具來察看,分別是:

  • df.head() / df.tail()
  • df.info()
  • df.describe()
  • df.describe(include=’object’)

第一個可以直接檢視前面頭 5 筆,我有時為了避免漏看一些資料,也會同時看 tail 來確認資料的狀態。

第二個 df.info() 可以直觀地察看不同欄位的缺值,可以注意的值有:

  • 資料總列數
  • 資料欄位
  • 資料型態
  • 資料缺失

通常這時我們還會記錄哪一些資料可以轉換,將資料型態轉換成 Categoricalint8 等,可以縮小整個資料集的大小,讓運算速度顯著提升。

第三、四個 df.describe() ,則是可以快速瞭解數值資料的平均、眾數、標準差、最大、最小、四分位距等資料,能供我們快速瞭解資料的分布與進行一些非常初步的假設。舉幾個我處理過的狀況,例如在資料中 age 欄位最大值到 2014 明顯不合理,很有可能有資料誤植的問題;性別完全沒有缺失,但最多的類別竟然是 unknown,等等必須先進行進一步的 EDA 來瞭解這些資料的分配是否合理。

3.資料分布是否符合常態(Normal Distribution)

通常為了檢視資料的分布,我們都會簡單的使用 Matplotlib 快速繪圖出來。檢查的目的一樣可以考慮:

  • 資料的分布狀況(Distribution Type)
  • 是否有缺值(Missing value)
  • 是否有稀疏資料的狀況(例如很多個 1 或 0)

對於何種分配,非常推薦可以直接參考 Distribution Gallery,直接查找還有相關的定義可以參照。

Plot all the feature to see these distribution

如果遇到資料分布不均,例如長尾分布(這在很多電商的 Log 中都會遇到),亦即對大多數的用戶都只有購買一些商品,而有少部份的用戶購買非常多的商品,這時候分配就會呈現偏差,對於模型的預測就會產生偏差,特別是在線性回歸(Linear Regression)這類型以 RMSE(root-mean-square error)為損失函數(Loss function)的模型影響更是嚴重。

這時就可以使用欠採樣(Undersampling)或過採樣Oversampling,如 Bootstrap 或 SMOTE 等技術,將資料補成不同類別的資料相近。或考慮以較進階的技巧如矩陣值分解(Matrix Factorization)、奇異值分解等方式來拆解成較小的矩陣相乘,具體可以參考論文 Factorization Machines 一文。

附註:損失函數(Loss Function)是指評估這個模型的預測能力好不好,預測能力越差,則 RMSE 就越大(誤差越大)。

4.資料是否需要清洗或變換(Clean Data)

有些資料則是需要切分成年、月、日等不同欄位,這時在前面以 df.head() 檢視後,就可以記錄成代辦清單來處理

提前給大家一個範例,在確認所有要處理的欄位後,下方的 Python code 就是將原本的時間標籤轉換成年、月、日三個欄位,來作為跑模型的新標籤。

2. 選擇模型或方法

如果有缺失值(Missing value)通常就會需要插值來補齊。這裡僅列出常用的兩個差值法:

  • 簡單插值法(Simple Imputer using mean, median, etc.)
  • K 最近鄰差值法(KNN Imputer)

還有很多不同的差值法可以使用,如馬可夫蒙地卡羅,甚至在生成新的時間區段,如原本沒有僅有每個月的資料,可以利用 Markov-Monte Carol Model 來生成每一天的資料,有興趣的讀者可以自行查閱。

Fill missing value with Mean and KNN

3. 進行資料加工

其實在真正執行時,筆者會習慣將要進行特徵工程的資料,都先列成代辦清單再一次逐步執行。原因是當我們的資料有數百個特徵時,如果單純依靠記憶力一定會忘記特定的資料到底有沒有處理。

例如處理到 Age 時,就忘記有沒有處理地址等等,還要滑回去看哪一些特徵已經被處理、處理到那個階段,這會讓處理的過程變的非常沒有效率。這個部分可以參考上一篇 新手如何快速上手資料科學專案流程全攻略?(上) 來瞭解概念,下面就只提一些我過去曾列出的紀錄,我會記錄的要點有:

哪些欄位、有什麼 Insight、觀察與推論到什麼、預期做什麼轉換、流程順序為何

  • 發現在 ActionAction_typeTime_elaspsed 等計算的比例資料的分布皆為右偏分布且沒有負值,這時可以考慮使用 COX-BOXYeo-Johnson 來進行資料轉換。
  • Age 有一些資料高達 1970 以上,推測是把年齡的欄位以西元年來填寫,因此等等可以針對這些資料透過 ApplyMap 來映射轉換。

Small but useful Tips:

如果 Train 和 Test 的特徵大部分都可以接合在一起處理,則可以考慮直接使用 Concat 的方式將資料整理在一起處理:

在真的開始動手處理資料之前,建議將原始資料以 DataFrame.copy() 的方式來複製出一份新的資料,避免重新讀取原始檔過久。以我在 Airbnb New User Prediction 的比賽中,因為數據有 Train.csvsession_log.csv 兩個檔案,我處理 session 的特徵到最後,甚至曾經因為直接在原本的 dataframe 上處理資料失誤,而需要重新讀取整份 Notebook 超過 10 次的窘境。

另外,資料如果過於離散,也需要進行分類或分箱。舉個實際的應用例子,例如年齡如果在類似的年齡層(例如 20~25 歲)的行為相似;但實際上輸入的年齡確有不同的數值,這可能導致模型無法準確地預測結果,就需要進行下列的特徵轉化,來降低資料的殘差對模型的影響。

以就下列出常用的三種方式:

  • 特徵離散化(分箱法)
  • 稀有類處理(Back-off)
  • 分箱計數

特徵離散化和稀有類處理這兩種方式,是在資料類別過多的情況下,可以處理散列或是資料的殘差很大問題;但缺點就是難以保證精確度,因為資料卻實有可能被錯誤分類,導致原本的資料狀態被模型錯誤解讀。

而分箱計數則可能有冷啟動的問題,亦即沒有歷史資料就無法對類別進行分類。因此沒有所謂最完美的方式,只有最合適的方式。

模型建置與預測(Modeling and Prediction)

終於來到整個專案中,聽起來最酷炫的地方了。但這一篇文章主要的目的不在於帶到最酷炫的模型建置技巧如 Stacking 等,而是以簡潔但完整的方式來說明在建置模型會經歷的階段。

以下是一般來說建立模型時會經歷的階段:

  • 模型選擇(Model Selection)
  • 特徵預處理(Preprocessing)
  • 準備訓練/測試集(Train / Test Split)
  • 訓練與評估模型(Model Training)

在一次比較完整的模型建立過程中,幾乎都會遇到上面四個步驟;但這並不代表每一個步驟都一定會被執行。換句話說,如果模型在一開始就已經是完整的特徵,那可能就不會經歷到特徵預處理(Preprocessing)。

因此實際上會經歷的流程還是必須根據專案來調整。那麼我們就來一一瞭解每個步驟分別在做什麼吧!

模型選擇(Model Selection)

這個步驟很吃經驗,也很吃對於問題的判斷。什麼意思呢?這裡舉一個簡單的例子:

「預測明天會不會下雨?」

這個問題在乍看之下是個分類問題,分成會下雨和不會下雨;但如果把題目稍稍地變一下,變成「預測明天會不會下雨的機率」是不是就變成一個回歸問題了。除此之外,還有許多資料本身的情境,也會決定要使用什麼模型。

舉另外一個例子,如果在一個電商平台上,所有用戶的行為資料集很完整,也許就可以考慮使用 XGBoost 來跑分類以進行產品推薦。

然而,如果用戶的資料非常的稀疏,例如整個商場上有十萬種商品,而每個用戶僅購買數十種,這時候可能就必須使用到矩陣值分解和協同過濾的演算法,來避免在進行一般的分類演算法時,有許多資料缺失(因為多數商品都沒有購買)而難以計算的狀況。

這裡附個 Scikit-Learn 上一張基礎且直覺的分類圖,我自己認為在初期判斷問題時,能夠很快地瞭解可能可以使用哪一些演算法。

From Scilit-learn official website

在模型選擇上,也要注意所選的演算法能夠吃下哪一些特徵種類,因為這會影響下階段的特徵預處理。這裡舉個例子,例如邏輯斯回歸(Logistic Resgession)就無法直接使用類別資料,必須轉換成 Dummy variable;但決策樹則不用(Decision Tree),可以直接讀取類別資料進演算法。

特徵預處理(Preprocessing)

通常在特徵預處理有常見的一些方式,這裡就舉出常用的類別特徵,需要進行的預處理(其實筆者目前也只有用過這兩種)。

  • Label Encoder
  • One-Hot Encoder

Label Encoder 通常會使用在「區間尺度的特徵」和「應變數」的轉換。例如用戶對於 App 的評分可能從非常不喜歡到非常喜歡,這時我們就會使用 Label Encoder 將其轉換成 1~5 的種類。

下面直接以 Code 來展示:

Label Encoder:

而對於模型來說,數值月大通常也代表越滿意,因次對於模型來說更容易解釋與預測。而「應變數」則無此限制,例如預測的結果可能是 10 個國家的類別變數,以 XGBoost Classifier 來說模型僅能輸出數字,因此必須使用 Label Encoder 來轉換,模型才能計算。

Label Encoder

One-Hot Encoder:

One-Hot Encoder 則通常使用在回歸類型的模型,這個轉換會將模型轉換成多個特徵,而每一個特徵則會佔其中一列。

然而,One-Hot Encoder 也有一個很致命的問題,就是當類別種類特別多的時候,可能導致特徵暴增,這時候就要考慮是否使用前面特徵工程的資料轉換成分箱或是裝箱計數等方式,來降低資料維度。

One-Hot Encoder

Label Encoder 和 One-Hot Encoder 其實在數學上還有自由度的問題,One-Hot 的自由度會被 0 所佔滿,所以得到的答案會有唯一解;而 Label Encoder 則因為少了一個變數來固定自由度,因此答案並非唯一解,這在考慮使用時也可以注意。

準備訓練/測試集(Train / Test Split)

在把所有的資料都準備好後,接下來最重要的便是驗證模型的效益;但 Test data 並不會有答案可以讓我們對照,那要怎麼辦呢?這時我們就需要使用 Cross Validation 來驗證模型的效益,以及檢驗模型是否有過擬合(Overfitting)的問題。

在進行交叉驗證的資料準備前,需要先將特徵矩陣(自變數)與應變數區分出來,如果是無監督學習則不用。

接下來,就來簡單地說明如何使用 Sklearn 的 train_test_split 套件,來區分訓練集與測試集:

這裡簡單說明裡面的參數:

  • dataX, dataY|預處理的 train_X 和 train_Y
  • test_size|想要用來測試的資料集比例
  • random_state|能讓分類的方式形成偽隨機,確保每一次跑出來的結果都相同。
  • Stratify|讓 data_Y 的整體比例和每一個驗證資料集都相同。

如果我們想要多次的 K-fold Cross-validation,則可以使用下列的方式:

我們可以看到在每一個回圈中,我們都把資料分成 K 等分,每一次檢驗的時候都拿其中的一份當作測試集,其他的就混合在一起當作訓練集,並檢視測試集與訓練集的 loss 來理解模型是否有過擬合或欠擬合的現象。

透過 K-fold 的 module,將資料儲存到 X_train_foldX_cv_fold 當中,讓我們可以針對不同份的資料進行建模與評估。這裡因為使用的是 XGboost,所以也把 DMatrix 的轉換加進去,可以加速演算法的計算速度。

更嚴謹的模型驗證方式:Train/Validation/Test

最近在觀看 Andrew Ng 的 ML 課程時,發現其實有另一種更嚴謹的驗證方式,便是將數據分成:Train、Validation、Test 三等分。

該方式乍看之下和上面的 K-fold 似乎重疊;但實際上更為嚴謹。完整的驗證流程如下:

  1. 純粹以 Training Set 當作訓練資料集。
  2. 純粹以 Validation Set 表現最好為判斷的依據。
  3. 最後以 Testing Set 作為模型泛化表現的依據。

如果以抽象的概念來說,你可以把 Training Set 當作是學校的課本,而 Validation Set 當作是校內的段考,而 Testing Set 則更像是學測和職考,最後來一次大驗收。常理之下,在書本內容不變的情況下,我們都會根據校內的段考來優化我們唸書的方式(亦即調參)。最後才會在大考(Testing Set)中去檢視誰真的有把書中的內容念熟(模型泛化能力好)。

而個別的比例,通常會是 Train:Validation:Test 為 60%:20%:20%,當然如果模型是以 DNN 來進行,Validation 和 Testing Set的比例可能就可以下降到 10%~15%。

High Bias v.s. High Variance

在選擇模型時,要如何評斷該模型是否最優呢?只要透過判斷 Train Set 和 Validation Set 的差異,就可以知道現在模型是否有 OverfittingUnderfitting 的問題。

  • High-Bias| Train 和 Val 的 Loss 都很高,模型 Under-fitting 了。
  • High-Variance|Val 的 Loss 遠高於 Train,模型 Over-fitting 了。
Image from Andrew Ng- ML Course

訓練與評估模型(Model Training)

將資料區分好後,接下來就是最重要的跑模型與檢驗模型的效益了。這裡接續使用上面的 Code 來繼續說明。在我們使用了 K-fold validation 後,這裡以 GridSearch 來進行暴力測試,以檢視不同的超參數(Hyperparameter)設定下模型的成效。

接著,我們將預測出來的結果放到我們原本初始化好的空 List 當中,最後再一次列印出來以檢視模型的預測狀況。

之後就會跑出下面這樣的結果,我們就可以來看看對於不同的 CV 來說,模型有沒有過擬合的問題。

以下面的結果來說,可以看到模型很明顯有過擬合的問題,例如第一組的 Train log loss 僅有 0.0008836359029379572;但 CV 的 Log loss 卻高達 0.0039039689005126712。因此在超參數的微調上還需要進行剪枝,以避免模型實際上在測試集的表現不佳。

在瞭解模型在測試集的預測程度後,通常會根據這個數字來進行超參數的調教,例如限制樹的深度或是枝葉的總數避免過擬合。

在檢驗模型的預測力之後,我們就要將這個模型進一步評估他在實際上的預測能力與分類域值的設定。

二元類別(Binary-Classification)的分類評估:

如果針對二元分類來說,最常使用的便是 Precision-Recall Curveauc-ROC (Area Under the Curve of the Receiver Operating Characteristic) 這個評估方式。這裡一樣以實際的 Code ,來展示如何檢視 Precision-Recall Curve 的分數:

Precision-Recall Curve:

Precision-Recall Curve

Precision-Recall Curve 是兩組分別衡量不同面向的評估指標。Precision 指的是「對於所有預測為真的結果,有多少比例為真」,屬於比較悲觀的預測指標;Recall 則是指「對於所有為真的結果來說,有多少的比例為真」。如果我把所有的結果都判斷為真,那 Recall Rate 就為直接為 100%;但 Precision 就會超級低,反之亦然。

為了解決這個問題,便發明了 F1-score 來平衡 Recall rate 和 Precision Rate 兩者。計算的方式如下:

藉由讓兩者都盡可能地保持最高,避免了只採用其中一項而導致整體的預測效果下降的問題。

至於 Precision-Recall Curve 要如何設定最佳域值呢?只要曲線越接近右上角,預測的效果就越好,代表模型分的很精確。也可以參考上述的 F1-score 來衡量整體的預測能力,找到最低點的衡量值。

Auc_ROC (Area Under the Curve of the Receiver Operating Characteristic):

auc-ROC

可以看到上圖有兩條件,紅色的線就是 ROC Curve,而虛線則是如果隨機猜測的準確程度。對於準確程度,我們通常會以混淆矩陣(Confusion Matrix)來檢視是否正確分類:

Confusion Matrix from Wikipedia

其目的就是為了檢驗在不同的閾值(Threshold)下,左上的 TP (True Positive) 和右上的 FP (False Positive) 的變化,以期找到一個在商業上可以接受的標準。

舉個例子,如果我想要設計一個垃圾信件過濾器,如果不小心把垃圾信件放到一般郵件裡的損失比較小;但如果把一般信件不小心放到垃圾信件,對用戶的損失就會很大,因此就會希望讓 TP 盡可能地大的情況下,也能盡量讓 TF 盡可能地小,甚至因此犧牲一些 TP 的比例都沒關係。

調整參數(Param Tuning)與特徵工程(Feature Engineer)

通常到這個階段如果成效不好,就會回到 Feature Engineering 的階段來思考是不是漏掉或能夠新增什麼新的特徵,通常也會建議詢問該領域資深經驗的人,通常都能夠獲得許多對模型有幫助的特徵,或是有自己沒考慮到的特徵可以進行轉換或生成。

另一部份,如果已經想不到有什麼特徵可以調整,就會開始調整整個模型的超參數,這就是大家常常稱的調參過程。過程通常都是漫長而無止盡的等待(當資料超大或訓練類神經網路時),就很有可能一整天的工作就是換參數、等結果、換參數、等結果…

話說回來,這篇是屬於完整地說明一個資料科學的專案如何進行,因此並不會花太多時間在說明超參數如何調整。而且不同的模型調參狀況,也會因為狀況不同而有所差異,因此筆者會再另闢一篇來寫。

視覺化/報告撰寫/結果分析

假設我們非常完美地設計出一個預測能力不錯的模型,通常就是要讓這個模型被實際運用在產品或是報告中了。這時通常就會遇到兩個狀況:

  • 該模型需要實際部署到產品上,因此需要資料工程師的協助。
  • 該模型僅使用分析的結果來預測或建議,因此需要製作報告。

第一個情境,也是我覺得資料科學家或多或少需要懂一些演算法部署技巧的原因。就算我們寫出一個很好的模型,實際部署時還是需要使用到如 Docker、AWS CodePipeline 等來串接與發布模型。

第二個情境下,我通常都會嘗試以數據敘事(Data Storytelling)來思考這個模型能夠如何說服利害關係人。

有時候分析結果有效,並非數據本身就提供洞見,而是分析的結果順服的了人。

除了以故事的方式來整理數據外,我通常也會以「心理學」的角度去看待這些數據,畢竟對於大多數的資料來說,產生者還是活生生的人啊。有時候真的很難想像這是一群什麼樣的使用者時,設計思考的訪談技巧與 POV 就能夠更進一步地定義使用者的狀態與問題。

因此,我一直深信所謂的資料科學並不是因此要取代質化分析,兩者在過程中一直以來都是相輔相成的。也唯有如此,分析的結果才能深入人心,讓利害關係人知道所謂的分析結果,不只是冷冰冰的數字。

這系列文章的總結

終於把上下兩篇寫完了,一開始覺得已經有一些練習專案的經驗,只是整理出來應該不難吧。但實際開始寫之後,發現許多細節都有意想不到的關連。

也確實,在寫這篇文章的過程中不可能包山包海,把所有的概念全部都含括在內,也會有一些疏漏或是筆誤。因此也希望讀者能不吝指教,文中的把問題點出來一起交流。

我認為在寫這篇文章之前,我其實並不真的完全理解整個分析的流程與概念,直到我逐漸一字一句地把內容寫出來、把程式碼重新梳理一遍,我才瞭解原來寫這些 function 的目的是為什麼,以及為什麼要這麼說、這麼做。

希望這兩篇文章能夠對剛踏入資料科學的人有幫助,謝謝你花時間把整篇文章讀完,也歡迎有興趣的讀者能夠留言一起交流成長。

--

--

學.誌|Chris Kang
學.誌|Chris Kang

Written by 學.誌|Chris Kang

嗨!我是 Chris,一位擁有技術背景的獵頭,熱愛解決生活與職涯上的挑戰。專注於產品管理/資料科學/前端開發 / 人生成長,在這條路上,歡迎你找我一起聊聊。歡迎來信合作和交流: chriskang0917@gmail.com