梯度下降-不同優化方法比較

前言

上篇介紹了梯度下降的原理,接下來介紹下,除了SGD外,其他的優化算法,以及python簡易實現代碼

雖然梯度下降算法效果很好,並且廣泛使用,但同時其也存在一些挑戰與問題需要解決:

  • 選擇一個合理的學習速率很難。如果學習速率過小,則會導致收斂速度很慢。如果學習速率過大,那麼其會阻礙收斂,即在極值點附近會振盪。

  • 學習速率調整(又稱學習速率調度,Learning rate schedules)試圖在每次更新過程中,改變學習速率,如退火。一般使用某種事先設定的策略或者在每次迭代中衰減一個較小的閾值。無論哪種調整方法,都需要事先進行固定設置,這邊便無法自適應每次學習的數據集特點。

  • 模型所有的參數每次更新都是使用相同的學習速率。如果數據特徵是稀疏的或者每個特徵有着不同的取值統計特徵與空間,那麼便不能在每次更新中每個參數使用相同的學習速率,那些很少出現的特徵應該使用一個相對較大的學習速率。

  • 對於非凸目標函數,容易陷入那些次優的局部極值點中,如在神經網路中。那麼如何避免呢。更嚴重的問題不是局部極值點,而是鞍點。

Momentum

如果在峽谷地區(某些方向較另一些方向上陡峭得多,常見於局部極值點),SGD會在這些地方附近振盪,從而導致收斂速度慢。這種情況下,動量(Momentum)便可以解決。動量在參數更新項中加上一次更新量(即動量項),即:
vαvϵJ(θ)v \leftarrow\alpha v-\epsilon \nabla J(\theta)
θθ+v\theta \leftarrow \theta+ v

其中動量項超參數α\alpha<1,一般是小於等於0.9

動量是模擬物理裏的概念,通過累積動量來作爲梯度使用。Momentum項能夠在相關的方向加速SGD,抑制震盪,從而加快收斂。比如在下降初期,如果下降方向一致,則能夠通過vv加速;而在下降中後期,當局部最小值來回震盪的時候,能夠通過 vv跳出陷阱(因爲gg趨於0,可以通過vv值跳出).

簡單代碼
J(θ)=θ2J(\theta)=\theta^2
J(θ)=2θJ^\prime(\theta)=2\theta
先看GD的代碼來做對比【θθϵJ(θ)\theta \leftarrow \theta-\epsilon \nabla J(\theta)

def gd(a,x0,n):
#a學習率,x0初始值,n迭代次數
    x=[x0]
    for i in range(n):
        b=round(x[i]-a*2*x[i],5)
        x.append(b)
        print(i+1,b)
gd(0.9,20,20)

1 -16.0
2 12.8
3 -10.24
4 8.192
5 -6.5536
6 5.24288
7 -4.1943
8 3.35544
9 -2.68435
10 2.14748
11 -1.71798
12 1.37438
13 -1.0995
14 0.8796
15 -0.70368
16 0.56294
17 -0.45035
18 0.36028
19 -0.28822
20 0.23058

迭代20次依舊沒有收斂到最小值

用Momentum代碼看一下

def mgd(a,ϵ,x0,n):
#a動量項超參數,ϵ學習率,x0初始值,n迭代次數
    v=0
    x=[x0]
    for i in range(n):
        v=v*a-ϵ*2*x[i]
        b=round(x[i]+v)
        x.append(b)
        print(i+1,b)
mgd(0.1,0.9,20,20)

1 -16
2 9
3 -5
4 3
5 -2
6 1
7 0
8 0
9 0
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0
20 0

可以發現在第七步就收斂到最小值

Nesterov

從山頂往下滾的球會盲目地選擇斜坡。更好的方式應該是在遇到傾斜向上之前應該減慢速度。
Nesterov accelerated gradient(NAG,涅斯捷羅夫梯度加速)不僅增加了動量項,並且在計算參數的梯度時,在損失函數中減去了動量項,這種方式預估了下一次參數所在的位置

θ~=θ+αv\tilde{\theta}=\theta+\alpha v
vαvϵJ(θ~)v \leftarrow\alpha v-\epsilon \nabla J(\tilde{\theta})
θθ+v\theta \leftarrow \theta+ v

Nesterov項在梯度更新時會做一個矯正,一面前進速度太快,同時提高靈敏度。Momentum項和Nesterov項都是爲了使梯度更新更加靈活,只是會通過人工指定不同的規則,使對不同情況有比較好的針對性。

簡單代碼

def ngd(a,ϵ,x0,n):
   v=0
   x=[x0]
   for i in range(n):
       v=v*a-ϵ*2*(x[i]-v*a)
       b=round(x[i]+v)
       x.append(b)
       print(i+1,b)
ngd(0.1,0.9,20,20)

1 -16
2 3
3 3
4 -2
5 0
6 1
7 -1
8 0
9 0
10 0
11 0
12 0
13 0
14 0
15 0
16 0
17 0
18 0
19 0
20 0

Adagrad

Adagrad是一種基於梯度的優化算法,它能夠對每個參數自適應不同的學習速率,對稀疏特徵,得到大的學習更新,對非稀疏特徵,得到較小的學習更新,因此該優化算法適合處理稀疏特徵數據。Adagrad能夠很好的提高SGD的魯棒性,google便用起來訓練大規模神經網絡(看片識貓:recognize cats in Youtube videos)。GloVe中便使用Adagrad來訓練得到詞向量(Word Embeddings), 頻繁出現的單詞賦予較小的更新,不經常出現的單詞則賦予較大的更新。
   在前述中,每個模型參數θiθ_i使用相同的學習速率ηη,而Adagrad在每一個更新步驟中對於每一個模型參數θiθ_i使用不同的學習速率ηiη_i,設第t次更新步驟中,目標函數的參數θiθ_i梯度爲gt,ig_{t,i}
  
gJ(θ)g\leftarrow\nabla J(\theta)
rr+ggr\leftarrow r+g\bigodot g
θθϵδ+rg\theta\leftarrow \theta-\frac{\epsilon}{\delta+\sqrt{r}}\bigodot g

爲了使得分母不爲0(通常δ=1e−7),另外如果分母不開根號,算法性能會很糟糕。

Adagrad其實是對學習率進行了約束,比如,當前期g比較小時,正則項比較大,能夠放大梯度;當後期g比較大的時候,正則項比較小,能夠約束梯度。Adagrad適合處理係數梯度,Adagrad對全局學習率參數比較敏感。

簡單代碼

def adagrad(ϵ,δ,x0,n):
    x=[x0]
    r=0
    for i in range(n):
        r=r+pow(2*x[i],2)
        b=round(x[i]-ϵ*2*x[i]/(δ+sqrt(r)),5)
        x.append(b)
        print(i+1,b)
adagrad(0.9,1e-7,2,20)

1 1.1
2 0.66627
3 0.41409
4 0.25968
5 0.1634
6 0.10296
7 0.06491
8 0.04093
9 0.02581
10 0.01628
11 0.01027
12 0.00648
13 0.00409
14 0.00258
15 0.00163
16 0.00103
17 0.00065
18 0.00041
19 0.00026
20 0.00016

可以發現收斂的很穩定

Adam

Adaptive Moment Estimation(Adam) 也是一種不同參數自適應不同學習速率方法,它計算曆史梯度衰減方式類似動量,如下:

gJ(θ)g\leftarrow\nabla J(\theta)
tt+1t\leftarrow t+1
有偏一階估計sρ1s+(1ρ1)gs\leftarrow \rho_1s+(1-\rho_1)g
有偏二階估計rρ2r+(1ρ2)ggr\leftarrow \rho_2r+(1-\rho_2)g\bigodot g

修正一階估計s^s1ρ1t\hat{s}\leftarrow \frac{s}{1-\rho_1^t}
修正二階估計r^r1ρ2t\hat{r}\leftarrow \frac{r}{1-\rho_2^t}
更新 θθϵs^r^+δ\theta\leftarrow \theta-\epsilon \frac{\hat{s}}{\sqrt{\hat{r}}+\delta}

建議默認值:ρ1\rho_1=0.9,ρ2\rho_2=0.999,δ\delta=10−8。

adam算法會通過梯度的一階矩估計和二階矩估計動態調整每個參數學習率。Adam主要優點在於,經過偏置矯正後,每一次學習率都有確定的範圍,使的參數值比較平穩。Adam對內存需求較小,適用於大數據集和高維空間,也適用於大多非凸優化,是深度學習常用的優化算法。
簡單代碼

def adm(ϵ,δ,ρ1,ρ2,x0,n):
    x=[x0]
    r=0
    s=0
    t=0
    for i in range(n):
        t=t+1
        s=ρ1*s+(1-ρ1)*2*x[i]
        r=ρ2*r+(1-ρ2)*2*x[i]*2*x[i]
        s1=s/(1-pow(ρ1,t))
        r1=r/(1-pow(ρ2,t))
        b=round(x[i]-ϵ*s1/(sqrt(r1)+δ),5)
        x.append(b)
        print(i+1,b)
adm(0.1,1e-8,0.9,0.999,2,20)

1 1.9
2 1.80017
3 1.70063
4 1.60151
5 1.50296
6 1.40514
7 1.30821
8 1.21235
9 1.11775
10 1.0246
11 0.93312
12 0.84353
13 0.75605
14 0.67091
15 0.58835
16 0.50861
17 0.43193
18 0.35853
19 0.28864
20 0.22246

使用adagrad(0.1,1e-7,2,20)相同參數測算結果

1 1.9
2 1.83113
3 1.77583
4 1.72857
5 1.68677
6 1.64901
7 1.61438
8 1.58227
9 1.55225
10 1.524
11 1.49727
12 1.47187
13 1.44765
14 1.42447
15 1.40224
16 1.38086
17 1.36026
18 1.34037
19 1.32114
20 1.30251

可以看到Adam效果會更好一些

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