机器学习 | 梯度下降原理及Python实现
文章目錄
- 1. 梯度下降
- 1.1 批量梯度下降(BGD)
- 1.1.1 學習率的設置
- 1.1.2 Python 實現 BGD
- 1.2 隨機梯度下降(SGD)
- 1.2.1 Python 實現 SGD
- 1.2.2 Sklearn 實現 SGD
- 1.3 小批量梯度下降(MBGD)
- 2. 三類梯度下降的比較
- 參考資料
相關文章:
機器學習 | 目錄
機器學習 | 網絡搜索及可視化
監督學習 | 線性回歸原理及Sklearn實現
An overview of gradient descent optimization algorithms
梯度下降優化算法綜述
Keras 中的優化程序
1. 梯度下降
梯度下降(Gradient Descent)是一種非常通用的優化算法,能夠為大范圍的問題找到最優解。梯度下降的中心思想就是迭代地調整參數從而使成本函數最小化。
假設你迷失在山上的迷霧中,你能夠感覺到的只有你腳下路面的坡度。快速到達山腳的策略就是沿著最陡的方向下坡。這就是梯度下降的做法:通過測量參數向量 θ\thetaθ 相關的誤差函數的局部梯度,并不斷沿著梯度的方向調整,演到梯度降為 0,到達最小值!
具體來說,首先使用一個隨機的 θ\thetaθ 值(這被稱為隨機初始化),然后逐步改進,每次踏出一步,每一步都嘗試降低一點成本函數(如在線性回歸中采用 MSE),直到算法收斂出一個最小值,如下所示:
梯度下降中一個重要參數就是每一步的步長,這卻取決于超參數學習率(Learning Rate)。如果學習率態度,算法需要經過大量迭代才能收斂,這將耗費很長時間,如圖所示,學習率太低:
反過來說,如果學習率太高,那可能會越過山谷直接到達山的另一邊(并沒有藍精靈),設置有可能比之前的起點還要高。這會導致算法發散,值越來越大,最后無法找到好的解決方案,如下所示,學習率太高:
最后,并不是所有的成本函數看起來都像一個漂亮的碗。有的可能看著像洞、像山脈、像高原或者是各種不規則的地形,導致很難收斂到最小值。
下圖顯示了梯度下降的兩個主要挑戰:如果隨機初始化,算法從左側起步,那么會收斂到一個局部最小值,而不是全局最小值。如果從右側起步,那么需要經過很長時間才能越過整片高原,如果停下來太早,將永遠達不到全局最小值。
以線性回歸模型為例,其成本函數 MSE 恰好是個凸函數,這意味著連接曲線上任意兩個點的線段永遠不會跟曲線相交。也就是說不存在局部最小,只有一個全局最小值,它同時也是一個連續函數,所以斜率不會產生陡峭的變化(即漢族利普西茨條件)。這兩點保證了即便是亂走,梯度下降都可以趨近到全局最小值(只要等待時間足夠長,學習率也不是太高)。
成本函數雖然是碗狀的,但如果不同特征的尺寸差別巨大,那它可能是一個非常細長的碗。如下圖所示的梯度下降,左邊的訓練集上特征 1 和特征 2 具有相同的數值規模,而右邊的訓練集上,特征 1 的數值則比特征 2 要小得多(因為特征 1 的值較小,所以 θ1\theta_1θ1? 需要更大的變化來來影響成本函數,這就是為什么碗形會沿著 θ1\theta_1θ1? 軸拉長)。
特征值無縮放和特征值縮放的梯度下降:
正如你所見,左圖的梯度下降算法直接走向最小值,可以快速到達。而在右圖中,顯示沿著與全局最小值方向近乎垂直的方向前進,接下來是一段幾乎平坦的長長的山谷。最后還是會抵達最小值,但是這需要花費大量的時間。
應用梯度下降時,需要保證全有特征值的大小比例都差不多(比如使用 Sklearn 的 StandardScaler 類),否則收斂的時間會長很多。
這張圖也說明,訓練模型也就是搜尋使成本函數(在訓練集上)最小化的參數組合。這是模型參數空間層面上的搜索:模型的參數越多,這個空間的維度就越多,搜索就越難。同樣是在干草堆里找尋一根針,在一個三百維的空間里就比一個在三維空間里要棘手得多,幸運的是,對于成本函數為凸函數的,針就躺在碗底。[1]
1.1 批量梯度下降(BGD)
要實現梯度下降,需要計算每個模型關于參數 θj\theta_jθj? 的成本函數的梯度。換言之,需要計算的是如果改變 θj\theta_jθj?,成本函數會改變多少,即偏導數。
以線性回歸的成本函數 MSEMSEMSE 為例,其偏導數為:
??θjMSE(θ)=??θj(1m∑i=1m(θT?X(i)?y(i))2)=2m∑i=1m(θT?x(i)?y(i))xj(i)(1)\begin{aligned} \frac{\partial}{\partial \theta_j}MSE(\theta) &=\frac{\partial}{\partial \theta_j} \bigg(\frac{1}{m}\sum_{i=1}^m(\theta^T \cdot X^{(i)}-y^{(i)})^2 \bigg)\\ &=\frac{2}{m}\sum_{i=1}^m(\theta^T \cdot x^{(i)}-y^{(i)})x_j^{(i)}\\ \end{aligned}\tag{1} ?θj???MSE(θ)?=?θj???(m1?i=1∑m?(θT?X(i)?y(i))2)=m2?i=1∑m?(θT?x(i)?y(i))xj(i)??(1)
如果不想單獨計算這些梯度,可以使用公式 (2) 對其進行一次性計算。梯度向量 ?θMSE(θ)\nabla_\theta MSE(\theta)?θ?MSE(θ),包含所有成本函數(每個模型參數一個)的偏導數。
成本函數 MSEMSEMSE 的梯度向量:
?θMSE(θ)=(??θ0MSE(θ)??θ1MSE(θ)???θnMSE(θ))=2mXT?(X?θ?y)(2)\nabla_\theta MSE(\theta)= \left( \begin{array}{cc} \frac{\partial}{\partial \theta_0}MSE(\theta) \\ \frac{\partial}{\partial \theta_1}MSE(\theta) \\ \vdots \\ \frac{\partial}{\partial \theta_n}MSE(\theta) \\ \end{array} \right) =\frac{2}{m}X^T \cdot(X \cdot \theta - y) \tag{2} ?θ?MSE(θ)=???????θ0???MSE(θ)?θ1???MSE(θ)??θn???MSE(θ)???????=m2?XT?(X?θ?y)(2)
對于公式 (2),其在計算梯度下降的每一步時,都是基于完整的訓練集 XXX 的。這就是為什么該算法被稱為批量梯度下降(Batch Gradient Descent):每一步都使用整批訓練數據。因此,面對非常龐大的訓練集時,算法會變得極慢。但是,梯度下降算法隨特征數量擴展的表現比較好:對于線性擬合,如果要訓練的線性模型擁有幾十萬個特征,使用梯度下降仍比標準方法要快得多。
一旦有了梯度向量,那個點向上,就朝反方向下坡。也就是從 θ\thetaθ 中減去 ?θMSE(θ)\nabla_\theta MSE(\theta)?θ?MSE(θ) 。這時學習率 η\etaη 就發揮作用了:用梯度向量乘以 η\etaη 確定下坡步長的大小。
梯度下降步長:
θ(nextstep)=θ?η?θMSE(θ)(3)\theta^{(next step)} = \theta - \eta \nabla_\theta MSE(\theta) \tag{3}θ(nextstep)=θ?η?θ?MSE(θ)(3)
1.1.1 學習率的設置
我們來看一下分別使用三種不同學習率時,梯度下降的前十步(虛線表示起點):
import numpy as np import matplotlib.pyplot as pltX = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1)X_b = np.c_[np.ones((100, 1)), X] # add x0 = 1 to each instance X_new = np.array([[0], [2]]) X_new_b = np.c_[np.ones((2, 1)), X_new]eta = 0.1 n_iterations = 1000 m = 100 theta = np.random.randn(2,1)for iteration in range(n_iterations):gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)theta = theta - eta * gradientstheta X_new_b.dot(theta) array([[4.04184331],[9.84392154]]) theta_path_bgd = []def plot_gradient_descent(theta, eta, theta_path=None):m = len(X_b)plt.plot(X, y, "b.")n_iterations = 1000for iteration in range(n_iterations):if iteration < 10:y_predict = X_new_b.dot(theta)style = "b-" if iteration > 0 else "r--"plt.plot(X_new, y_predict, style)gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)theta = theta - eta * gradientsif theta_path is not None:theta_path.append(theta)plt.xlabel("$x_1$", fontsize=18)plt.axis([0, 2, 0, 15])plt.title(r"$\eta = {}$".format(eta), fontsize=16) np.random.seed(42) theta = np.random.randn(2,1) # random initializationplt.figure(figsize=(10,4)) plt.subplot(131); plot_gradient_descent(theta, eta=0.02) plt.ylabel("$y$", rotation=0, fontsize=18) plt.subplot(132); plot_gradient_descent(theta, eta=0.1, theta_path=theta_path_bgd) plt.subplot(133); plot_gradient_descent(theta, eta=0.5) plt.show()可以看出:
-
左圖的學習率太低:在前十步依然無法找到解決方案,但是只要結果長時間的迭代就一定可以找到解決方案;
-
中間的學習率看起來非常的棒:幾次迭代就收斂出了最終解;
-
而右邊的學習率太高:算法發散,直接跳過了數據區域,并且每一步都離實際解決方案越來越遠。
要找到合適的學習率,可以使用網絡搜索。但是你可能需要限制迭代次數,這樣網絡搜索就可以淘汰掉那些收斂耗時太長的模型。
然而怎么限制迭代次數呢?如果設置太低,算法可能在離最優解還很遠時就停止了;但是如果設置得太高,模型到達最優解后,繼續迭代參數不再變化,又會浪費時間。
一個簡單的方法時,在開始設置一個非常大的迭代次數,但是當梯度向量的值變得很微小時中斷算法——也就是當他的范數變得低于 ε\varepsilonε(稱為容差)時,因為這是梯度下降已經(幾乎)到達了最小值。
收斂率:當成本函數為凸函數,并且斜率沒有陡峭的變化時(如 MSE 成本函數),通過批量梯度下降可以看出一個固定的學習率有一個收斂率,為 o(1迭代次數)o(\frac{1}{迭代次數})o(迭代次數1?)。換句話說,如果將容差 ε\varepsilonε 縮小為原來的1/10(以得到更精確的解),算法將不得不運行 10 倍的迭代次數。
1.1.2 Python 實現 BGD
Python 實現批量梯度下降計算線性回歸模型 θ\thetaθ:
import numpy as np import matplotlib.pyplot as pltX = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1)X_b = np.c_[np.ones((100, 1)), X] # add x0 = 1 to each instanceeta = 0.1 n_iterations = 1000 m = 100 theta = np.random.randn(2,1)for iteration in range(n_iterations):gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)theta = theta - eta * gradientsprint('theta:\n{}\n'.format(theta))X_new = np.array([[0], [2]]) X_new_b = np.c_[np.ones((2, 1)), X_new] # add x0 = 1 to each instance y_predict = X_new_b.dot(theta)plt.plot(X_new, y_predict, "r-") plt.plot(X, y, "b.") plt.axis([0, 2, 0, 15]) plt.show() theta: [[4.20831857][2.79226572]]利用批量梯度下降法計算的 theta 結果與標準方程法的結果一致!
1.2 隨機梯度下降(SGD)
批量梯度下降的主要問題時它要用整個訓練集來計算每一步的梯度,所以訓練集很大時,算法會特別慢。與之相反的極端是隨機梯度下降(Stochastic Gradient Descent),每一步在訓練集中隨機選擇一個實例,并且僅基于該單個實例來計算梯度。顯然,這讓算法變得快多了,因為每個迭代都只需要操作少量的數據。它也可以被用來訓練海量的數據集,因為每次迭代只需要在內存中運行一個實例即可( SGD 可以作為核外算法實現)。
另一方面,由于算法的隨機性質,它比批量梯度下降要不規則得多。成本函數將不再是緩緩降低知道抵達最小值,而是不斷上上下下,但是從整體來看,還是在慢慢下降。隨著時間的推移,最終會非常接近最小值,但是即使它到達了最小值,依然還會持續反彈,永遠不會停止。所以算法停下來的參數值肯定是足夠好的,但不是最優的。
當成本函數非常不規則時(如高原的例子),隨機梯度下降其實可以幫助算法跳出局部最小值,所以相比批量梯度下降,它對找到全局最小值更有優勢。
因為,隨機性的好處在于可以逃離局部最優,但缺點是永遠定位不出最小值。要解決這個困境,有一個辦法時逐步降低學習率。開始的步長比較大(這有助于快速進展和逃離局部最小值),然后越來越小,讓算法盡量靠近全局最小值。這個過程叫做模擬退火,因為它類似于冶金時融化的金屬慢慢冷卻的退火過程。確定每個迭代學習率的函數叫作學習計劃。如果學習率降得太快,可能會陷入局部最小值,甚至是停留在走向最小值的半途中。如果學習率太慢,你可能需要太長時間太能跳到差不多最小值附近,如果提早結束訓練,可能只得到一個次優的解決方案。
1.2.1 Python 實現 SGD
按照慣例,我們用 n_epochs 來表示迭代次數,每一次迭代稱為一輪。
import numpy as np import matplotlib.pyplot as pltX = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1) X_b = np.c_[np.ones((100, 1)), X] # add x0 = 1 to each instancetheta_path_sgd = [] m = len(X_b) np.random.seed(42)n_epochs = 50 t0, t1 = 5, 50 # learning schedule hyperparametersdef learning_schedule(t):return t0 / (t + t1)theta = np.random.randn(2,1) # random initializationfor epoch in range(n_epochs):for i in range(m):if epoch == 0 and i < 20: y_predict = X_new_b.dot(theta) style = "b-" if i > 0 else "r--" plt.plot(X_new, y_predict, style) random_index = np.random.randint(m)xi = X_b[random_index:random_index+1]yi = y[random_index:random_index+1]gradients = 2 * xi.T.dot(xi.dot(theta) - yi)eta = learning_schedule(epoch * m + i)theta = theta - eta * gradientstheta_path_sgd.append(theta) plt.plot(X, y, "b.") plt.xlabel("$x_1$", fontsize=18) plt.ylabel("$y$", rotation=0, fontsize=18) plt.axis([0, 2, 0, 15]) plt.show() theta array([[3.954103 ],[3.03548045]])前面的批量梯度下降需要在整個訓練集上迭代 1000 次,而這段代碼只迭代了 50 次就得到了一個相當不錯的解。
1.2.2 Sklearn 實現 SGD
在 Scikit-Learn 里,用 SGD 執行線性回歸可以使用 sklearn.linear_model.SGDRegressor 類,其默認優化的成本函數是平方誤差。
我們從學習率為 0.1(eta0=0.1)開始,使用默認學習計劃(與之前的學習計劃不同)運行了 50 輪,并且沒有使用任何的正則化(penalty=None),將得到一個跟標準方程的解非常相近的解決方案:
from sklearn.linear_model import SGDRegressor sgd_reg = SGDRegressor(max_iter=50, tol=-np.infty, penalty=None, eta0=0.1, random_state=42) sgd_reg.fit(X, y.ravel()) sgd_reg.intercept_, sgd_reg.coef_ (array([3.9539613]), array([3.05185657]))1.3 小批量梯度下降(MBGD)
小批量梯度下降(Mini-Batch Gradient Descent):每一步的梯度計算,既不是基于整個訓練集(如批量梯度下降),也不是基于單個實例(如隨機梯度下降),而是基于一小部分隨機的實例(也就是小批量)。
相比隨機梯度下降,小批量梯度下降的主要優勢在于可以從矩陣運算的硬件優化中獲得顯著的性能提升,特別是需要用到圖形處理器時。MBGD 算法在參數空間層面的前進過程也不像 SGD 那么不穩定,特別是批量較大時。所以小批量梯度下降最終會比 SGD 更接近最小值一些。但是另一方面,它可能更難從局部最小值中逃脫。
2. 三類梯度下降的比較
如下圖所示,三種梯度下降算法在訓練過程中參數空間里的行進路線,它們最終都匯集在最小值附近,批量梯度下降最終停在了最小值上,而隨機梯度下降和小批量梯度下降還在繼續游走。但是批量梯度下降花費了大量時間來計算每一步,而用好了學習計劃,梯度下降和小批量梯度下降也同樣能達到最小值。
theta_path_mgd = []n_iterations = 50 minibatch_size = 20np.random.seed(42) theta = np.random.randn(2,1) # random initializationt0, t1 = 200, 1000 def learning_schedule(t):return t0 / (t + t1)t = 0 for epoch in range(n_iterations):shuffled_indices = np.random.permutation(m)X_b_shuffled = X_b[shuffled_indices]y_shuffled = y[shuffled_indices]for i in range(0, m, minibatch_size):t += 1xi = X_b_shuffled[i:i+minibatch_size]yi = y_shuffled[i:i+minibatch_size]gradients = 2/minibatch_size * xi.T.dot(xi.dot(theta) - yi)eta = learning_schedule(t)theta = theta - eta * gradientstheta_path_mgd.append(theta) theta array([[3.9746783 ],[3.07082231]]) theta_path_bgd = np.array(theta_path_bgd) theta_path_sgd = np.array(theta_path_sgd) theta_path_mgd = np.array(theta_path_mgd)plt.figure(figsize=(7,4)) plt.plot(theta_path_sgd[:, 0], theta_path_sgd[:, 1], "r-s", linewidth=1, label="Stochastic") plt.plot(theta_path_mgd[:, 0], theta_path_mgd[:, 1], "g-+", linewidth=2, label="Mini-batch") plt.plot(theta_path_bgd[:, 0], theta_path_bgd[:, 1], "b-o", linewidth=3, label="Batch") plt.legend(loc="upper left", fontsize=16) plt.xlabel(r"$\theta_0$", fontsize=20) plt.ylabel(r"$\theta_1$ ", fontsize=20, rotation=0) plt.axis([2.5, 4.5, 2.3, 3.9]) plt.show()最后,我們來比較一下目前為止所討論過的線性回歸算法( m 是訓練實例的數量,n 是特征數量):
參考資料
[1] 周志華. 機器學習[M]. 北京: 清華大學出版社, 2016: 106-115.
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的机器学习 | 梯度下降原理及Python实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab字符处理
- 下一篇: synergy在Windows和Linu