摘要:表示學習率,是梯度下降法的一個超參數,其取值影響最優解的速度。因此在使用梯度下降法之前,最好進行數據歸一化。同時在隨機梯度下降法中學習率的取值是逐漸遞減的,為了防止固定取值的學習率使得梯度下降到達最優解附近時繼續跳出這個范圍。
梯度下降法不是一個機器學習算法,而是一種基于搜索的最優化方法,用于最小化一個效用函數。
簡單理解梯度下降法假設存在一個只有一個參數 $ heta$ 的損失函數 $J$,想找到最小極值處的 $ heta$,如圖所示:
借助于損失函數 $J$ 在 $ heta$ 處的切線,可以直觀的反映出損失函數 $J$ 在 $ heta$ 處的導數大小;導數的大小代表著 $ heta$ 變化時 $J$ 相應的變化。
同時導數也可以代表 $J$ 增大的方向,如果將導數與 $-eta$ 相乘,即 $-etafrac{dJ}{d heta}$ 代表了 $J$ 減小的方向。
$eta$ 表示學習率,是梯度下降法的一個超參數,其取值影響最優解的速度。太小會減慢收斂學習速度,太大可能導致不收斂。
如果 $J$ 中存在多處導數為零的情況,即存在一個全局最優解和多個局部最優解,此時可以多次使用梯度下降法,每次隨機化一個初始點。
對于有多個 $ heta$ 的 $J$ 類似,即找出全局最優解處的這些 $ heta$ 的值。
模擬梯度下降法首先,模擬一個損失曲線 $J$:
import numpy as np plot_x = np.linspace(-1, 6, 141) plot_y = (plot_x - 2.5) ** 2 - 1
作圖表示如下:
定義函數 dJ() 用于求 $J$ 在 $ heta$ 處的導數:
def dJ(theta): return 2 * (theta - 2.5)
函數 J() 用于求 $J$ 在 $ heta$ 處的大小:
def J(theta): return (theta - 2.5) ** 2 - 1
接著使用梯度下降法,首先給 $ heta$ 賦一個初值 0 及學習率 $eta$ 為 0.1,接著在循環里進行多次梯度下降。每次循環都要求得 $J$ 在 $ heta$ 處的導數值 $gradient$,并且 $ heta$ 向導數的負方向移動,即:$ heta= heta-eta*gradient$。
由于計算機計算浮點數存在誤差,對于求得的 $ heta$ 可能不能剛好等于 0,因此設定一個精度值(epsilon = 1e-8),如果新的 $ heta$ 對應的損失函數的值與上一次 $ heta$ 對應的損失函數的值的差值滿足精度要求,就表示找到了要找的 $ heta$。程序如下:
theta = 0.0 eta = 0.1 epsilon = 1e-8 while True: gradient = dJ(theta) last_theta = theta theta = theta - eta * gradient if (abs(J(theta) - J(last_theta)) < epsilon): break
運行程序求得的 $ heta$ 為:2.499891109642585。
對于 $ heta$ 的取值變化,可以用圖片表示,如下(紅色的點):
對于學習率 $eta$,這里取值 0.1 是沒有問題的,但如果取值 1.1 程序運行就會報錯:
OverflowError Traceback (most recent call last)in 8 9 theta = theta - eta * gradient ---> 10 if (abs(J(theta) - J(last_theta)) < epsilon): 11 break 12 in J(theta) 1 def J(theta): ----> 2 return (theta - 2.5) ** 2 - 1 OverflowError: (34, "Result too large")
這是因為學習率過大會導致 J(theta) 越來越大。為了使程序不會報錯,修改 J() 方法:
def J(theta): try: return (theta - 2.5) ** 2 - 1 except: return float("inf")
注意,當無窮減無窮時,結果時 nan 而不是 0,此時 if (abs(J(theta) - J(last_theta)) < epsilon) 將永遠無法觸發而使得程序進入死循環。為了解決這個問題,增加一個新的超參數 n_iters,表示能夠執行循環的最大次數。
比如使 n_iters=10,$ heta$ 取值變化如圖:
最后,把梯度下降法封裝到方法中:
def gradient_descent(initial_theta, eta, n_iters=1e4, epsilon=1e-8): theta = initial_theta i_ters = 0 while i_ters < n_iters: gradient = dJ(theta) last_theta = theta theta = theta - eta * gradient if (abs(J(theta) - J(last_theta)) < epsilon): break i_ters += 1 return theta多元線性回歸中的梯度下降法 原理
多元線性回歸的損失函數為:
$$ J=sum_{i=1}^{m}(y^{(i)} - hat{y}^{(i)})^2 $$
其中:$hat{y}^{(i)} = heta_{0} + heta_{1}X_{1}^{(i)} + heta_{2}X_{2}^{(i)} + ... + heta_{n}X_{n}^{(i)}$ 。
對 $J$ 求導為:
$$ abla J=(frac{partial J}{partial heta_0},frac{partial J}{partial heta_1},...,frac{partial J}{partial heta_n}) $$
其中:$frac{partial J}{partial heta_i}$ 為偏導數,與導數的求法一樣。
對 $ abla J$ 進一步計算:
$$ abla J( heta) = egin{pmatrix} frac{partial J}{partial heta_0} frac{partial J}{partial heta_1} frac{partial J}{partial heta_2} cdots frac{partial J}{partial heta_n} end{pmatrix} = egin{pmatrix} sum_{i=1}^{m}2(y^{(i)} - X_b^{(i)} heta)·(-1) sum_{i=1}^{m}2(y^{(i)} - X_b^{(i)} heta)·(-X_1^{(i)}) sum_{i=1}^{m}2(y^{(i)} - X_b^{(i)} heta)·(-X_2^{(i)}) cdots sum_{i=1}^{m}2(y^{(i)} - X_b^{(i)} heta)·(-X_n^{(i)}) end{pmatrix} = 2·egin{pmatrix} sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)}) sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_1^{(i)} sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_2^{(i)} cdots sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_n^{(i)} end{pmatrix} $$
其中:$X_b = egin{pmatrix} 1 & X_1^{(1)} & X_2^{(1)} & cdots & X_n^{(1)} 1 & X_1^{(2)} & X_2^{(2)} & cdots & X_n^{(2)} cdots & & & & cdots 1 & X_1^{(m)} & X_2^{(m)} & cdots & X_n^{(m)} end{pmatrix}$
這個結果是與樣本數量 m 相關的,為了使結果與 m 無關,對這個梯度除以 m,即:
$$ abla J( heta) = frac{2}{m}·egin{pmatrix} sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)}) sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_1^{(i)} sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_2^{(i)} cdots sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_n^{(i)} end{pmatrix} $$
此時,目標函數就成了使 $frac{1}{m}sum_{i=1}^{m}(y^{(i)} - hat{y}^{(i)})^2$ 盡可能小,即均方誤差盡可能小:
$$ J( heta) = MSE(y, hat{y}) $$
使用梯度下降法訓練模型首先模擬訓練數據集:
import numpy as np x = np.random.random(size=100) y = x * 3.0 + 4.0 + np.random.normal(size=100) X = x.reshape(-1, 1)
定義函數 J() 計算損失函數的值:
def J(theta, X_b, y): try: return np.sum((y - X_b.dot(theta)) ** 2) / len(X_b) except: return float("inf")
函數 dJ() 對 $ heta$ 求導數:
def dJ(theta, X_b, y): res = np.empty(len(theta)) res[0] = np.sum(X_b.dot(theta) - y) for i in range(1, len(theta)): res[i] = (X_b.dot(theta) - y).dot(X_b[:, i]) return res * 2 / len(X_b)
注意:對 $J$ 求導更好的方式是進行向量化處理,即 $ abla J( heta) = frac{2}{m}·X_b^T·(X_b heta-y)$,dJ() 改寫為:
def dJ(theta, X_b, y): return X_b.T.dot(X_b.dot(theta) - y) * 2 / len(X_b)
梯度下降的過程為:
def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8): theta = initial_theta i_ters = 0 while i_ters < n_iters: gradient = dJ(theta, X_b, y) last_theta = theta theta = theta - eta * gradient if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon): break i_ters += 1 return theta
執行程序找出最優的 $ heta$ 如下:
X_b = np.hstack([np.ones((len(X), 1)), X]) initial_theta = np.zeros(X_b.shape[1]) eta = 0.01 theta = gradient_descent(X_b, y, initial_theta, eta)
$ heta$ 結果為:
array([4.0269033, 3.0043078])
將梯度下降法封裝到線性回歸算法的模型訓練方法 fit_gd() 中:
class LinearRegression: # other codes here def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4): def J(theta, X_b, y): try: return np.sum((y - X_b.dot(theta)) ** 2) / len(X_b) except: return float("inf") def dJ(theta, X_b, y): return X_b.T.dot(X_b.dot(theta) - y) * 2 /len(X_b) def gradient_descent(X_b, y, initial_theta, eta, n_iters=n_iters, epsilon=1e-8): theta = initial_theta i_ters = 0 while i_ters < n_iters: gradient = dJ(theta, X_b, y) last_theta = theta theta = theta - eta * gradient if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon): break i_ters += 1 return theta X_b = np.hstack([np.ones((len(X_train), 1)), X_train]) initial_theta = np.zeros(X_b.shape[1]) self._theta = gradient_descent(X_b, y_train, initial_theta, eta) self.interception_ = self._theta[0] self.coef_ = self._theta[1:] return self
注意:隨機梯度下降法在真實的數據集 (X, y) 中,X 整體不在一個規模上會影響梯度的結果,而梯度的結果再乘以 $eta$ 得到步長就太大或太小,從而導致訓練出的模型可能很差。因此在使用梯度下降法之前,最好進行數據歸一化。
$ abla J( heta) = frac{2}{m}·egin{pmatrix} sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)}) sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_1^{(i)} sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_2^{(i)} cdots sum_{i=1}^{m}(X_b^{(i)} heta - y^{(i)})·X_n^{(i)} end{pmatrix}$ 中每一項都要對所有樣本進行計算,因此這種梯度下降法稱為批量梯度下降法(Batch Gradient Descent)。如果 m 非常大,使用批量梯度下降法計算梯度的計算量就會非常大。
改進的方法是對每一項計算時不再計算所有的樣本而是選取其中一個樣本進行計算,即:
$$ 2·egin{pmatrix} (X_b^{(i)} heta - y^{(i)})·X_0^{(i)} (X_b^{(i)} heta - y^{(i)})·X_1^{(i)} (X_b^{(i)} heta - y^{(i)})·X_2^{(i)} cdots (X_b^{(i)} heta - y^{(i)})·X_n^{(i)} end{pmatrix} = 2·(X_b^{(i)})^T·(X_b^{(i)} heta-y^{(i)}) $$
這樣的方式就是隨機梯度下降法(Stochastic Gradient Descent),此時搜索路徑如圖所示:
隨機梯度下降法不能保證梯度下降的方向就是損失函數減小最快的方向(有時會是增大的方向),但整體上是會到達最小值附近的(滿足一定的精度)。
同時在隨機梯度下降法中學習率 $eta$ 的取值是逐漸遞減的,為了防止固定取值的學習率使得梯度下降到達最優解附近時繼續跳出這個范圍。一個合理的取值方式為:
$$ eta = frac{t0}{i\_iter + t1} $$
其中:$t0、t1$ 為超參數。
定義函數 dJ_sgd() 對應批量梯度下降法中對損失函數求導的過程,此時傳入函數的就不再是所有樣本 $(X_b, y)$ 了,而是其中一個樣本 $(X_b^{(i)}, y^{(i)})$:
def dJ_sgd(theta, X_b_i, y_i): return X_b_i.T.dot(X_b_i.dot(theta) - y_i) * 2.
函數 sgd() 中定義了一個 learning_rate() 方法用來計算學習率,傳入參數為當前迭代次數。
因為要隨機的選取樣本中的一個,但又要將所有樣本看一遍,所以我們將這個樣本集打亂形成一個新的樣本集 $(X_{b,new}^{(i)}, y_{new}^{(i)})$,同時指定參數 n_iters 表示將這個樣本集看幾遍:
def sgd(X_b, y, initial_theta, n_iters, t0, t1): def learning_rate(t): return t0 / (t + t1) theta = initial_theta m = len(X_b) for cur_iter in range(n_iters): indexes = np.random.permutation(m) X_b_new = X_b[indexes] y_new = y[indexes] for i in range(m): grandient = dJ_sgd(theta, X_b_new[i], y_new[i]) theta = theta - learning_rate(cur_iter * m + i) * grandient return theta
將隨機梯度下降法封裝到線性回歸算法的模型訓練方法 fit_sgd() 中:
class LinearRegression: # other codes here def fit_sgd(self, X_train, y_train, n_iters=5, t0=5, t1=50): def dJ_sgd(theta, X_b_i, y_i): return X_b_i.T.dot(X_b_i.dot(theta) - y_i) * 2. def sgd(X_b, y, initial_theta, n_iters, t0, t1): def learning_rate(t): return t0 / (t + t1) theta = initial_theta m = len(X_b) for cur_iter in range(n_iters): indexes = np.random.permutation(m) X_b_new = X_b[indexes] y_new = y[indexes] for i in range(m): grandient = dJ_sgd(theta, X_b_new[i], y_new[i]) theta = theta - learning_rate(cur_iter * m + i) * grandient return theta X_b = np.hstack([np.ones((len(X_train), 1)), X_train]) initial_theta = np.zeros(X_b.shape[1]) self._theta = sgd(X_b, y_train, initial_theta, n_iters, t0, t1) self.interception_ = self._theta[0] self.coef_ = self._theta[1:] return self
在 Scikit Learn 的 linear_model 模塊中提供了一個使用隨機梯度下降法的回歸算法 SGDRegressor:
from sklearn.linear_model import SGDRegressor源碼地址
Github | ML-Algorithms-Action
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/45024.html
閱讀 1496·2023-04-26 01:28
閱讀 3315·2021-11-22 13:53
閱讀 1420·2021-09-04 16:40
閱讀 3189·2019-08-30 15:55
閱讀 2677·2019-08-30 15:54
閱讀 2489·2019-08-30 13:47
閱讀 3368·2019-08-30 11:27
閱讀 1146·2019-08-29 13:21