一、優化與深度學習
優化與估計
儘管優化方法可以最小化深度學習中的損失函數值,但本質上優化方法達到的目標與深度學習的目標並不相同。
- 優化方法目標:訓練集損失函數值
- 深度學習目標:測試集損失函數值(泛化性)
現實世界中,訓練集數據和測試集數據的分佈存在差異,因此可能訓練和測試過程中誤差不盡相同。
優化在深度學習中的挑戰
1. 局部最小值
舉例:f(x)=xcosπx
2. 鞍點
舉例:一維函數 f(x)=x3
對於高維變量輸入,我們通過二階導數Hessian矩陣來說明:
A=⎣⎢⎢⎢⎢⎢⎡∂x12∂2f∂x2∂x1∂2f⋮∂xn∂x1∂2f∂x1∂x2∂2f∂x22∂2f⋮∂xn∂x2∂2f⋯⋯⋱⋯∂x1∂xn∂2f∂x2∂xn∂2f⋮∂xn2∂2f⎦⎥⎥⎥⎥⎥⎤
對於多維變量 x=[x1,x2,…,xn], 當一階偏導∂x∂f=0 ,二階偏導有正有負時,則 x 這時可稱爲函數f(⋅)的鞍點。
我們以二維變量舉例:z=f(x)=x12−x22
3. 梯度消失
深度學習中的諸如sigmoid,tanh等激活函數在偏離中心的區域梯度逐漸消失,趨近於0。多層這樣的激活函數的堆疊,導致深層神經網絡訓練過程中會經常面臨梯度消失的難題。我們以tanh函數圖例說明:
二、凸優化基礎
凸集
在凸集中的示意圖中我們可以總結出凸集的幾何性質:
- 凸集中任意兩點連線上的點仍在凸集中
- 凸集的交集仍舊是凸集
- 凸集的並集不一定是凸集
凸函數
對於凸函數凸性的描述,我們可以引申凸集第一條性質得到如下的不等式:
λf(x)+(1−λ)f(x′)≥f(λx+(1−λ)x′)
我們舉三個函數的例子來直觀的說明函數凸性和上述不等式的對應關係:
上面的不等式說明任意兩點的割線始終位於函數上方,我們泛化到任意點集{xi},使用數學歸納法,可以證明凸函數滿足Jensen不等式:
i∑αif(xi)≥f(i∑αixi)
在概率論中,如果把 αi 看成取值爲 xi 的離散變量 x 的概率分佈, 就可以得到:
Ex[f(x)]≥f(Ex[x])
其中, E[⋅] 表示期望。
凸函數的性質
-
無局部極小值
證明:(反證法) 假設存在x∈X是局部最小值,則存在全局最小值x′∈X , 使得f(x)>f(x′) , 則對λ∈(0,1] :
f(x)>λf(x)+(1−λ)f(x′)≥f(λx+(1−λ)x′)
可以觀察到f(x)在x的鄰域內並非達到局部最小,至此證畢。
-
與凸集的關係
結論:對於凸函數f(x) ,定義集合Sb:={x∣x∈X and f(x)≤b} ,則集合Sb 爲凸集。
證明:對於點x,x′∈Sb , 有f(λx+(1−λ)x′)≤λf(x)+(1−λ)f(x′)≤b , 故λx+(1−λ)x′∈Sb 對映凸集的第一條性質。
這裏舉一個非凸函數來可視化這條性質:
f(x,y)=0.5x2+cos(2πy)
從等高線圖中可以看出,當都滿足小於某一特定值時,可能形成無交集的兩部分,取值集合不具有凸性。
-
凸函數與二階導數
結論:f′′(x)≥0⟺f(x)是凸函數
證明:必要性(⇐)
對於凸函數:
21f(x+ϵ)+21f(x−ϵ)≥f(2x+ϵ+2x−ϵ)=f(x)
故:
f′′(x)=ε→0limϵϵf(x+ϵ)−f(x)−ϵf(x)−f(x−ϵ)f′′(x)=ε→0limϵ2f(x+ϵ)+f(x−ϵ)−2f(x)≥0
充分性(⇒)
令 a<x<b 爲 f(x) 上的三個點,由拉格朗日中值定理:
f(x)−f(a)=(x−a)f′(α) for some α∈[a,x] and f(b)−f(x)=(b−x)f′(β) for some β∈[x,b]
根據單調性,有 f′(β)≥f′(α), 故:
f(b)−f(a)=f(b)−f(x)+f(x)−f(a)=(b−x)f′(β)+(x−a)f′(α)≥(b−a)f′(α)
有限制條件凸函數的優化方法
舉例:
xminimizef(x) subject to ci(x)≤0 for all i∈{1,…,N}
1. 拉格朗日乘子法
L(x,α)=f(x)+i∑αici(x) where αi≥0
2. 添加懲罰項
欲使ci(x)≤0 , 將項αici(x) 加入目標函數,如多層感知機章節中的2λ∣∣w∣∣2
3. 投影法
ProjX(x)=x′∈Xargmin∥x−x′∥2
三、梯度下降
理想的梯度下降發生在局部凸函數中。
一維梯度下降
證明:沿梯度反方向移動自變量可以減小函數值
泰勒展開:
f(x+ϵ)=f(x)+ϵf′(x)+O(ϵ2)
代入沿梯度方向的移動量ηf′(x),η∈(0,1]:
f(x−ηf′(x))=f(x)−ηf′2(x)+O(η2f′2(x))f(x−ηf′(x))≲f(x)x←x−ηf′(x)其中η被稱爲學習率/步長,當學習率過大時,梯度下降無法收斂到局部最小值。
當學習率過小時,收斂速度緩慢。
除面臨如何選擇合適的學習率的問題,梯度下降還經常被局部最小值所困擾:
多維梯度下降
僅在一維梯度下降中就面臨學習率選擇,局部最小值陷阱的問題。在多維梯度下降中,這兩個問題變得更復雜。對於多維梯度下降,我們可以用數學語言表示成:
∇f(x)=[∂x1∂f(x),∂x2∂f(x),…,∂xd∂f(x)]⊤f(x+ϵ)=f(x)+ϵ⊤∇f(x)+O(∥ϵ∥2)x←x−η∇f(x)
以二維梯度下降舉例:
f(x)=x12+2x22
eta = 0.1
def f_2d(x1, x2):
return x1 ** 2 + 2 * x2 ** 2
def gd_2d(x1, x2):
return (x1 - eta * 2 * x1, x2 - eta * 4 * x2)
def train_2d(trainer, steps=20):
x1, x2 = -5, -2
results = [(x1, x2)]
for i in range(steps):
x1, x2 = trainer(x1, x2)
results.append((x1, x2))
print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))
return results
對於多維梯度下降,我們通過考慮二階導數來實現自動選擇學習率,這裏我們首先介紹牛頓法:
牛頓法
在 x+ϵ 處泰勒展開(此處使用二次展開,並使用peano餘項):
f(x+ϵ)=f(x)+ϵ⊤∇f(x)+21ϵ⊤∇∇⊤f(x)ϵ+O(∥ϵ∥3)
最小值點處滿足:∇f(x)=0 即我們希望∇f(x+ϵ)=0 , 對上式關於 ϵ 求導,忽略高階無窮小,有:∇f(x)+Hfϵ=0 and hence ϵ=−Hf−1∇f(x)
這樣我們就獲得了牛頓法對變量的更新公式。
牛頓法的收斂性分析
只考慮在函數爲凸函數(或在局部凸函數的範圍內), 且最小值點上 f′′(x∗)>0時的收斂速度:
令 xk 爲第 k 次迭代後 x 的值, ek:=xk−x∗ 表示 xk 到最小值點 x∗ 的距離,由f′(x∗)=0,展開成Lagrange餘項形式:
0=f′(xk−ek)=f′(xk)−ekf′′(xk)+21ek2f′′′(ξk)for some ξk∈[xk−ek,xk]
兩邊除以 f′′(xk) , 有:
ek−f′(xk)/f′′(xk)=21ek2f′′′(ξk)/f′′(xk)
代入更新方程 xk+1=xk−f′(xk)/f′′(xk) , 得到:
xk−x∗−f′(xk)/f′′(xk)=21ek2f′′′(ξk)/f′′(xk)xk+1−x∗=ek+1=21ek2f′′′(ξk)/f′′(xk)
當 21f′′′(ξk)/f′′(xk)≤c 時,有:
ek+1≤cek2
我們稱之爲具有二階收斂速度。
預處理(Heissan陣輔助梯度下降)
在牛頓法中,我們需要計算Heissan矩陣來進行變量的更新,Heissan矩陣大小是d×d,其中d代表變量的維度,計算複雜度爲O(d2)。我們改用Heissan陣的對角陣diag(Hf)來代替Heissan陣進行變量更新,diag(Hf)的計算複雜度降至O(d),同時也能對各維度變量變化尺度進行歸一化的處理,保證各方向下降速率基本一致。
x←x−ηdiag(Hf)−1∇x
梯度下降與線性搜索(共軛梯度法)(略)
隨機梯度下降
在實際的優化過程中,對於求解Heissan陣這樣耗費計算的步驟仍然是無法實現的,因此對牛頓法進行進一步的鬆弛,我們得到簡單計算即可迭代的隨機梯度下降算法。
隨機梯度下降參數更新
對於有 n 個樣本對訓練數據集,設 fi(x) 是第 i 個樣本的損失函數, 則目標函數爲:
f(x)=n1i=1∑nfi(x)
其梯度爲:
∇f(x)=n1i=1∑n∇fi(x)
使用該梯度的一次更新的時間複雜度爲O(n),這種梯度下降的更新算法也被稱爲最速梯度下降法。我們每次更新都要計算數據集中全部樣本的梯度。
而隨機梯度下降是最速梯度下降的變種,每次迭代僅對一個樣本計算梯度,其時間複雜度爲O(1):
x←x−η∇fi(x)
且有:
Ei∇fi(x)=n1i=1∑n∇fi(x)=∇f(x)
舉例:
f(x1,x2)=x12+2x22
eta = 0.1
lr = (lambda: 1)
def f(x1, x2):
return x1 ** 2 + 2 * x2 ** 2
def gradf(x1, x2):
return (2 * x1, 4 * x2)
def sgd(x1, x2):
global lr
(g1, g2) = gradf(x1, x2)
(g1, g2) = (g1 + np.random.normal(0.1), g2 + np.random.normal(0.1))
eta_t = eta * lr()
return (x1 - eta_t * g1, x2 - eta_t * g2)
動態學習率
在隨機梯度下降的過程中,我們可以發現學習率應當隨着學習過程的進行而逐漸減小。這裏借用李宏毅老師的課件來說明學習率設定的重要性:
下面介紹幾種設置動態學習率的方法:
η(t)=ηi if ti≤t≤ti+1η(t)=η0⋅e−λtη(t)=η0⋅(βt+1)−α piecewise constant exponential polynomial
小批量隨機梯度下降
在使用隨機梯度下降算法的過程中我們會發現,更新的梯度受所選定的樣本影響很大,即方差很大。會導致梯度下降過程中出現偏離局部最小值方向的更新。但如果使用所有樣本進行梯度計算又會佔用高計算資源,因此我們採用一種折衷的辦法來進行隨機梯度下降。也就是批量梯度下降(與小批量梯度下降的原理相同)。隨機從樣本中選取一部分數據計算當前的平均梯度方向,迭代速度比最速梯度下降法更快,比隨機梯度下降每次更新梯度方向精度更高。
隨機梯度下降:loss: 0.245968, 0.463836 sec per epoch,2 epoch
小批量隨機梯度下降:loss: 0.243900, 0.065017 sec per epoch,batch_size=10,2 epoch
四、優化算法
梯度下降優化法經歷了SGD→SGDM→NAG→AdaGrad→AdaDelta→RMSProp→Adam→Nadam
這樣的發展歷程。之所以會不斷地提出更加優化的方法,究其原因,是引入了動量(Momentum)這個概念。最初,人們引入一階動量來給梯度下降法加入慣性(即,越陡的坡可以允許跑得更快些)。後來,在引入二階動量之後,才真正意味着“自適應學習率”優化算法時代的到來。
動量法(Momentum)
在 Dive into DL Section 11.4 中,目標函數有關自變量的梯度代表了目標函數在自變量當前位置下降最快的方向。因此,梯度下降也叫作最速下降(steepest descent)。在每次迭代中,梯度下降根據自變量當前位置,沿着當前位置的梯度更新自變量。然而,如果自變量的迭代方向僅僅取決於自變量當前位置,這可能會帶來一些問題。對於noisy gradient,我們需要謹慎的選取學習率和batch size, 來控制梯度方差和收斂的結果。
gt=∂w∣Bt∣1i∈Bt∑f(xi,wt−1)=∣Bt∣1i∈Bt∑gi,t−1.
An ill-conditioned Problem
Condition Number of Hessian Matrix:
condH=λminλmax
where λmax,λmin is the maximum amd minimum eignvalue of Hessian matrix.
讓我們考慮一個輸入和輸出分別爲二維向量x=[x1,x2]⊤和標量的目標函數:
f(x)=0.1x12+2x22
condH=0.24=20→ill-conditioned
最大學習率限制條件
- For f(x), according to convex optimizaiton conclusions, we need step size η<L1 to have the fastest convergence, where L=maxx∇2f(x).
- To guarantee the convergence, we need to have η<L2 .
Supp: Preconditioning
在二階優化中,我們使用Hessian matrix的逆矩陣(或者pseudo inverse)來左乘梯度向量 i.e.Δx=H−1g,這樣的做法稱爲precondition,相當於將 H 映射爲一個單位矩陣,擁有分佈均勻的Spectrum,也即我們去優化的等價標函數的Hessian matrix爲良好的identity matrix。
與Dive into DL Section 11.4一節中不同,這裏將x12係數從1減小到了0.1。下面實現基於這個目標函數的梯度下降,並演示使用學習率爲0.4時自變量的迭代軌跡。
可以看到,同一位置上,目標函數在豎直方向(x2軸方向)比在水平方向(x1軸方向)的斜率的絕對值更大。因此,給定學習率,梯度下降迭代自變量時會使自變量在豎直方向比在水平方向移動幅度更大。那麼,我們需要一個較小的學習率從而避免自變量在豎直方向上越過目標函數最優解。然而,這會造成自變量在水平方向上朝最優解移動變慢。
下面我們試着將學習率調得稍大一點,此時自變量在豎直方向不斷越過最優解並逐漸發散。當學習率爲0.6時,L=maxx∇2f(x)=[4,0.2],L2=[0.5,10],已經超出範圍,因此在其中一個維度上無法收斂。
Solution to ill-condition
- Preconditioning gradient vector: applied in Adam, RMSProp, AdaGrad, Adelta, KFC, Natural gradient and other secord-order optimization algorithms.
- Averaging history gradient: like momentum, which allows larger learning rates to accelerate convergence; applied in Adam, RMSProp, SGD momentum.
Momentum Algorithm
動量法的提出是爲了解決梯度下降的上述問題。設時間步 t 的自變量爲 xt,學習率爲 ηt。
在時間步 t=0,動量法創建速度變量 m0,並將其元素初始化成 0。在時間步 t>0,動量法對每次迭代的步驟做如下修改:
mtxt←βmt−1+ηtgt,←xt−1−mt,
Another version:
mtxt←βmt−1+(1−β)gt,←xt−1−αtmt,
αt=1−βηt
其中,動量超參數 β滿足 0≤β<1。當 β=0 時,動量法等價於小批量隨機梯度下降。
在解釋動量法的數學原理前,讓我們先從實驗中觀察梯度下降在使用動量法後的迭代軌跡。
eta, beta = 0.4, 0.5
def momentum_2d(x1, x2, v1, v2):
v1 = beta * v1 + eta * 0.2 * x1
v2 = beta * v2 + eta * 4 * x2
return x1 - v1, x2 - v2, v1, v2
epoch 20, x1 -0.062843, x2 0.001202
可以看到使用較小的學習率 η=0.4 和動量超參數 β=0.5 時,動量法在豎直方向上的移動更加平滑,且在水平方向上更快逼近最優解。下面使用較大的學習率 η=0.6,此時自變量也不再發散
η=0.6: epoch 20, x1 0.007188, x2 0.002553
指數加權平均(Exponential Moving Average)
爲了從數學上理解動量法,讓我們先解釋一下指數加權移動平均(exponential moving average)。給定超參數 0≤β<1,當前時間步 t 的變量 yt 是上一時間步 t−1 的變量 yt−1 和當前時間步另一變量 xt 的線性組合:
yt=βyt−1+(1−β)xt.
我們可以對 yt 展開:
yt=(1−β)xt+βyt−1=(1−β)xt+(1−β)⋅βxt−1+β2yt−2=(1−β)xt+(1−β)⋅βxt−1+(1−β)⋅β2xt−2+β3yt−3=(1−β)i=0∑tβixt−i
(1−β)i=0∑tβi=1−β1−βt(1−β)=(1−βt)
指數加權平均的優勢
我們可以看到指數加權平均的求解過程實際上是一個遞推的過程,那麼這樣就會有一個非常大的好處,每當我要求從0到某一時刻(n)的平均值的時候,我並不需要像普通求解平均值的作爲,保留所有的時刻值,類和然後除以n。
而是隻需要保留0~(n-1)時刻的平均值和n時刻的值即可。也就是每次只需要保留常數值,然後進行運算即可,這對於深度學習中的海量數據來說,是一個很好的減少內存和空間的做法。
Supp
Approximate Average of 1−β1 Steps
令 n=1/(1−β),那麼 (1−1/n)n=β1/(1−β)。因爲
n→∞lim(1−n1)n=exp(−1)≈0.3679,
所以當 β→1時,β1/(1−β)=exp(−1),如 0.9520≈exp(−1)。如果把 exp(−1) 當作一個比較小的數,我們可以在近似中忽略所有含 β1/(1−β) 和比 β1/(1−β) 更高階的係數的項。例如,當 β=0.95 時,
yt≈0.05i=0∑190.95ixt−i.
因此,在實際中,我們常常將 yt 看作是對最近 1/(1−β) 個時間步的 xt 值的加權平均。例如,當 γ=0.95 時,yt 可以被看作對最近20個時間步的 xt 值的加權平均;當 β=0.9 時,yt 可以看作是對最近10個時間步的 xt 值的加權平均。而且,離當前時間步 t 越近的 xt 值獲得的權重越大(越接近1)。
由指數加權移動平均理解動量法
現在,我們對動量法的速度變量做變形:
mt←βmt−1+(1−β)(1−βηtgt).
Another version:
mt←βmt−1+(1−β)gt.
xt←xt−1−αtmt,
αt=1−βηt
由指數加權移動平均的形式可得,速度變量 vt 實際上對序列 {ηt−igt−i/(1−β):i=0,…,1/(1−β)−1} 做了指數加權移動平均。換句話說,相比於小批量隨機梯度下降,動量法在每個時間步的自變量更新量近似於將前者對應的最近 1/(1−β) 個時間步的更新量做了指數加權移動平均後再除以 1−β。所以,在動量法中,自變量在各個方向上的移動幅度不僅取決當前梯度,還取決於過去的各個梯度在各個方向上是否一致。在本節之前示例的優化問題中,所有梯度在水平方向上爲正(向右),而在豎直方向上時正(向上)時負(向下)。這樣,我們就可以使用較大的學習率,從而使自變量向最優解更快移動。
SGDM
相對於小批量隨機梯度下降,動量法需要對每一個自變量維護一個同它一樣形狀的速度變量,且超參數裏多了動量超參數。實現中,我們將速度變量用更廣義的狀態變量states
表示。
def init_momentum_states():
v_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
v_b = torch.zeros(1, dtype=torch.float32)
return (v_w, v_b)
def sgd_momentum(params, states, hyperparams):
for p, v in zip(params, states):
v.data = hyperparams['momentum'] * v.data + hyperparams['lr'] * p.grad.data
p.data -= v.data
AdaGrad
在之前介紹過的優化算法中,目標函數自變量的每一個元素在相同時間步都使用同一個學習率來自我迭代。舉個例子,假設目標函數爲f,自變量爲一個二維向量[x1,x2]⊤,該向量中每一個元素在迭代時都使用相同的學習率。例如,在學習率爲η的梯度下降中,元素x1和x2都使用相同的學習率η來自我迭代:
x1←x1−η∂x1∂f,x2←x2−η∂x2∂f.
在動量法中我們看到當x1和x2的梯度值有較大差別時,需要選擇足夠小的學習率使得自變量在梯度值較大的維度上不發散。但這樣會導致自變量在梯度值較小的維度上迭代過慢。動量法依賴指數加權移動平均使得自變量的更新方向更加一致,從而降低發散的可能。本節我們介紹AdaGrad算法,它根據自變量在每個維度的梯度值的大小來調整各個維度上的學習率,從而避免統一的學習率難以適應所有維度的問題 [1]。
Algorithm
AdaGrad算法會使用一個小批量隨機梯度gt按元素平方的累加變量st。在時間步0,AdaGrad將s0中每個元素初始化爲0。在時間步t,首先將小批量隨機梯度gt按元素平方後累加到變量st:
st←st−1+gt⊙gt,
其中⊙是按元素相乘。接着,我們將目標函數自變量中每個元素的學習率通過按元素運算重新調整一下:
xt←xt−1−st+ϵη⊙gt,
其中η是學習率,ϵ是爲了維持數值穩定性而添加的常數,如10−6。這裏開方、除法和乘法的運算都是按元素運算的。這些按元素運算使得目標函數自變量中每個元素都分別擁有自己的學習率。
Feature
需要強調的是,小批量隨機梯度按元素平方的累加變量st出現在學習率的分母項中。因此,如果目標函數有關自變量中某個元素的偏導數一直都較大,那麼該元素的學習率將下降較快;反之,如果目標函數有關自變量中某個元素的偏導數一直都較小,那麼該元素的學習率將下降較慢。然而,由於st一直在累加按元素平方的梯度,自變量中每個元素的學習率在迭代過程中一直在降低(或不變)。所以,當學習率在迭代早期降得較快且當前解依然不佳時,AdaGrad算法在迭代後期由於學習率過小,可能較難找到一個有用的解。
下面我們仍然以目標函數f(x)=0.1x12+2x22爲例觀察AdaGrad算法對自變量的迭代軌跡。我們實現AdaGrad算法並使用和上一節實驗中相同的學習率0.4。可以看到,自變量的迭代軌跡較平滑。但由於st的累加效果使學習率不斷衰減,自變量在迭代後期的移動幅度較小。
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
def adagrad_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
s1 += g1 ** 2
s2 += g2 ** 2
x1 -= eta / math.sqrt(s1 + eps) * g1
x2 -= eta / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def init_adagrad_states():
s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
s_b = torch.zeros(1, dtype=torch.float32)
return (s_w, s_b)
def adagrad(params, states, hyperparams):
eps = 1e-6
for p, s in zip(params, states):
s.data += (p.grad.data**2)
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
RMSProp
AdaGrad因爲調整學習率時分母上的變量st一直在累加按元素平方的小批量隨機梯度,所以目標函數自變量每個元素的學習率在迭代過程中一直在降低(或不變)。因此,當學習率在迭代早期降得較快且當前解依然不佳時,AdaGrad算法在迭代後期由於學習率過小,可能較難找到一個有用的解。爲了解決這一問題,RMSProp算法對AdaGrad算法做了修改。該算法源自Coursera上的一門課程,即“機器學習的神經網絡”。
Algorithm
我們之前提及過指數加權移動平均。不同於AdaGrad算法裏狀態變量st是截至時間步t所有小批量隨機梯度gt按元素平方和,RMSProp算法將這些梯度按元素平方做指數加權移動平均。具體來說,給定超參數0≤γ0計算
vt←βvt−1+(1−β)gt⊙gt.
和AdaGrad算法一樣,RMSProp算法將目標函數自變量中每個元素的學習率通過按元素運算重新調整,然後更新自變量
xt←xt−1−vt+ϵα⊙gt,
其中η是學習率,ϵ是爲了維持數值穩定性而添加的常數,如10−6。因爲RMSProp算法的狀態變量st是對平方項gt⊙gt的指數加權移動平均,所以可以看作是最近1/(1−β)個時間步的小批量隨機梯度平方項的加權平均。如此一來,自變量每個元素的學習率在迭代過程中就不再一直降低(或不變)。
照例,讓我們先觀察RMSProp算法對目標函數f(x)=0.1x12+2x22中自變量的迭代軌跡。回憶在AdaGrad算法一節使用的學習率爲0.4的AdaGrad算法,自變量在迭代後期的移動幅度較小。但在同樣的學習率下,RMSProp算法可以更快逼近最優解。
RMSProp 實現
def init_rmsprop_states():
s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
s_b = torch.zeros(1, dtype=torch.float32)
return (s_w, s_b)
def rmsprop(params, states, hyperparams):
gamma, eps = hyperparams['beta'], 1e-6
for p, s in zip(params, states):
s.data = gamma * s.data + (1 - gamma) * (p.grad.data)**2
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
我們將初始學習率設爲0.01,並將超參數γ設爲0.9。此時,變量st可看作是最近1/(1−0.9)=10個時間步的平方項gt⊙gt的加權平均。
AdaDelta
除了RMSProp算法以外,另一個常用優化算法AdaDelta算法也針對AdaGrad算法在迭代後期可能較難找到有用解的問題做了改進。有意思的是,AdaDelta算法沒有學習率這一超參數。
Algorithm
AdaDelta算法也像RMSProp算法一樣,使用了小批量隨機梯度gt按元素平方的指數加權移動平均變量st。在時間步0,它的所有元素被初始化爲0。給定超參數0≤ρ0,同RMSProp算法一樣計算
st←ρst−1+(1−ρ)gt⊙gt.
與RMSProp算法不同的是,AdaDelta算法還維護一個額外的狀態變量Δxt,其元素同樣在時間步0時被初始化爲0。我們使用Δxt−1來計算自變量的變化量:
gt′←st+ϵΔxt−1+ϵ⊙gt,
其中ϵ是爲了維持數值穩定性而添加的常數,如10−5。接着更新自變量:
xt←xt−1−gt′.
最後,我們使用Δxt來記錄自變量變化量gt′按元素平方的指數加權移動平均:
Δxt←ρΔxt−1+(1−ρ)gt′⊙gt′.
可以看到,如不考慮ϵ的影響,AdaDelta算法與RMSProp算法的不同之處在於使用Δxt−1來替代超參數η。
AdaDelta實現
AdaDelta算法需要對每個自變量維護兩個狀態變量,即st和Δxt。我們按AdaDelta算法中的公式實現該算法。
def init_adadelta_states():
s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
delta_w, delta_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta) in zip(params, states):
s[:] = rho * s + (1 - rho) * (p.grad.data**2)
g = p.grad.data * torch.sqrt((delta + eps) / (s + eps))
p.data -= g
delta[:] = rho * delta + (1 - rho) * g * g
Adam
Adam算法在RMSProp算法基礎上對小批量隨機梯度也做了指數加權移動平均。下面我們來介紹這個算法。
Algorithm
Adam算法使用了動量變量mt和RMSProp算法中小批量隨機梯度按元素平方的指數加權移動平均變量vt,並在時間步0將它們中每個元素初始化爲0。給定超參數0≤β1<1(算法作者建議設爲0.9),時間步t的動量變量mt即小批量隨機梯度gt的指數加權移動平均:
mt←β1mt−1+(1−β1)gt.
和RMSProp算法中一樣,給定超參數0≤β2<1(算法作者建議設爲0.999),
將小批量隨機梯度按元素平方後的項gt⊙gt做指數加權移動平均得到vt:
vt←β2vt−1+(1−β2)gt⊙gt.
由於我們將m0和s0中的元素都初始化爲0,
在時間步t我們得到mt=(1−β1)∑i=1tβ1t−igi。將過去各時間步小批量隨機梯度的權值相加,得到 (1−β1)∑i=1tβ1t−i=1−β1t。需要注意的是,當t較小時,過去各時間步小批量隨機梯度權值之和會較小。例如,當β1=0.9時,m1=0.1g1。爲了消除這樣的影響,對於任意時間步t,我們可以將mt再除以1−β1t,從而使過去各時間步小批量隨機梯度權值之和爲1。這也叫作偏差修正。在Adam算法中,我們對變量mt和vt均作偏差修正:
m^t←1−β1tmt,
v^t←1−β2tvt.
接下來,Adam算法使用以上偏差修正後的變量m^t和m^t,將模型參數中每個元素的學習率通過按元素運算重新調整:
gt′←v^t+ϵηm^t,
其中η是學習率,ϵ是爲了維持數值穩定性而添加的常數,如10−8。和AdaGrad算法、RMSProp算法以及AdaDelta算法一樣,目標函數自變量中每個元素都分別擁有自己的學習率。最後,使用gt′迭代自變量:
xt←xt−1−gt′.
Adam實現
我們按照Adam算法中的公式實現該算法。其中時間步t通過hyperparams
參數傳入adam
函數。
def init_adam_states():
v_w, v_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
return ((v_w, s_w), (v_b, s_b))
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1 - beta1) * p.grad.data
s[:] = beta2 * s + (1 - beta2) * p.grad.data**2
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p.data -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr) + eps)
hyperparams['t'] += 1