如何解決機器學習算法執行表現不佳的問題

本文最初發表於 Towards Data Science 博客,經原作者 Rashida Nasrin Sucky 授權,InfoQ 中文站翻譯並分享。

我們花了那麼多時間去開發一個機器學習算法。但在部署完成後,如果該算法性能較差,就會變得令人沮喪。問題是,如果算法沒有達到預期的效果,那下一步該怎麼辦呢?是什麼地方出了問題?訓練數據的數量是否足夠?使用的特徵是否正確?是否應該繼續收集更多的數據?我們可以這樣做,但這樣一來,既費時又費錢。我們應該增加更多的特徵嗎?那也可能很昂貴。

往哪個方向走?

如果你的機器學習算法沒有達到預期的效果,那下一步該怎麼辦?下面有幾個選擇:

  • 獲取更多的訓練數據,這是非常耗時的。甚至可能需要數月才能獲得更多的研究數據。
  • 獲取更多的訓練特徵。這可能也會非常耗時。但如果添加一些多項式特徵可行的話,那就太酷了。
  • 選擇一組較小的訓練特徵集。
  • 增加正則化項。
  • 減少正則化項。

那麼,接下來你應該嘗試哪種選擇呢?不要隨便開始嘗試任何選擇,這可不是一個好主意。因爲你最終可能會在一些沒有幫助的事情上耗費太多時間。你需要做的是,先發現問題,然後採取相應的行動。學習曲線有助於輕鬆地發現問題,這可以節省很多時間。

學習曲線對於確定如何提高算法的性能非常有用。判斷算法是否存在偏差或欠擬合,方差或過擬合,或者兩者兼而有之,學習曲線在這方面是很有用的。

學習曲線的工作原理

學習曲線是成本函數的曲線圖。訓練數據的成本函數和交叉驗證數據的成本函數在同一個圖中給出了關於算法的重要見解。作爲提醒,下面是成本函數的公式:

$J_{\text {train}}(\theta)=\frac{1}{2 m} \sum_{i=1}^{m}\left(h_{\theta}\left(x^{i}\right)-y^{i}\right)^{2}$

$J_{c v}(\theta)=\frac{1}{2 m_{c v}} \sum_{i=1}^{m_{c v}}\left(h_{\theta}\left(x_{c v}^{i}\right)-y_{c v}^{i}\right)^{2}$

換句話說,學習曲線是預測輸出減去原始輸出的平方除以訓練數據數量的兩倍。爲了繪製學習曲線,我們需要將這些成本函數繪製成訓練數據($m$)的函數。我們將只使用較小的訓練數據子集來訓練數據,而不是使用所有的訓練數據。

請看下面的圖片:

這是一個概念。

如果訓練的數據數量太少,算法將對訓練數據完全擬合,並且成本函數將返回 0。

上圖清楚地表明,當我們只用一個、兩個或三個數據算法訓練數據時,可以很好地學習很少的數據,並且訓練成本爲零或接近於零。但是這種算法在其他數據上並不能得到很好的性能。

當你嘗試將交叉驗證數據擬合到該算法上時,它在交叉驗證數據上表現不佳的可能性很高。因此,交叉驗證數據的成本函數將返回非常高的值。

另一方面,當我們使用越來越多的數據來訓練算法時,它將不能很好地擬合訓練數據。這樣,訓練成本就會越來越高。

同時,由於該算法是在大量數據上進行訓練的,所以,在交叉驗證數據上的表現會更好,交叉驗證數據的成本函數會返回一個較低的值。下面是如何制定學習曲線的方法。

開發一種學習方法

我將逐步演示如何繪製學習曲線。爲了繪製學習曲線,我們首先需要一個機器學習算法。爲簡單起見,我將使用線性迴歸算法。讓我們先開發一個線性迴歸算法。

首先,導入包和數據集。我在這裏使用的數據集取自 Andrew Ng(吳恩達)的 Coursera 機器學習課程。在這個數據集中,$X$ 值和 $y$ 值分別組織在 Excel 文檔中的不同工作表中。

提醒一下,$X$ 是我們將用於開發和訓練機器學習算法的特徵。$y$ 是我們需要預測的輸出特徵。

交叉驗證數據的 $X$ 值和 $y$ 值也組織在同一個 Excel 文檔的另外兩個工作表中。我在文末提供了這個數據集的下載鏈接。請隨時下載這個數據集並親自練習。

%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
file = pd.ExcelFile('dataset.xlsx')
df = pd.read_excel(file, 'Xval', header=None)
df.head()

以同樣的方式,導入訓練集的 y 值:

y = pd.read_excel(file, 'yval', header=None)
y.head()

讓我們快速開發線性迴歸算法。

定義假設

線性迴歸使用非常基本的線性方程來預測,我們在學校都學過。公式如下:

$Y = C + BX$

對於機器學習,我們使用不同的術語。

$h= \theta_0 + \theta_1X$

這裏,$h$ 是假設或預測值,$\theta_0$ 和 $\theta_1$ 是係數,$X$ 是輸入特徵。

我們已經有了 $X$。我們需要計算出 $h$,它應該與 $y$ 的值匹配,因爲我們的目標是能夠預測 $y$ 的值。

$\theta_0$ 和 $\theta_1$ 在開始時隨機初始化。我們將通過迭代不斷優化 $\theta_0$ 和 $\theta_1$ 的值。

在每次迭代中,我們將使用成本函數和梯度公式來計算成本,以更新 $\theta$ 值。

成本函數和梯度下降

成本函數爲我們提供了預測值與原始輸出特徵有多大差異的概念。在這裏,我們的輸出特徵是 $y$,預測的輸出將是“$h”。所以,成本函數會告訴我們,“$h$”與“$y$”的偏差有多大。我們希望成本函數值越低越好。

成本函數公式如下:

$J\left(\theta_{0}, \theta_{1}\right)=\frac{1}{2 m} \Sigma\left(h_{i}-y_{i}\right)^{2}$

我們將繼續運行算法,直到成本函數最小。在每次迭代中,我們使用梯度下降來更新 $\theta$ 值。

爲了更新 $\theta$ 值,我們將從之前的 $\theta$ 值中扣除梯度下降。當我們將其編碼時,它會更加清晰。

$\theta_{0}=\theta_{0}-\alpha \frac{1}{m} \Sigma\left(h_{i}-y_{i}\right)$

$\theta_{1}=\theta_{1}-\alpha \frac{1}{m} \Sigma\left(h_{i}-y_{i}\right) X$

其中,$m$ 是訓練數據的數量,而 $\alpha$ 是學習率。

開發線性迴歸算法

利用上述公式開發假設和成本函數。

m = len(df)
def hypothesis(theta, X):
return theta[0] + theta[1]*X
def cost_calc(theta, X, y):
return (1/2*m) * np.sum((hypothesis(theta, X) - y)**2)

現在,我們將定義梯度下降來優化參數 $\theta_0$ 和 $\theta_1$。 在每次迭代中,我們將更新 $\theta$ 值,並跟蹤成本函數和 $\theta$ 值。

最後,它將返回每次迭代的成本列表 $\thetra$ 值。代碼很簡單,請參閱如下代碼段:

def gradient_descent(theta, X, y, epoch, alpha):
cost = []
theta_hist = []
i = 0
while i < epoch:
hx = hypothesis(theta, X)
theta[0] -= alpha*(sum(hx-y)/m)
theta[1] -= (alpha * np.sum((hx - y) * X))/m
cost.append(cost_calc(theta, X, y))
i += 1
return theta, cost

這就完成了線性迴歸算法。我們需要一種方法來預測輸出。在預測方法中,我們將使用來自梯度下降函數和假設函數的最終 $\theta$ 進行預測。

def predict(theta, X, y, epoch, alpha):
theta, cost = gradient_descent(theta, X, y, epoch, alpha)
return hypothesis(theta, X), cost, theta

現在,將參數初始化爲零,並使用predict函數來預測輸出變量。

theta = [0,0]
y_predict, cost, theta = predict(theta, df[0], y[0], 1400, 0.001)

更新後的 $\theta$ 值爲:[10.724868115832654, 0.3294833798797125]

現在,將預測 $output(h)$ 和原始 $output(y)$ 與 $df$ 或 $X$ 繪製在同一圖中。

plt.figure()
plt.scatter(df, y)
plt.scatter(df, y_predict)

看起來算法運行得很好。預測的輸出線是從中性位置開始的。

是時候繪製學習曲線了!

繪製學習曲線

現在,我們可以繪製學習曲線了。首先,讓我們導入交叉驗證數據及的 $x$ 和 $y$ 值。正如我之前提到的,我們將它們組織在單獨的 Excel 表格中。

file = pd.ExcelFile('dataset.xlsx')
cross_val = pd.read_excel(file, 'X', header=None)
cross_val.head()

cross_y = pd.read_excel(file, 'y', header=None)
cross_y.head()

出於這個目的,我想稍微修改一下gradient_descent函數。

在之前的gradient_descent函數中,我們計算了每次迭代的成本。我這麼做是因爲在傳統的機器學習算法開發中,這是一個很好的實踐做法。

但對於學習曲線,我們並不需要每次迭代的成本。因此,爲了節省運行時間,我將不計算每個輪數的成本函數。我們將只返回更新的參數。

def grad_descent(theta, X, y, epoch, alpha):
i = 0
while i < epoch:
hx = hypothesis(theta, X)
theta[0] -= alpha*(sum(hx-y)/m)
theta[1] -= (alpha * np.sum((hx - y) * X))/m
i += 1
return theta

正如我之前所討論的,要制定一個學習曲線,我們需要用不同的訓練數據子集來訓練學習算法。

在我們的訓練數據集中,我們有 21 個數據。我將只使用 1 個數據訓練算法,然後使用 2 個數據,再然後使用 3 個數據,以此類推,一直到 21 個數據。

因此,我們將在 21 個訓練數據子集上對算法進行 21 次訓練。我們還將跟中每個訓練數據子集的成本函數。請仔細閱讀一下代碼,這樣理解纔會更清楚。

j_tr = []
theta_list = []
for i in range(0, len(df)):
theta = [0,0]
theta_list.append(grad_descent(theta, df[0][:i], y[0][:i], 1400, 0.001))
j_tr.append(cost_calc(theta, df[0][:i], y[0][:i]))
theta_list

下面是每個訓練數據子集的訓練參數:

以下是每個訓練子集的成本:

看看每個子集的成本。當訓練數據僅爲 1 或 2 時,成本爲零或幾乎爲零。隨着我們不斷增加訓練數據,成本也隨之上升,這正是我們所預期的。

現在,對訓練數據的所有子集使用上述參數來計算交叉驗證數據的成本:

j_val = []
for i in theta_list:
j_val.append(cost_calc(i, cross_val[0], cross_y[0]))
j_val

剛開始的時候,由於訓練參數來自太少的訓練數據,所以成本確實很高。但隨着訓練數據的增多,參數的改進,交叉驗證誤差將會不斷下降。

讓我們將訓練誤差和交叉驗證誤差繪製在同一圖中:

%matplotlib inline
import matplotlib.pyplot as plt
plt.figure()
plt.scatter(range(0, 21), j_tr)
plt.scatter(range(0, 21), j_val)

這就是我們的學習曲線。

從學習曲線中得出決策

上面的學習曲線看起來不錯。它正以我們預期的那樣流動着。一開始,訓練誤差太小,驗證誤差太大。

慢慢地,它們彼此完全重疊在一起了。所以,這是完美的!但在現實生活中,這種情況並不經常發生。

大多數機器學習算法並非第一次就能完美地工作。幾乎每時每刻,它都會有一些我們需要解決的問題。下面我將討論一些問題。

我們可能會發現學習曲線如下所示:

如果在訓練誤差和驗證誤差之間存在顯著的差距,則表明存在高方差問題。這也可以稱爲過擬合問題。

獲取更多訓練數據或選擇更小的特徵集,或者兩者做法都可以解決這個問題。

如果學習曲線看起來是這樣的,這就意味着在開始時訓練誤差太小,驗證誤差太高。慢慢地,訓練誤差越來越高,而驗證誤差越來越低。但在某一時刻上,它們將變得平行。從圖中可以看出,經過某一點後,即使有了更多的訓練數據,交叉驗證誤差也不會再減少了。

在這種情況下,獲取更多的訓練數據並不能改善機器學習算法。

這表明該學習算法存在高偏差問題。在這種情況下,獲取更多的訓練特徵可能會有所幫助。

修正學習算法

假設我們正在實現線性迴歸算法,但算法並沒有如預期的那樣工作。

那該怎麼辦?

首先,繪製一條學習曲線,就像我在下面演示的那樣:

  1. 如果檢測到高方差問題,請根據特徵的重要性選擇較小的特徵集。如果有幫助的話,那將會節省一些時間。如果沒有,請嘗試獲取更多的訓練數據。

  2. 如果你從學習曲線中檢測到高偏差問題,那麼你已經知道獲取額外的特徵是一個可能的解決方案。你甚至可以嘗試添加一些多項式特徵。大量的時間對你很有幫助,也節省了很多時間。

  3. 如果你正在實現一個帶有正則化項 $\lambda$ 的算法,如果該算法存在高偏差,請嘗試減少 $\lambda$;如果該算法存在高方差問題,請嘗試增大 $\lambda$。這裏有一篇文章《如何改進機器學習算法:偏差、方差和正則化》(How to Improve a Machine Learning Algorithm: Bias, Variance and, Regularization),詳細闡述了正則項與偏差和方差的關係:

在使用神經網絡的情況下,我們也可能會遇到這種偏差或方差問題。

對於高偏差或欠擬合的問題,我們需要增加神經元的數量或隱藏層的數量。爲了解決高方差或過擬合的問題,我們應該減少神經元的數量或隱藏層的數量。我們甚至可以用不同數量的神經元繪製學習曲線。

作者介紹:

Rashida Nasrin Sucky,波士頓大學學生。

原文鏈接:

https://towardsdatascience.com/your-ml-algorithm-is-not-performing-well-613dd2b07fc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章