DL基本知識(三)momentum梯度下降&python實現

事先聲明,這篇博客的適用人羣是剛開始學習梯度下降的同學,有無深度學習基礎不是特別重要,重要的是有一顆學習探索的心~

關鍵字:momentum、函數求導、Python

一直以來,不是很懂隨機梯度下降中momentum的概念,後來又好好地研究了下momentum,順便總結下,這篇博客旨在讓各位看官以最快的速度弄懂momentum的含義,因而如下主要內容的梗概比較簡短,讓大家快速上手:

  • 原理
  • Python實現

原理

首先說一下原始的沒有momentum的梯度下降,最一開始的每次的迭代過程如下:

  1. 隨機抽取mm個樣本,{x1,x2,...,xm}\{x_{1}, x_{2},..., x_{m}\},
  2. 計算梯度並更新參數爲如下公式:
    g=1mθi=1mL(f(xi;θ),yi)g = \frac{1}{m}\triangledown _{\theta }\sum_{i=1}^{m}L(f(x_{i};\theta), y_{i})
    θ=θεg\theta=\theta-\varepsilon g

經過上述操作,每次梯度更新的時候都會更新εg-\varepsilon g,這個恆定的值會帶來很多麻煩:比如說如果梯度下降過程中曲面太平坦,如果還按照上述步伐走,梯度下降地會比較緩慢例如下圖所示;

相反,如果梯度下降過程中太陡峭,那麼下降時就會一直震盪,例如下圖所示,

上述兩種情況都沒辦法達到穩定下降的目的。針對上述缺點,提出了momentum這個概念,在這裏不講述其物理意義,因爲很容易把人弄暈,到最後速度和位移都混在一起,現在只講原理,

還是按照上面的思路,不過這裏引入了vv這個概念,其實這個vv就是梯度的改變量, 迭代過程如下所示,

  1. 隨機抽取mm個樣本,{x1,x2,...,xm}\{x_{1}, x_{2},..., x_{m}\},
  2. 計算梯度並更新參數爲如下公式:
    g=1mθi=1mL(f(xi;θ),yi)g = \frac{1}{m}\triangledown _{\theta }\sum_{i=1}^{m}L(f(x_{i};\theta), y_{i})
    v=αvεgv=\alpha v - \varepsilon g
    θ=θ+v\theta = \theta+ v

經過上述操作, 梯度的變化量的推導公式如下所示,

第一次迭代公式如下,其中v1=εg1v_1=-\varepsilon g_1 θ=θ+v1=θεg1\theta=\theta+v_1 = \theta-\varepsilon g_1

第二次迭代公式如下,θ=θ+v2=θ+αv1εg2=θε(αg1+g2)\theta=\theta+v_2 = \theta + \alpha v_1 -\varepsilon g_2 = \theta -\varepsilon (\alpha g_1+g_2)

通過第二個公式可以得到:如果本次和上次的梯度符號是相同的,那麼就能夠加速下降(幅度變大),就能夠解決原先下降太慢的問題;如果本次和上次的梯度符號是相反的,那麼這次就和上次相互抑制,減緩震盪。

經過上述操作,就能夠解決最一開始博主所討論的兩個問題了,是不是很棒!

Python實現

根據上述原理可以看出,使用momentum時需要保留之前輪的vn1lv_{n-1}^l,這裏ll代表的是神經網絡的層的編號,這樣做是因爲神經網絡每一層的WW都是不相同的,而momentum需要針對每一層都做一個類似緩衝的操作,因而這時必須要添加一個靜態變量來保存上一輪的值,且這個靜態變量需要定義爲一個dict才能滿足上述需求,具體代碼如下:

import numpy as np
from collections import defaultdict

class WeightUpdate:
	# 保存之前輪的狀態
    CACHE = defaultdict()

    def __init__(self, weight_update_param=None):
        self.weight_update_func = None
        self._generate_weight_update_func(weight_update_param)

    def _generate_weight_update_func(self, weight_update_param=None):
        if weight_update_param is None:
            weight_update_param = {
                "mode": "momentum",
                "alpha": 0.01
            }
        self.weight_update_param = weight_update_param
        # 方便拓展
        if weight_update_param["mode"] == "momentum":
            self.weight_update_func = self._momentum
        else:
            pass

    def _momentum(self, W, delta_W, learning_rate, name=None):
        """Momentum weight update.

        :param W:
        :param delta_W:
        :param learning_rate:
        :return:
        """
        if W.shape != delta_W.shape:
            return None

        if "alpha" not in self.weight_update_param:
            return None

        v = WeightUpdate.CACHE.get(name, np.zeros_like(W))

        alpha = self.weight_update_param["alpha"]
        WeightUpdate.CACHE[name] = -learning_rate * delta_W + alpha * v
        W += WeightUpdate.CACHE[name]

        return W

更多細節可以到github repo中查詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章