給你講講機器學習數據預處理中,歸一化(normalization)的重要性。

前情回顧

Previously, on 玉樹芝蘭 ……

我給你寫了一篇《如何用 Python 和 Tensorflow 2.0 神經網路分類表格數據?》,為你講解了 Tensorflow 2.0 處理結構化數據的分類。

結尾處,我給你留了一個問題

把測試集輸入模型中,檢驗效果。結果是這樣的:

model.evaluate(test_ds)

準確率接近80%,看起來很棒,對嗎?

但是,有一個疑問:

注意這張截圖。訓練的過程中,除了第一個輪次外,其餘4個輪次的這幾項重要指標居然都沒變

它們包括:

  • 訓練集損失
  • 訓練集準確率
  • 驗證集損失
  • 驗證集準確率

所謂機器學習,就是不斷迭代改進。

如果每一輪下來,結果都一模一樣,這裡八成有鬼。

我給了你提示:

看一個分類模型的好壞,不能只看準確率(accuracy)。對於二元分類問題,你可以關注一下 f1 score,以及混淆矩陣(confusion matrix)。

這段時間,你通過思考,發現問題產生原因,以及解決方案了嗎?

從留言的反饋來看,有讀者能夠正確指出了問題。

但很遺憾,我沒有能見到有人提出正確和完整的解決方案。

這篇文章,咱們就來談談,機器為什麼「不肯學習」?以及怎麼做,才能讓它「學得進去」。

環境

本文的配套源代碼,我放在了這個 Github 項目中。請你點擊這個鏈接(t.cn/ESJmj4h)訪問。

如果你對我的教程滿意,歡迎在頁面右上方的 Star 上點擊一下,幫我加一顆星。謝謝!

注意這個頁面的中央,有個按鈕,寫著「在 Colab 打開」(Open in Colab)。請你點擊它。

然後,Google Colab 就會自動開啟。

我建議你點一下上圖中紅色圈出的 「COPY TO DRIVE」 按鈕。這樣就可以先把它在你自己的 Google Drive 中存好,以便使用和回顧。

Colab 為你提供了全套的運行環境。你只需要依次執行代碼,就可以復現本教程的運行結果了。

如果你對 Google Colab 不熟悉,沒關係。我這裡有一篇教程,專門講解 Google Colab 的特點與使用方式。

為了你能夠更為深入地學習與瞭解代碼,我建議你在 Google Colab 中開啟一個全新的 Notebook ,並且根據下文,依次輸入代碼並運行。在此過程中,充分理解代碼的含義。

這種看似笨拙的方式,其實是學習的有效路徑

代碼

請你在 Colab Notebook 裏,找到這一條分割線:

用滑鼠點擊它,然後從菜單裡面選擇 Runtime -> Run Before

運行結束後,你會獲得如下圖的結果:

《如何用 Python 和 Tensorflow 2.0 神經網路分類表格數據?》一文的結果已經成功復現。

下面我們依次來解讀後面的語句。

首先,我們利用 Keras API 中提供的 predict 函數,來獲得測試集上的預測結果。

pred = model.predict(test_ds)

但是請注意,由於我們的模型最後一層,用的激活函數是 sigmoid , 因此 pred 的預測結果,會是從0到1區間內的小數。

而我們實際需要輸出的,是整數0或者1,代表客戶「流失」(1)或者「未流失」(0)。

幸好, numpy 軟體包裡面,有一個非常方便的函數 rint ,可以幫助我們四捨五入,把小數變成整數。

pred = np.rint(pred)

我們來看看輸出結果:

pred

有了預測輸出結果,下面我們就可以用更多的方法,檢驗分類效果了。

根據前文的提示,這裡我們主要用到兩項統計功能:

  • 分類報告
  • 混淆矩陣

我們先從 Scikit-learn 軟體包導入對應的功能。

from sklearn.metrics import classification_report, confusion_matrix

然後,我們對比測試集實際標記,即 test[Exited] ,和我們的預測結果。

print(classification_report(test[Exited], pred))

這裡,你立刻就能意識到出問題了——有一個分類,即「客戶流失」(1)裏,三項重要指標(precision, recall 和 f1-score)居然都是0!

我們用同樣的數據查看混淆矩陣,看看到底發生了什麼。

print(confusion_matrix(test[Exited], pred))

混淆矩陣的讀法是,行代表實際分類,列代表預測分類,分別從0到1排列。

上圖中,矩陣的含義就是:模型預測,所有測試集數據對應的輸出都是0;其中預測成功了1585個(實際分類就是0),預測錯誤415個(實際分類其實是1)。

也就是說,咱們費了半天勁,訓練出來的模型只會傻乎乎地,把所有分類結果都設置成0.

在機器學習裏,這是一個典型的笨模型(dummy model)。

如果咱們的測試集裡面,標籤分類0和1的個數是均衡的(一樣一半),那這種笨模型,應該獲得 50% 的準確率。

然而,我們實際看看,測試集裡面,分類0(客戶未流失)到底佔多大比例:

len(test[test[Exited] == 0])/len(test)

結果是:

0.7925

這個數值,恰恰就是《如何用 Python 和 Tensorflow 2.0 神經網路分類表格數據?》一文裡面,我們在測試集上獲得了準確率。

一開始我們還認為,將近80%的準確率,是好事兒。

實際上,這模型著實很傻,只有一根筋。

設想我們拿另外一個測試集,裡面只有 1% 的標註是類別0,那麼測試準確率也就只有 1% 。

為了不冤枉模型,咱們再次確認一下。

使用 numpy 中的 unique 函數,查看一下預測結果 pred 中,到底有幾種不同的取值。

np.unique(pred)

結果是:

array([0.], dtype=float32)

果不其然,全都是0.

果真是「人工不智能」啊!

分析

問題出在哪裡呢?

模型根本就沒有學到東西。

每一輪下來,結果都一樣,毫無進步。

說到這裡,你可能會有疑惑:

老師,是不是你講解出錯了?

兩周前,我在 UNT 給學生上課的時候,他們就提出來了這疑問。

我早有準備,立即佈置了一個課堂練習。

讓他們用這套流程,處理另外的一個數據集。

這個數據集你也見過,就是我在《貸還是不貸:如何用Python和機器學習幫你決策?》裡面用過的貸款審批數據。

我把數據放在了這個鏈接(t.cn/ESJ3x3o),你如果感興趣的話,不妨也試著用前文介紹的流程,自己跑一遍。

學生們有些無奈地做了這個練習。

他們的心理活動大概是這樣的:

你教的這套流程,連演示數據都搞不定,更別說練習數據了。做了也是錯的。是不是先糾正了錯誤,再讓我們練啊?

然後,當運行結果出來的時候,我在一旁,靜靜看著他們驚詫、沉思,以至於抓狂的表情。

同一套流程,在另外的數據上使用,機器確實學習到了規律

數據集的細節裡面,藏著什麼魔鬼?

歸一

直接說答案:

流程上確實有問題。數值型數據沒有做歸一化(normalization)。

歸一化是什麼?

就是讓不同特徵列上的數值,擁有類似的分佈區間。

最簡單的方法,是根據訓練集上的對應特徵,求 Z 分數。

Z 分數的定義是:

  • Mean 是均值
  • Standard Deviation 是標準差

為什麼一定要做這一步?

回顧一下咱們的數據。

我這裡用紅色標出來了所有數值特徵列。

看看有什麼特點?

對,它們的分佈迥異。

NumOfProducts 的波動範圍,比起 Balance 或者 EstimatedSalary,要小得多。

機器學習,並不是什麼黑科技。

它的背後,是非常簡單的數學原理。

最常用的迭代方法,是梯度下降(Gradient descent)。如下圖所示:

其實就是奔跑著下降,找局部最優解。

如果沒跑到,繼續跑。

如果跑過轍了,再跑回來。

但問題在於,你看到的這張圖,是隻有1維自變數的情況。

咱們觀察的數據集,僅數值型數據,就有6個。因此至少是要考察這6個維度。

不好意思,我無法給你繪製一個六維圖形,自己腦補吧。

但是注意,對這六個維度,咱們用的,卻是同一個學習速率(learning rate)。

就好像同一個老師,同時給6個學生上數學課。

如果這六個維度分佈一致,這樣是有意義的。

這也是為什麼大多數學校裡面,都要分年級授課。要保證授課對象的理解能力,盡量相似。

但假如這「6個學生」裏,有一個是愛因斯坦,一個是阿甘。

你說老師該怎麼講課?

愛因斯坦聽得舒服的進度,阿甘早就跟不上了。

阿甘能接受的進度,愛因斯坦聽了可能無聊到想撞牆。

最後老師決定——太難了,我不教了!

於是誰都學不到東西了。

對應到我們的例子,就是因為數據分佈差異過大,導致不論往哪個方向嘗試改變參數,都按下葫蘆浮起瓢,越來越糟。

於是模型判定,呆在原地不動,是最好的策略

所以,它乾脆不學了。

怎麼辦?

這個時候,就需要歸一化了。

對應咱們這個不恰當的舉例,就是在課堂上,老師要求每個人都保持每天一單位(unit)的學習進度。

只不過,愛因斯坦的一單位,是100頁書。

阿甘同學……兩行,還能接受吧?

新代碼

請你點擊這個鏈接(t.cn/ESJBJHW)訪問更新後的代碼。

按照之前的方式,點擊「在 Colab 打開」(Open in Colab)。使用 「COPY TO DRIVE」 按鈕,存放在你自己的 Google Drive 中。

對比觀察後,你會發現,改動只有1個代碼段落

就是把原先的數值型特徵採集從這樣:

for header in numeric_columns:
feature_columns.append(feature_column.numeric_column(header))

變成這樣:

for header in numeric_columns:
feature_columns.append(
feature_column.numeric_column(
header,
normalizer_fn=lambda x: (tf.cast(x, dtype=float)-train[header].mean())/train[header].std()))

尤其要注意,我們要保證平均值和標準差來自於訓練集。只有這樣,才能保證模型對驗證集和測試集的分佈一無所知,結果的檢驗纔有意義。否則,就如同考試作弊一樣。

這就是為了歸一化,你所需做的全部工作。

這裡我們依然保持原先的隨機種子設定。也就是凡是使用了隨機函數的功能(訓練集、驗證集和測試集的劃分等),都與更新代碼之前完全一致

這樣做,改變代碼前後的結果纔有可對比性

下面我們使用菜單欄裡面的 "Run All" 運行一下代碼。

之後查看輸出。

首先我們可以注意到,這次的訓練過程,數值終於有變化了。

因為其他變數全都保持一致。所以這種變化,沒有別的解釋,只能是因為使用了歸一化(normalization)。

我們更加關心的,是這次的分類報告,以及混淆矩陣。

分類報告是這樣的:

注意這一次,類別1上面的幾項指標,終於不再是0了。

混淆矩陣中,類別1裏,也有36個預測正確的樣本了。

成功了!

……

別急著歡呼。

雖然機器在學習和改進,但是效果好像也不是很好嘛。例如類別1的 Recall 簡直慘不忍睹。

有沒有什麼辦法改進呢?

這個問題,就需要你瞭解如何微調模型,以及超參數的設定了。

我把推薦的學習資料,放在了公眾號的對應文章裏,歡迎查看。

小結

這篇文章裏,我為你介紹了以下知識點:

  • 分類模型性能驗證(尤其是 Accuracy 之外的)評測指標;
  • 預處理過程中數值數據歸一化(Normalization)的重要性;
  • 如何在 Tensorflow 2.0 的數據預處理和特徵抽取中使用歸一化;
  • 如何利用模型預測分類結果,並且使用第三方軟體包功能快速統計彙報。

希望上述內容,能對你使用深度神經網路進行機器學習有幫助。

祝深度學習愉快!

延伸閱讀

你可能也會對以下話題感興趣。點擊鏈接就可以查看。

  • 如何高效學 Python ?
  • 《文科生數據科學上手指南》分享
  • 如何用 Python 和 fast.ai 做圖像深度遷移學習?
  • 如何用 Python 和深度遷移學習做文本分類?
  • 如何用 Python 和 BERT 做中文文本二元分類?

喜歡請點贊和打賞。還可以微信關注和置頂我的公眾號「玉樹芝蘭」(nkwangshuyi)。

如果你對 Python 與數據科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數據科學?》,裡面還有更多的有趣問題及解法。

題圖:來自於 freepixels


推薦閱讀:
相關文章