路遥知马力——Momentum动量梯度
NAG:在滑板下降過程中 也就是速度加快的時候 增大水平方向的力(累計的動量方向) 而在上升的過程中 也就是速度下降的時候
減少垂直方向的力(當前的梯度方向) 兩種情況下 的最終結果
都是加大了往最優點方向的值 加速了 接近最優點的速度
本文收錄在無痛的機器學習第一季。
寫在前面:感謝
@夏龍對本文的審閱和提出的寶貴意見,歡迎各位大神多多指點。上一回扯了些關于梯度下降的事情,當然那只是個開頭,有關梯度下降相關的內容非常多。為了保證完整性,今天再扯一下另外一個在梯度下降中十分重要的東西,那就是動量——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 * stepx -= pre_gradprint '[ Epoch {0} ] grad = {1}, x = {2}'.format(i, grad, x)if abs(sum(grad)) < 1e-6:break; return x
可以看出這個算法和之前的梯度下降法相比,唯一不同的就是多了一個pre_grad*discount,這就是動量發揮余熱的地方。
那么動量究竟有什么作用呢?今天主要扯它其中的一個作用,那就是幫助你穿越“山谷”。怎么來理解穿越“山谷”呢?先來一個待優化函數。這次的問題相對復雜些,是一個二元二次函數:
def f(x):
return x[0] * x[0] + 50 * x[1] * x[1]
def g(x):
return np.array([2 * x[0], 100 * x[1]])
xi = np.linspace(-200,200,1000)
yi = np.linspace(-100,100,1000)
X,Y = np.meshgrid(xi, yi)
Z = X * X + 50 * Y * Y
上面這個函數在等高線圖上是這樣的:
其中中心的藍色點表示了最優值。我們根據這個圖發揮下想象,這個函數在y軸十分陡峭,在x軸相對平緩些。好了話說完我們趕緊拿樸素梯度下降來嘗試下:
gd([150,75], 0.016, g)
經過50輪的迭代,他的優化過程圖如下所示:
可以看出我們從某個點出發,整體趨勢向著最優點前進,這個是沒有問題的,但是前進的速度似乎有點乏力,是不是步長又設小了?有了之前的經歷,這一回我們在設置步長時變得小心了許多:
res, x_arr = gd([150,75], 0.019, g)
contour(X,Y,Z, x_arr)
好像成效不是很明顯啊,而且優化的過程中左右來回抖是怎么回事?看著這個曲線讓我想起了一個極限運動:
(來自網絡,如有侵權立即刪除)
沒錯,其實算法眼中的這個函數很這張圖很像,而算法也果然沒有讓大家“失望”,選擇了一條艱難的道路進行優化——就像從一邊的高臺滑下,然后滑到另一邊,這樣艱難地前進。沒辦法,這就是梯度下降法。在它的眼中,這樣走是最快的,而事實上,每個優化點所對應的梯度方向也確實是那個方向。
大神們這時可能會聊起特征值的問題,關于這些問題以后再說。好吧,現在我們只能繼續挑步長,說不定步長再大點,“滑板少年”還能再快點呢!
res, x_arr = gd([150,75], 0.02, g)
contour(X,Y,Z, x_arr)
好吧……我們的滑板少年已經徹底玩脫了……這已經是我們能設的最大的步長了(上一次關于步長和函數之間的關系在這里依然受用),再設大些我們的滑板少年就飛出去了。對于這個問題,由于兩個坐標軸方向的函數屬性不同,為了防止在優化的過程中發散,步長只能夠根據最陡峭的方向設定。當然,解決快速收斂這個問題還有其他的辦法,這里我們看看沖量如何搞定這位滑板少年。
很自然地,我們在想,要是少年能把行動的力量集中在往前走而不是兩邊晃就好了。這個想法分兩個步驟:首先是集中力量向前走,然后是盡量不要在兩邊晃。這時候,我們的動量就閃亮登場了。我們發現滑板少年每一次的行動只會在以下三個方向進行:
- 沿-x方向滑行
- 沿+y方向滑行
- 沿-y方向滑行
我們可以想象到,當使用了動量后,實際上沿-y和+y方向的兩個力可以相互抵消,而-x方向的力則會一直加強,這樣滑板少年會在y方向打轉,但是y方向的力量會越來越小,但是他在-x方向的速度會比之前快不少!
好了,那我們看看加了動量技能的滑板少年的實際表現:
momentum([150,75], 0.016, g)
總算沒有讓大家失望,盡管滑板少年還是很貪玩,但是在50輪迭代后,他還是來到了最優點附近。可以說是基本完成了我們的任務吧。當然由于動量的問題,前面幾輪迭代他在y軸上玩得似乎比以前還歡樂,這個問題我們后面會提。但不管怎么說,總算完成目標了。
后來,又有高人發明了解決前面動量沒有解決的問題的算法,干脆不讓滑板少年愉快地玩耍了,也就是傳說中的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_gradgrad = g(x_future)pre_grad = pre_grad * 0.7 + grad x -= pre_grad * stepprint '[ Epoch {0} ] grad = {1}, x = {2}'.format(i, grad, x)if abs(sum(grad)) < 1e-6:break; return xnesterov([150,75], 0.012, g)
好了,滑板少年已經哭暈在廁所……
費了這么多話,我們總算把穿越“山谷”這件事情說完了,下面還要說一個數值上的事情。在CNN的訓練中,我們的開山祖師已經給了我們動量的建議配置——0.9(剛才的例子全部是0.7),那么0.9的動量有多大量呢?終于要來點公式了……
我們用G表示每一輪的更新量,g表示當前一步的梯度量(方向*步長),t表示迭代輪數,表示沖量的衰減程度,那么對于時刻t的梯度更新量有:
那么我們可以計算下對于梯度g0對從G0到GT的總貢獻量為
我們發現它的貢獻是一個等比數列,如果=0.9,那么跟據等比數列的極限運算方法,我們知道在極限狀態下,它一共貢獻了自身10倍的能量。如果=0.99呢?那就是100倍了。
那么在實際中我們需要多少倍的能量呢?
本文相關代碼詳見:https://github.com/hsmyy/zhihuzhuanlan/blob/master/momentum.ipynb
廣告時間
更多精彩盡在《深度學習輕松學:核心算法與視覺實踐》!
總結
以上是生活随笔為你收集整理的路遥知马力——Momentum动量梯度的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 批量梯度下降(BGD)、随机梯度下降(S
- 下一篇: Python中正则表达式用法 重点格式以