原理
機(jī)器學(xué)習(xí)就是定義一個f(x),找到f(x)的最佳參數(shù),讓的過程。那么如何找到這個最佳參數(shù)呢?
梯度學(xué)習(xí)就是應(yīng)用最廣泛的一種方法。
為什么需要梯度下降
每個模型都有自己的損失函數(shù),訓(xùn)練一個模型的過程,就是找到使損失函數(shù)最小的最佳參數(shù)的過程。在簡單的線性回歸中,我們通過最小二乘法來求解參數(shù);但是一般損失函數(shù)都是比較復(fù)雜的,很難通過求解得到。這時候,我們就可以通過梯度下降去求解。
梯度下降算法作為一個聰明很多的算法,抓住了參數(shù)與損失值之間的導(dǎo)數(shù),也就是能夠計算梯度(gradient),通過導(dǎo)數(shù)告訴我們此時此刻某參數(shù)應(yīng)該朝什么方向,以怎樣的速度運(yùn)動,能安全高效降低損失值,朝最小損失值靠攏。
什么是梯度
多元函數(shù)的導(dǎo)數(shù)就是梯度,對每個變量進(jìn)行微分,然后用逗號分隔,因此梯度是一個向量,代表了下降的方向。
假設(shè)有個二元函數(shù),求偏導(dǎo)為
那么在點(diǎn)(1,2),梯度
在單變量函數(shù)中,梯度代表函數(shù)的微分,代表著函數(shù)在某個點(diǎn)的斜率;
對于多變量函數(shù),梯度代表是一個向量,代表在給定點(diǎn)上升最快的方向
梯度指向誤差值增加最快的方向。我們的目標(biāo)是找到損失函數(shù)(也就是誤差)最小對應(yīng)的參數(shù),因此我們需要沿著反梯度的方向進(jìn)行搜索。
理解梯度下降
梯度下降就是從群山中山頂找一條最短的路走到山谷最低的地方。
一個人被困在山上,需要從山頂?shù)缴焦?。但此時霧很大,看不清下山的路徑。他必須利用自己周圍的信息去找到下山的路徑。這個時候,他就可以利用梯度下降算法來幫助自己下山。具體來說就是,以他當(dāng)前的所處的位置為基準(zhǔn),隨機(jī)選擇一個方向,然后每次邁步都選擇最陡的方向。然后每走一段距離,都反復(fù)采用同一個方法:如果發(fā)現(xiàn)腳下的路是下坡,就順著最陡的方向走一步,如果發(fā)現(xiàn)腳下的路是上坡,就逆著方向走一步,最后就能成功的抵達(dá)山谷。

從數(shù)學(xué)的角度出發(fā),針對損失函數(shù)L,可以證明按照負(fù)梯度的方向移動,損失函數(shù)最終能夠達(dá)到一個最小值。
那么我們就可以得到損失函數(shù)值(也就是下一步的落腳點(diǎn))的迭代公式:
針對于上述公式,有一些常見的問題:
為什么要梯度要乘以一個負(fù)號?
我們已經(jīng)知道:梯度的方向就是損失函數(shù)值在此點(diǎn)上升最快的方向,是損失增大的區(qū)域,而我們要使損失最小,因此就要逆著梯度方向走,自然就是負(fù)的梯度的方向,所以此處需要加上負(fù)號
關(guān)于參數(shù) :
我們已經(jīng)知道,梯度對應(yīng)的是下山的方向,而參數(shù) 對應(yīng)的是步伐的長度。在學(xué)術(shù)上,我們稱之為“學(xué)習(xí)率”(learning rate),是模型訓(xùn)練時的一個很重要的超參數(shù),能直接影響算法的正確性和效率:
- 首先,學(xué)習(xí)率不能太大。因此從數(shù)學(xué)角度上來說,一階泰勒公式只是一個近似的公式,只有在學(xué)習(xí)率很小,也就是很小時才成立。并且從直觀上來說,如果學(xué)習(xí)率太大,那么有可能會“邁過”最低點(diǎn),從而發(fā)生“搖擺”的現(xiàn)象(不收斂),無法得到最低點(diǎn)
- 其次,學(xué)習(xí)率又不能太小。如果太小,會導(dǎo)致每次迭代時,參數(shù)幾乎不變化,收斂學(xué)習(xí)速度變慢,使得算法的效率降低,需要很長時間才能達(dá)到最低點(diǎn)。
缺點(diǎn):
梯度算法只能達(dá)到局部最優(yōu)解,不是全局最優(yōu)解。
那么對應(yīng)的解決方案如下:首先隨機(jī)產(chǎn)生多個初始參數(shù)集,即多組;然后分別對每個初始參數(shù)集使用梯度下降法,直到函數(shù)值收斂于某個值;最后從這些值中找出最小值,這個找到的最小值被當(dāng)作函數(shù)的最小值。當(dāng)然這種方式不一定能找到全局最優(yōu)解,但是起碼能找到較好的。
對于梯度下降來說,初始點(diǎn)的位置,也是一個超參數(shù)。
線性回歸梯度下降代碼
def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""根據(jù)訓(xùn)練數(shù)據(jù)集X_train, y_train, 使用梯度下降法訓(xùn)練Linear Regression模型"""
assert X_train.shape[0] == y_train.shape[0], \
"the size of X_train must be equal to the size of y_train"
def J(theta, X_b, y):
try:
return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
except:
return float('inf')
def dJ(theta, X_b, y):
return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(y)
def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):
theta = initial_theta
cur_iter = 0
while cur_iter < 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
cur_iter += 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, n_iters)
self.intercept_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
然后使用向量化的方式編寫代碼,但是發(fā)現(xiàn)在真實數(shù)據(jù)中效果比較差,這是因為數(shù)據(jù)的規(guī)模不一樣,因此在梯度下降之前需要使用歸一化。