梯度下降(Grandient Descent)
梯度下降的核心原理
:
- 函數的梯度方向表示了函數值增長速度最快的方向,那麼和它相反的方向就可以看作是函數值減少速度最快的方向。對機器學習模型優化問題,當目標設定爲求解目標函數最小值時,只要朝着梯度下降的方向前進,就能不斷逼近最優值。
最簡單的梯度下降算法 - 固定學習率的方法:
- 待優化的函數
- 待優化函數的導數
- 變量:保存當前優化過程中的參數值,優化開始時該變量將被初始化成某個數值,優化過程中這個變量會不斷變化,直到它找到最小值
- 變量grad:保存變量點處的梯度值
- 變量step:表示沿着梯度下降方向行進的步長,即學習率(Learining Rate),在優化過程中它將固定不變
def gd(x_start, step, g): # Gradient Descent
x = x_start
for i in range(20):
grad = g(x)
x -= grad * step
print '[ epoch {0} ] grad = {1}, x = {2}'.format(i, grad, x)
if abs(grad) < 1e-6:
break;
return x
由於優化的目標是尋找梯度爲0的極值點,代碼在每一輪迭代結束後衡量變量所在的梯度值,因此當梯度值足夠小的時,就認爲已經進入最優值附近一個極小的領域,和最優值之間的差別不再明顯,就可以停止優化了。最後的值就是最優解的位置。
有興趣的可以跑一下以下簡單的二次函數的demo:
def f(x):
return x * x - 2 * x + 1
def g(x):
return 2 * x - 2
gd(5,0.1,g)
合適的步長非常重要
動量算法(Momentum)
在優化求解的過程中,動量
代表了之前迭代優化量,它將在後面的優化過程中持續發揮作用,推動目標值前進。擁有了動量,一個已經結束的更新量不會立刻消失,只會以一定的形式衰減,剩下的能量將繼續在優化過程中發揮作用
。
def momentum(x_start, step, g ,discount = 0.7):
x = np.array(x_start, dtype='float64')
pre_grad = np.zeros_like(x)
for i in range(50):
grad = g(x)
pre_grad = pre_grad * discount + grad * step
x -= pre_grad
print '[ epoch {0} ] grad = {1}, x = {2}'.format(i, grad ,x)
if abs(sum(grad)) < 1e-6:
break;
return x
代碼中多出了一個新變量 pre_grad,這個變量就是用於存儲歷史積累的動量,每一輪迭代動量都會乘以一個打折量(discount)做能量衰減,但它依然會被用於更新參數。
動量算法相比較梯度下降,能使得梯度在下降時減少左右震盪
(震盪的方向是相反的,由於歷史積累的動量,會相互抵消),加快梯度下降速度。
但是動量優化存在一點問題,前面幾輪的迭代過程中,梯度的震盪會比原來的還大,爲了解決這個更強烈的抖動,就有了接下來的Nesterov算法
Nesterov算法
def nesterov(x_start, step, g, discount = 0.7)
x = np.array(x_start,dtype='float64')
pre_grad = np.zeros_like(x)
for i in range(50):
x_future = x - step * discount * pre_grad
grad = g(x_future)
pre_grad = pre_grad * 0.7 + grad
x -= pre_grad * step
print '[ Epoch {0} ] grad = {1} , x = {2} '.format(i, grad, x)
if abs(sum(grad)) < 1e-6:
break;
return x
與動量算法相比,動量算法計算了當前目標點的梯度,而Nesterov算法計算了動量更新後優化點的梯度
。當優化點已經積累了某個抖動方向的梯度後,這時對於動量算法來說,雖然當前點的梯度指向積累梯度的相反方向,但是量不夠大,所以最終的優化方向還會在積累的方向上前進一段。對於Nesterov來說,如果按照積累方向再往前多走一段,這時梯度中指向積累梯度相反方向的量變大了許多,所以最終兩個方向的梯度抵消,反而使得抖動方向的量迅速減少。Nesterov的衰減速度確實比動量方法要快不少。
很多科研人員已經給出了動量打折率的建議配置 – 0.9
:
如果用表示每一輪迭代的動量,表示當前一輪迭代的更新量(方向 * 步長),表示迭代輪數,表示動量的打折率,那麼對於時刻的梯度更新量如下:
所以,對於第一輪迭代的更新來說,從到,它的總貢獻量爲:
它的貢獻和爲一個等比數列的和,比值爲。如果,那麼更新量在極限狀態下貢獻值:
當時,它一共貢獻了相當於自身10倍的能量。如果,那就是100倍能量了。
SGD的變種算法
Adagrad
Adagrad是一種自適應
的梯度下降方法。何爲自適應呢?在梯度下降法中,參數的更新量等於梯度乘以學習率,也就是說,更新量和梯度是正相關的;而在實際應用中,每個參數的梯度各有不同,有的梯度大,有的梯度比較小,那麼就有可能遇到參數優化不均衡
的情況。
參數優化不均衡對模型訓練來說不是件好事,這意味着不同的參數更新適用於不同的學習率。而Adagrad的自適應算法也正是要解決這個問題。算法希望不同參數的更新量能夠比較均衡。對於已經更新比較多的參數,它的更新量要適當衰減,而更新比較少的參數,它的更新量要儘量多一些,它的參數更新公式如下:
其中的取值一般比較小,它只是爲了防止分母爲0.
def adagrad(x_start, step, g , delta=1e-8):
x.np.array(x_start,dtype='float64')
sum_grad = np.zeros_like(x)
for i in range(50):
grad = g(x)
sum_grad += grad * grad
x -= step* grad /(np.sqrt(sum_grad)+delta)
if abs(sum(grad)) < 1e-6:
break;
return x
從公式和代碼中可以發現,算法積累了歷史的梯度值的和,並用這個加和來調整每個參數的更新量——對於之前更新量大的參數,分母也會比較大,於是未來它的更新量會比較小;對於之前更新量小的參數,分母也相對小一些,於是未來它的更新量會相對大一些。
Rmsprop
Adagrad算法有一個很大的問題,那就是隨着優化的迭代次數不斷增加, 更新公式的分母項會變得越來越大
。所以理論上更新量也會越來越小,這對優化十分不利。Rmsprop就試圖解決這個問題,在它的算法中,分母的梯度平方和不再隨優化而增加,而是做加權平均。更新公式如下:
def rmsprop(x_start, step, g , rms_decay = 0.9, delta=1e-8):
x = np.array(x_start, dtype = 'float64')
sum_grad = np.zeros_like(x)
passing_dot = [x.copy()]
for i in range(50):
grad = g(x)
sum_grad = rms_decay * sum_grad + (1 - rms_decay) * grad * grad
x -= step * grad /(np.sqrt(sum_grad) + delta)
passing_dot.append(x.copy())
if abs(sum(grad)) < 1e-6:
break;
return x , passing_dot
Adam
Adam既包含了動量
算法的思想,也包含了RmsProp的自適應
梯度的思想。在計算過程彙總,Adam既要像動量算法那樣計算累計的動量:
又要像RmsProp那樣計算梯度的滑動平方和:
作者沒有直接把這兩個計算值加入最終計算的公式中,作者推導了兩個計算量與期望的差距,於是給這兩個變量加上了修正量:
最後,兩個計算量將融合到最終的公式中:
def adam(x_start, step, g, beta1 = 0.9, beta2 = 0.999, delta=1e-8):
x = np.array(x_start, dtype='float64')
sum_m = np.zeros_like(x)
sum_v = np.zeros_like(x)
passing_dot = [x.copy()]
for i in range (50):
grad = g(x)
sum_m = beta1 * sum_m + (1 - beta1) * grad
sum_v = beta2 * sum_v + (1 - beta2) * grad * grad
correction = np.sqrt(1 - beta2 ** i) / (1 - beta1 ** i)
x -= step * correction * sum_m / (np.sqrt(sum_v) + delta)
passing_dot.append(x.copy())
if abs(sum(grad)) < 1e-6:
break;
return x, passing_dot
Adam算法結合了動量的“慣性”和自適應的“起步快”這兩個特點。綜合來看,RmsProp和Adam的表現更平穩,現在大部分科研人員都在使用這兩種優化方法。