DL notes 05:深度學習相關的優化基礎(入門級)

一、優化與深度學習

優化與估計

儘管優化方法可以最小化深度學習中的損失函數值,但本質上優化方法達到的目標與深度學習的目標並不相同。

  • 優化方法目標:訓練集損失函數值
  • 深度學習目標:測試集損失函數值(泛化性)

現實世界中,訓練集數據和測試集數據的分佈存在差異,因此可能訓練和測試過程中誤差不盡相同。
train-test-error

優化在深度學習中的挑戰

1. 局部最小值

舉例:f(x)=xcosπxf(x) = x\cos \pi x
global-local-min

2. 鞍點

舉例:一維函數 f(x)=x3f(x) = x^{3}
1d-saddlepoint
對於高維變量輸入,我們通過二階導數Hessian矩陣來說明:
A=[2fx122fx1x22fx1xn2fx2x12fx222fx2xn2fxnx12fxnx22fxn2] A=\left[\begin{array}{cccc}{\frac{\partial^{2} f}{\partial x_{1}^{2}}} & {\frac{\partial^{2} f}{\partial x_{1} \partial x_{2}}} & {\cdots} & {\frac{\partial^{2} f}{\partial x_{1} \partial x_{n}}} \\ {\frac{\partial^{2} f}{\partial x_{2} \partial x_{1}}} & {\frac{\partial^{2} f}{\partial x_{2}^{2}}} & {\cdots} & {\frac{\partial^{2} f}{\partial x_{2} \partial x_{n}}} \\ {\vdots} & {\vdots} & {\ddots} & {\vdots} \\ {\frac{\partial^{2} f}{\partial x_{n} \partial x_{1}}} & {\frac{\partial^{2} f}{\partial x_{n} \partial x_{2}}} & {\cdots} & {\frac{\partial^{2} f}{\partial x_{n}^{2}}}\end{array}\right]
對於多維變量 x=[x1,x2,,xn]\mathbf{x} = [x_1,x_2,\dots,x_n], 當一階偏導fx=0\frac{\partial f}{\partial \mathbf{x}} = 0 ,二階偏導有正有負時,則 x\mathbf{x} 這時可稱爲函數f()f( \cdot )的鞍點。
我們以二維變量舉例:z=f(x)=x12x22z=f(\mathbf{x}) = x_1^2-x_2^2
2d-saddlepoint

3. 梯度消失

深度學習中的諸如sigmoidsigmoid,tanhtanh等激活函數在偏離中心的區域梯度逐漸消失,趨近於0。多層這樣的激活函數的堆疊,導致深層神經網絡訓練過程中會經常面臨梯度消失的難題。我們以tanhtanh函數圖例說明:
vanishing-pts

二、凸優化基礎

凸集

凸集1
凸集2
凸集3
在凸集中的示意圖中我們可以總結出凸集的幾何性質:

  1. 凸集中任意兩點連線上的點仍在凸集中
  2. 凸集的交集仍舊是凸集
  3. 凸集的並集不一定是凸集

凸函數

對於凸函數凸性的描述,我們可以引申凸集第一條性質得到如下的不等式:
λf(x)+(1λ)f(x)f(λx+(1λ)x)\lambda f(x)+(1-\lambda) f\left(x^{\prime}\right) \geq f\left(\lambda x+(1-\lambda) x^{\prime}\right)
我們舉三個函數的例子來直觀的說明函數凸性和上述不等式的對應關係:
convex-non-con
上面的不等式說明任意兩點的割線始終位於函數上方,我們泛化到任意點集{xi}\{x_i\},使用數學歸納法,可以證明凸函數滿足Jensen不等式:
iαif(xi)f(iαixi)\sum_{i} \alpha_{i} f\left(x_{i}\right) \geq f\left(\sum_{i} \alpha_{i} x_{i}\right)
在概率論中,如果把 αi\alpha_i 看成取值爲 xix_i 的離散變量 xx 的概率分佈, 就可以得到:
Ex[f(x)]f(Ex[x])E_{x}[f(x)] \geq f\left(E_{x}[x]\right)
其中, E[]E[ \cdot ] 表示期望。

凸函數的性質

  1. 無局部極小值
    證明:(反證法) 假設存在xXx \in X是局部最小值,則存在全局最小值xXx' \in X , 使得f(x)>f(x)f(x) > f(x') , 則對λ(0,1]\lambda \in(0,1] :
    f(x)>λf(x)+(1λ)f(x)f(λx+(1λ)x)f(x)>\lambda f(x)+(1-\lambda) f(x^{\prime}) \geq f(\lambda x+(1-\lambda) x^{\prime})
    可以觀察到f(x)f(x)xx的鄰域內並非達到局部最小,至此證畢。

  2. 與凸集的關係
    結論:對於凸函數f(x)f(x) ,定義集合Sb:={xxX and f(x)b}S_{b}:=\{x | x \in X \text { and } f(x) \leq b\} ,則集合SbS_b 爲凸集。
    證明:對於點x,xSbx,x' \in S_b , 有f(λx+(1λ)x)λf(x)+(1λ)f(x)bf\left(\lambda x+(1-\lambda) x^{\prime}\right) \leq \lambda f(x)+(1-\lambda) f\left(x^{\prime}\right) \leq b , 故λx+(1λ)xSb\lambda x+(1-\lambda) x^{\prime} \in S_{b} 對映凸集的第一條性質。
    這裏舉一個非凸函數來可視化這條性質:
    f(x,y)=0.5x2+cos(2πy)f(x, y)=0.5 x^{2}+\cos (2 \pi y)
    non-conv-2d
    從等高線圖中可以看出,當都滿足小於某一特定值時,可能形成無交集的兩部分,取值集合不具有凸性。

  3. 凸函數與二階導數
    結論f(x)0f(x)f^{''}(x) \ge 0 \Longleftrightarrow f(x)是凸函數
    證明:必要性(\Leftarrow
    對於凸函數:
    12f(x+ϵ)+12f(xϵ)f(x+ϵ2+xϵ2)=f(x)\frac{1}{2} f(x+\epsilon)+\frac{1}{2} f(x-\epsilon) \geq f\left(\frac{x+\epsilon}{2}+\frac{x-\epsilon}{2}\right)=f(x)
    故:
    f(x)=limε0f(x+ϵ)f(x)ϵf(x)f(xϵ)ϵϵf^{\prime \prime}(x)=\lim _{\varepsilon \rightarrow 0} \frac{\frac{f(x+\epsilon) - f(x)}{\epsilon}-\frac{f(x) - f(x-\epsilon)}{\epsilon}}{\epsilon}f(x)=limε0f(x+ϵ)+f(xϵ)2f(x)ϵ20f^{\prime \prime}(x)=\lim _{\varepsilon \rightarrow 0} \frac{f(x+\epsilon)+f(x-\epsilon)-2 f(x)}{\epsilon^{2}} \geq 0
    充分性(\Rightarrow
    a<x<ba < x < bf(x)f(x) 上的三個點,由拉格朗日中值定理:
    f(x)f(a)=(xa)f(α) for some α[a,x] and f(b)f(x)=(bx)f(β) for some β[x,b]\begin{array}{l}{f(x)-f(a)=(x-a) f^{\prime}(\alpha) \text { for some } \alpha \in[a, x] \text { and }} \\ {f(b)-f(x)=(b-x) f^{\prime}(\beta) \text { for some } \beta \in[x, b]}\end{array}
    根據單調性,有 f(β)f(α)f^{\prime}(\beta) \geq f^{\prime}(\alpha), 故:
    f(b)f(a)=f(b)f(x)+f(x)f(a)=(bx)f(β)+(xa)f(α)(ba)f(α)\begin{aligned} f(b)-f(a) &=f(b)-f(x)+f(x)-f(a) \\ &=(b-x) f^{\prime}(\beta)+(x-a) f^{\prime}(\alpha) \\ & \geq(b-a) f^{\prime}(\alpha) \end{aligned}
    凸函數二階導數

有限制條件凸函數的優化方法

舉例:
minimizexf(x) subject to ci(x)0 for all i{1,,N}\begin{array}{l}{\underset{\mathbf{x}}{\operatorname{minimize}} f(\mathbf{x})} \\ {\text { subject to } c_{i}(\mathbf{x}) \leq 0 \text { for all } i \in\{1, \ldots, N\}}\end{array}

1. 拉格朗日乘子法

L(x,α)=f(x)+iαici(x) where αi0L(\mathbf{x}, \alpha)=f(\mathbf{x})+\sum_{i} \alpha_{i} c_{i}(\mathbf{x}) \text { where } \alpha_{i} \geq 0

2. 添加懲罰項

欲使ci(x)0c_i(x) \leq 0 , 將項αici(x)\alpha_ic_i(x) 加入目標函數,如多層感知機章節中的λ2w2\frac{\lambda}{2} ||w||^2

3. 投影法

ProjX(x)=argminxXxx2\operatorname{Proj}_{X}(\mathbf{x})=\underset{\mathbf{x}^{\prime} \in X}{\operatorname{argmin}}\left\|\mathbf{x}-\mathbf{x}^{\prime}\right\|_{2}
proj

三、梯度下降

理想的梯度下降發生在局部凸函數中。

一維梯度下降

證明:沿梯度反方向移動自變量可以減小函數值
泰勒展開:
f(x+ϵ)=f(x)+ϵf(x)+O(ϵ2)f(x+\epsilon)=f(x)+\epsilon f^{\prime}(x)+\mathcal{O}\left(\epsilon^{2}\right)
代入沿梯度方向的移動量ηf(x),η(0,1]\eta f^{\prime}(x),\eta\in(0,1]:
f(xηf(x))=f(x)ηf2(x)+O(η2f2(x))f\left(x-\eta f^{\prime}(x)\right)=f(x)-\eta f^{\prime 2}(x)+\mathcal{O}\left(\eta^{2} f^{\prime 2}(x)\right)f(xηf(x))f(x)f\left(x-\eta f^{\prime}(x)\right) \lesssim f(x)xxηf(x)x \leftarrow x-\eta f^{\prime}(x)其中η\eta被稱爲學習率/步長,當學習率過大時,梯度下降無法收斂到局部最小值。
學習率過大
當學習率過小時,收斂速度緩慢。
學習率過小
除面臨如何選擇合適的學習率的問題,梯度下降還經常被局部最小值所困擾:
局部最小值

多維梯度下降

僅在一維梯度下降中就面臨學習率選擇,局部最小值陷阱的問題。在多維梯度下降中,這兩個問題變得更復雜。對於多維梯度下降,我們可以用數學語言表示成:
f(x)=[f(x)x1,f(x)x2,,f(x)xd]\nabla f(\mathbf{x})=\left[\frac{\partial f(\mathbf{x})}{\partial x_{1}}, \frac{\partial f(\mathbf{x})}{\partial x_{2}}, \dots, \frac{\partial f(\mathbf{x})}{\partial x_{d}}\right]^{\top}f(x+ϵ)=f(x)+ϵf(x)+O(ϵ2)f(\mathbf{x}+\epsilon)=f(\mathbf{x})+\epsilon^{\top} \nabla f(\mathbf{x})+\mathcal{O}\left(\|\epsilon\|^{2}\right)xxηf(x)\mathbf{x} \leftarrow \mathbf{x}-\eta \nabla f(\mathbf{x})
以二維梯度下降舉例:
f(x)=x12+2x22f(x) = x_1^2 + 2x_2^2

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

2d梯度下降
對於多維梯度下降,我們通過考慮二階導數來實現自動選擇學習率,這裏我們首先介紹牛頓法:

牛頓法

x+ϵx + \epsilon 處泰勒展開(此處使用二次展開,並使用peano餘項):
f(x+ϵ)=f(x)+ϵf(x)+12ϵf(x)ϵ+O(ϵ3)f(\mathbf{x}+\epsilon)=f(\mathbf{x})+\epsilon^{\top} \nabla f(\mathbf{x})+\frac{1}{2} \epsilon^{\top} \nabla \nabla^{\top} f(\mathbf{x}) \epsilon+\mathcal{O}\left(\|\epsilon\|^{3}\right)

最小值點處滿足:f(x)=0\nabla f(\mathbf{x})=0 即我們希望f(x+ϵ)=0\nabla f(\mathbf{x} + \epsilon)=0 , 對上式關於 ϵ\epsilon 求導,忽略高階無窮小,有:f(x)+Hfϵ=0 and hence ϵ=Hf1f(x)\nabla f(\mathbf{x})+\boldsymbol{H}_{f} \boldsymbol{\epsilon}=0 \text { and hence } \epsilon=-\boldsymbol{H}_{f}^{-1} \nabla f(\mathbf{x})
這樣我們就獲得了牛頓法對變量的更新公式。

牛頓法的收斂性分析

只考慮在函數爲凸函數(或在局部凸函數的範圍內), 且最小值點上 f(x)>0f''(x^*) > 0時的收斂速度:
xkx_k 爲第 kk 次迭代後 xx 的值, ek:=xkxe_k:=x_k−x^∗ 表示 xkx_k 到最小值點 xx^∗ 的距離,由f(x)=0f'(x^{*}) = 0,展開成Lagrange餘項形式:
0=f(xkek)=f(xk)ekf(xk)+12ek2f(ξk)for some ξk[xkek,xk]0=f^{\prime}\left(x_{k}-e_{k}\right)=f^{\prime}\left(x_{k}\right)-e_{k} f^{\prime \prime}\left(x_{k}\right)+\frac{1}{2} e_{k}^{2} f^{\prime \prime \prime}\left(\xi_{k}\right) \text{for some } \xi_{k} \in\left[x_{k}-e_{k}, x_{k}\right]
兩邊除以 f(xk)f′′(x_k) , 有:
ekf(xk)/f(xk)=12ek2f(ξk)/f(xk)e_{k}-f^{\prime}\left(x_{k}\right) / f^{\prime \prime}\left(x_{k}\right)=\frac{1}{2} e_{k}^{2} f^{\prime \prime \prime}\left(\xi_{k}\right) / f^{\prime \prime}\left(x_{k}\right)
代入更新方程 xk+1=xkf(xk)/f(xk)x_{k+1}=x_k−f′(x_k)/f′′(x_k) , 得到:
xkxf(xk)/f(xk)=12ek2f(ξk)/f(xk)x_k - x^{*} - f^{\prime}\left(x_{k}\right) / f^{\prime \prime}\left(x_{k}\right) =\frac{1}{2} e_{k}^{2} f^{\prime \prime \prime}\left(\xi_{k}\right) / f^{\prime \prime}\left(x_{k}\right)xk+1x=ek+1=12ek2f(ξk)/f(xk)x_{k+1} - x^{*} = e_{k+1} = \frac{1}{2} e_{k}^{2} f^{\prime \prime \prime}\left(\xi_{k}\right) / f^{\prime \prime}\left(x_{k}\right)
12f(ξk)/f(xk)c\frac{1}{2} f^{\prime \prime \prime}\left(\xi_{k}\right) / f^{\prime \prime}\left(x_{k}\right) \leq c 時,有:
ek+1cek2e_{k+1} \leq c e_{k}^{2}
我們稱之爲具有二階收斂速度。

預處理(Heissan陣輔助梯度下降)

在牛頓法中,我們需要計算Heissan矩陣來進行變量的更新,Heissan矩陣大小是d×dd\times d,其中dd代表變量的維度,計算複雜度爲O(d2)\mathcal{O}(d^{2})。我們改用Heissan陣的對角陣diag(Hf)diag(H_f)來代替Heissan陣進行變量更新,diag(Hf)diag(H_f)的計算複雜度降至O(d)\mathcal{O}(d),同時也能對各維度變量變化尺度進行歸一化的處理,保證各方向下降速率基本一致。
xxηdiag(Hf)1x\mathbf{x} \leftarrow \mathbf{x}-\eta \operatorname{diag}\left(H_{f}\right)^{-1} \nabla \mathbf{x}

梯度下降與線性搜索(共軛梯度法)(略)

隨機梯度下降

在實際的優化過程中,對於求解Heissan陣這樣耗費計算的步驟仍然是無法實現的,因此對牛頓法進行進一步的鬆弛,我們得到簡單計算即可迭代的隨機梯度下降算法。

隨機梯度下降參數更新

對於有 nn 個樣本對訓練數據集,設 fi(x)f_i(x) 是第 ii 個樣本的損失函數, 則目標函數爲:
f(x)=1ni=1nfi(x)f(\mathbf{x})=\frac{1}{n} \sum_{i=1}^{n} f_{i}(\mathbf{x})
其梯度爲:
f(x)=1ni=1nfi(x)\nabla f(\mathbf{x})=\frac{1}{n} \sum_{i=1}^{n} \nabla f_{i}(\mathbf{x})
使用該梯度的一次更新的時間複雜度爲O(n)\mathcal{O}(n),這種梯度下降的更新算法也被稱爲最速梯度下降法。我們每次更新都要計算數據集中全部樣本的梯度。
而隨機梯度下降是最速梯度下降的變種,每次迭代僅對一個樣本計算梯度,其時間複雜度爲O(1)\mathcal{O}(1):
xxηfi(x)\mathbf{x} \leftarrow \mathbf{x}-\eta \nabla f_{i}(\mathbf{x})
且有:
Eifi(x)=1ni=1nfi(x)=f(x)\mathbb{E}_{i} \nabla f_{i}(\mathbf{x})=\frac{1}{n} \sum_{i=1}^{n} \nabla f_{i}(\mathbf{x})=\nabla f(\mathbf{x})

舉例:
f(x1,x2)=x12+2x22f(x_1, x_2) = x_1^2 + 2 x_2^2

eta = 0.1
lr = (lambda: 1)  # Constant learning rate

def f(x1, x2):
    return x1 ** 2 + 2 * x2 ** 2  # Objective

def gradf(x1, x2):
    return (2 * x1, 4 * x2)  # Gradient

def sgd(x1, x2):  # Simulate noisy gradient
    global lr  # Learning rate scheduler
    (g1, g2) = gradf(x1, x2)  # Compute gradient
    (g1, g2) = (g1 + np.random.normal(0.1), g2 + np.random.normal(0.1))
    eta_t = eta * lr()  # Learning rate at time t
    return (x1 - eta_t * g1, x2 - eta_t * g2)  # Update variables

sgd

動態學習率

在隨機梯度下降的過程中,我們可以發現學習率應當隨着學習過程的進行而逐漸減小。這裏借用李宏毅老師的課件來說明學習率設定的重要性:
學習率的重要
下面介紹幾種設置動態學習率的方法:
η(t)=ηi if titti+1 piecewise constant η(t)=η0eλt exponential η(t)=η0(βt+1)α polynomial \begin{array}{ll}{\eta(t)=\eta_{i} \text { if } t_{i} \leq t \leq t_{i+1}} & {\text { piecewise constant }} \\ {\eta(t)=\eta_{0} \cdot e^{-\lambda t}} & {\text { exponential }} \\ {\eta(t)=\eta_{0} \cdot(\beta t+1)^{-\alpha}} & {\text { polynomial }}\end{array}

小批量隨機梯度下降

在使用隨機梯度下降算法的過程中我們會發現,更新的梯度受所選定的樣本影響很大,即方差很大。會導致梯度下降過程中出現偏離局部最小值方向的更新。但如果使用所有樣本進行梯度計算又會佔用高計算資源,因此我們採用一種折衷的辦法來進行隨機梯度下降。也就是批量梯度下降(與小批量梯度下降的原理相同)。隨機從樣本中選取一部分數據計算當前的平均梯度方向,迭代速度比最速梯度下降法更快,比隨機梯度下降每次更新梯度方向精度更高。

隨機梯度下降:loss: 0.245968, 0.463836 sec per epoch,2 epoch
single-sgd
小批量隨機梯度下降:loss: 0.243900, 0.065017 sec per epoch,batch_size=10,2 epoch
minibatch

四、優化算法

梯度下降優化法經歷了SGD→SGDM→NAG→AdaGrad→AdaDelta→RMSProp→Adam→Nadam這樣的發展歷程。之所以會不斷地提出更加優化的方法,究其原因,是引入了動量(Momentum)這個概念。最初,人們引入一階動量來給梯度下降法加入慣性(即,越陡的坡可以允許跑得更快些)。後來,在引入二階動量之後,才真正意味着“自適應學習率”優化算法時代的到來。

動量法(Momentum)

Dive into DL Section 11.4 中,目標函數有關自變量的梯度代表了目標函數在自變量當前位置下降最快的方向。因此,梯度下降也叫作最速下降(steepest descent)。在每次迭代中,梯度下降根據自變量當前位置,沿着當前位置的梯度更新自變量。然而,如果自變量的迭代方向僅僅取決於自變量當前位置,這可能會帶來一些問題。對於noisy gradient,我們需要謹慎的選取學習率和batch size, 來控制梯度方差和收斂的結果。
gt=w1BtiBtf(xi,wt1)=1BtiBtgi,t1. \mathbf{g}_t = \partial_{\mathbf{w}} \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} f(\mathbf{x}_{i}, \mathbf{w}_{t-1}) = \frac{1}{|\mathcal{B}_t|} \sum_{i \in \mathcal{B}_t} \mathbf{g}_{i, t-1}.

An ill-conditioned Problem

Condition Number of Hessian Matrix:

condH=λmaxλmin cond_{H} = \frac{\lambda_{max}}{\lambda_{min}}

where λmax,λmin\lambda_{max}, \lambda_{min} is the maximum amd minimum eignvalue of Hessian matrix.

讓我們考慮一個輸入和輸出分別爲二維向量x=[x1,x2]\boldsymbol{x} = [x_1, x_2]^\top和標量的目標函數:

f(x)=0.1x12+2x22 f(\boldsymbol{x})=0.1x_1^2+2x_2^2

condH=40.2=20ill-conditioned cond_{H} = \frac{4}{0.2} = 20 \quad \rightarrow \quad \text{ill-conditioned}

最大學習率限制條件

  • For f(x)f(x), according to convex optimizaiton conclusions, we need step size η<1L\eta < \frac{1}{L} to have the fastest convergence, where L=maxx2f(x)L=max_{x} \nabla^{2}f(x).
  • To guarantee the convergence, we need to have η<2L\eta < \frac{2}{L} .

Supp: Preconditioning

在二階優化中,我們使用Hessian matrix的逆矩陣(或者pseudo inverse)來左乘梯度向量 i.e.Δx=H1gi.e. \Delta_{x} = H^{-1}\mathbf{g},這樣的做法稱爲precondition,相當於將 HH 映射爲一個單位矩陣,擁有分佈均勻的Spectrum,也即我們去優化的等價標函數的Hessian matrix爲良好的identity matrix。

Dive into DL Section 11.4一節中不同,這裏將x12x_1^2係數從11減小到了0.10.1。下面實現基於這個目標函數的梯度下降,並演示使用學習率爲0.40.4時自變量的迭代軌跡。
lr調整
可以看到,同一位置上,目標函數在豎直方向(x2x_2軸方向)比在水平方向(x1x_1軸方向)的斜率的絕對值更大。因此,給定學習率,梯度下降迭代自變量時會使自變量在豎直方向比在水平方向移動幅度更大。那麼,我們需要一個較小的學習率從而避免自變量在豎直方向上越過目標函數最優解。然而,這會造成自變量在水平方向上朝最優解移動變慢。

下面我們試着將學習率調得稍大一點,此時自變量在豎直方向不斷越過最優解並逐漸發散。當學習率爲0.6時,L=maxx2f(x)=[4,0.2]L= max_{x} \nabla^{2}f(x) = [4,0.2]2L=[0.5,10]\frac{2}{L} = [0.5,10],已經超出範圍,因此在其中一個維度上無法收斂。
lr調整2

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

動量法的提出是爲了解決梯度下降的上述問題。設時間步 tt 的自變量爲 xt\boldsymbol{x}_t,學習率爲 ηt\eta_t
在時間步 t=0t=0,動量法創建速度變量 m0\boldsymbol{m}_0,並將其元素初始化成 0。在時間步 t>0t>0,動量法對每次迭代的步驟做如下修改:

mtβmt1+ηtgt,xtxt1mt, \begin{aligned} \boldsymbol{m}_t &\leftarrow \beta \boldsymbol{m}_{t-1} + \eta_t \boldsymbol{g}_t, \\ \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{m}_t, \end{aligned}

Another version:

mtβmt1+(1β)gt,xtxt1αtmt, \begin{aligned} \boldsymbol{m}_t &\leftarrow \beta \boldsymbol{m}_{t-1} + (1-\beta) \boldsymbol{g}_t, \\ \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \alpha_t \boldsymbol{m}_t, \end{aligned}

αt=ηt1β \alpha_t = \frac{\eta_t}{1-\beta}

其中,動量超參數 β\beta滿足 0β<10 \leq \beta < 1。當 β=0\beta=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
momentum1
可以看到使用較小的學習率 η=0.4\eta=0.4 和動量超參數 β=0.5\beta=0.5 時,動量法在豎直方向上的移動更加平滑,且在水平方向上更快逼近最優解。下面使用較大的學習率 η=0.6\eta=0.6,此時自變量也不再發散
η=0.6\eta=0.6: epoch 20, x1 0.007188, x2 0.002553
momentum2

指數加權平均(Exponential Moving Average)

爲了從數學上理解動量法,讓我們先解釋一下指數加權移動平均(exponential moving average)。給定超參數 0β<10 \leq \beta < 1,當前時間步 tt 的變量 yty_t 是上一時間步 t1t-1 的變量 yt1y_{t-1} 和當前時間步另一變量 xtx_t 的線性組合:

yt=βyt1+(1β)xt. y_t = \beta y_{t-1} + (1-\beta) x_t.

我們可以對 yty_t 展開:

yt=(1β)xt+βyt1=(1β)xt+(1β)βxt1+β2yt2=(1β)xt+(1β)βxt1+(1β)β2xt2+β3yt3=(1β)i=0tβixti \begin{aligned} y_t &= (1-\beta) x_t + \beta y_{t-1}\\ &= (1-\beta)x_t + (1-\beta) \cdot \beta x_{t-1} + \beta^2y_{t-2}\\ &= (1-\beta)x_t + (1-\beta) \cdot \beta x_{t-1} + (1-\beta) \cdot \beta^2x_{t-2} + \beta^3y_{t-3}\\ &= (1-\beta) \sum_{i=0}^{t} \beta^{i}x_{t-i} \end{aligned}

(1β)i=0tβi=1βt1β(1β)=(1βt) (1-\beta)\sum_{i=0}^{t} \beta^{i} = \frac{1-\beta^{t}}{1-\beta} (1-\beta) = (1-\beta^{t})

指數加權平均的優勢

我們可以看到指數加權平均的求解過程實際上是一個遞推的過程,那麼這樣就會有一個非常大的好處,每當我要求從0到某一時刻(n)的平均值的時候,我並不需要像普通求解平均值的作爲,保留所有的時刻值,類和然後除以n。

而是隻需要保留0~(n-1)時刻的平均值和n時刻的值即可。也就是每次只需要保留常數值,然後進行運算即可,這對於深度學習中的海量數據來說,是一個很好的減少內存和空間的做法。

Supp

Approximate Average of 11β\frac{1}{1-\beta} Steps

n=1/(1β)n = 1/(1-\beta),那麼 (11/n)n=β1/(1β)\left(1-1/n\right)^n = \beta^{1/(1-\beta)}。因爲

limn(11n)n=exp(1)0.3679, \lim_{n \rightarrow \infty} \left(1-\frac{1}{n}\right)^n = \exp(-1) \approx 0.3679,

所以當 β1\beta \rightarrow 1時,β1/(1β)=exp(1)\beta^{1/(1-\beta)}=\exp(-1),如 0.9520exp(1)0.95^{20} \approx \exp(-1)。如果把 exp(1)\exp(-1) 當作一個比較小的數,我們可以在近似中忽略所有含 β1/(1β)\beta^{1/(1-\beta)} 和比 β1/(1β)\beta^{1/(1-\beta)} 更高階的係數的項。例如,當 β=0.95\beta=0.95 時,

yt0.05i=0190.95ixti. y_t \approx 0.05 \sum_{i=0}^{19} 0.95^i x_{t-i}.

因此,在實際中,我們常常將 yty_t 看作是對最近 1/(1β)1/(1-\beta) 個時間步的 xtx_t 值的加權平均。例如,當 γ=0.95\gamma = 0.95 時,yty_t 可以被看作對最近20個時間步的 xtx_t 值的加權平均;當 β=0.9\beta = 0.9 時,yty_t 可以看作是對最近10個時間步的 xtx_t 值的加權平均。而且,離當前時間步 tt 越近的 xtx_t 值獲得的權重越大(越接近1)。

由指數加權移動平均理解動量法

現在,我們對動量法的速度變量做變形:

mtβmt1+(1β)(ηt1βgt). \boldsymbol{m}_t \leftarrow \beta \boldsymbol{m}_{t-1} + (1 - \beta) \left(\frac{\eta_t}{1 - \beta} \boldsymbol{g}_t\right).

Another version:

mtβmt1+(1β)gt. \boldsymbol{m}_t \leftarrow \beta \boldsymbol{m}_{t-1} + (1 - \beta) \boldsymbol{g}_t.

xtxt1αtmt, \begin{aligned} \boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \alpha_t \boldsymbol{m}_t, \end{aligned}

αt=ηt1β \alpha_t = \frac{\eta_t}{1-\beta}

由指數加權移動平均的形式可得,速度變量 vt\boldsymbol{v}_t 實際上對序列 {ηtigti/(1β):i=0,,1/(1β)1}\{\eta_{t-i}\boldsymbol{g}_{t-i} /(1-\beta):i=0,\ldots,1/(1-\beta)-1\} 做了指數加權移動平均。換句話說,相比於小批量隨機梯度下降,動量法在每個時間步的自變量更新量近似於將前者對應的最近 1/(1β)1/(1-\beta) 個時間步的更新量做了指數加權移動平均後再除以 1β1-\beta。所以,在動量法中,自變量在各個方向上的移動幅度不僅取決當前梯度,還取決於過去的各個梯度在各個方向上是否一致。在本節之前示例的優化問題中,所有梯度在水平方向上爲正(向右),而在豎直方向上時正(向上)時負(向下)。這樣,我們就可以使用較大的學習率,從而使自變量向最優解更快移動。

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

在之前介紹過的優化算法中,目標函數自變量的每一個元素在相同時間步都使用同一個學習率來自我迭代。舉個例子,假設目標函數爲ff,自變量爲一個二維向量[x1,x2][x_1, x_2]^\top,該向量中每一個元素在迭代時都使用相同的學習率。例如,在學習率爲η\eta的梯度下降中,元素x1x_1x2x_2都使用相同的學習率η\eta來自我迭代:

x1x1ηfx1,x2x2ηfx2. x_1 \leftarrow x_1 - \eta \frac{\partial{f}}{\partial{x_1}}, \quad x_2 \leftarrow x_2 - \eta \frac{\partial{f}}{\partial{x_2}}.

在動量法中我們看到當x1x_1x2x_2的梯度值有較大差別時,需要選擇足夠小的學習率使得自變量在梯度值較大的維度上不發散。但這樣會導致自變量在梯度值較小的維度上迭代過慢。動量法依賴指數加權移動平均使得自變量的更新方向更加一致,從而降低發散的可能。本節我們介紹AdaGrad算法,它根據自變量在每個維度的梯度值的大小來調整各個維度上的學習率,從而避免統一的學習率難以適應所有維度的問題 [1]。

Algorithm

AdaGrad算法會使用一個小批量隨機梯度gt\boldsymbol{g}_t按元素平方的累加變量st\boldsymbol{s}_t。在時間步0,AdaGrad將s0\boldsymbol{s}_0中每個元素初始化爲0。在時間步tt,首先將小批量隨機梯度gt\boldsymbol{g}_t按元素平方後累加到變量st\boldsymbol{s}_t

stst1+gtgt, \boldsymbol{s}_t \leftarrow \boldsymbol{s}_{t-1} + \boldsymbol{g}_t \odot \boldsymbol{g}_t,

其中\odot是按元素相乘。接着,我們將目標函數自變量中每個元素的學習率通過按元素運算重新調整一下:

xtxt1ηst+ϵgt, \boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \frac{\eta}{\sqrt{\boldsymbol{s}_t + \epsilon}} \odot \boldsymbol{g}_t,

其中η\eta是學習率,ϵ\epsilon是爲了維持數值穩定性而添加的常數,如10610^{-6}。這裏開方、除法和乘法的運算都是按元素運算的。這些按元素運算使得目標函數自變量中每個元素都分別擁有自己的學習率。

Feature

需要強調的是,小批量隨機梯度按元素平方的累加變量st\boldsymbol{s}_t出現在學習率的分母項中。因此,如果目標函數有關自變量中某個元素的偏導數一直都較大,那麼該元素的學習率將下降較快;反之,如果目標函數有關自變量中某個元素的偏導數一直都較小,那麼該元素的學習率將下降較慢。然而,由於st\boldsymbol{s}_t一直在累加按元素平方的梯度,自變量中每個元素的學習率在迭代過程中一直在降低(或不變)。所以,當學習率在迭代早期降得較快且當前解依然不佳時,AdaGrad算法在迭代後期由於學習率過小,可能較難找到一個有用的解。

下面我們仍然以目標函數f(x)=0.1x12+2x22f(\boldsymbol{x})=0.1x_1^2+2x_2^2爲例觀察AdaGrad算法對自變量的迭代軌跡。我們實現AdaGrad算法並使用和上一節實驗中相同的學習率0.4。可以看到,自變量的迭代軌跡較平滑。但由於st\boldsymbol{s}_t的累加效果使學習率不斷衰減,自變量在迭代後期的移動幅度較小。

# 二維示例
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

adagrad

   # 多維實現
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\boldsymbol{s}_t一直在累加按元素平方的小批量隨機梯度,所以目標函數自變量每個元素的學習率在迭代過程中一直在降低(或不變)。因此,當學習率在迭代早期降得較快且當前解依然不佳時,AdaGrad算法在迭代後期由於學習率過小,可能較難找到一個有用的解。爲了解決這一問題,RMSProp算法對AdaGrad算法做了修改。該算法源自Coursera上的一門課程,即“機器學習的神經網絡”。

Algorithm

我們之前提及過指數加權移動平均。不同於AdaGrad算法裏狀態變量st\boldsymbol{s}_t是截至時間步tt所有小批量隨機梯度gt\boldsymbol{g}_t按元素平方和,RMSProp算法將這些梯度按元素平方做指數加權移動平均。具體來說,給定超參數0γ00 \leq \gamma 0計算

vtβvt1+(1β)gtgt. \boldsymbol{v}_t \leftarrow \beta \boldsymbol{v}_{t-1} + (1 - \beta) \boldsymbol{g}_t \odot \boldsymbol{g}_t.

和AdaGrad算法一樣,RMSProp算法將目標函數自變量中每個元素的學習率通過按元素運算重新調整,然後更新自變量

xtxt1αvt+ϵgt, \boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \frac{\alpha}{\sqrt{\boldsymbol{v}_t + \epsilon}} \odot \boldsymbol{g}_t,

其中η\eta是學習率,ϵ\epsilon是爲了維持數值穩定性而添加的常數,如10610^{-6}。因爲RMSProp算法的狀態變量st\boldsymbol{s}_t是對平方項gtgt\boldsymbol{g}_t \odot \boldsymbol{g}_t的指數加權移動平均,所以可以看作是最近1/(1β)1/(1-\beta)個時間步的小批量隨機梯度平方項的加權平均。如此一來,自變量每個元素的學習率在迭代過程中就不再一直降低(或不變)。

照例,讓我們先觀察RMSProp算法對目標函數f(x)=0.1x12+2x22f(\boldsymbol{x})=0.1x_1^2+2x_2^2中自變量的迭代軌跡。回憶在AdaGrad算法一節使用的學習率爲0.4的AdaGrad算法,自變量在迭代後期的移動幅度較小。但在同樣的學習率下,RMSProp算法可以更快逼近最優解。
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,並將超參數γ\gamma設爲0.9。此時,變量st\boldsymbol{s}_t可看作是最近1/(10.9)=101/(1-0.9) = 10個時間步的平方項gtgt\boldsymbol{g}_t \odot \boldsymbol{g}_t的加權平均。

AdaDelta

除了RMSProp算法以外,另一個常用優化算法AdaDelta算法也針對AdaGrad算法在迭代後期可能較難找到有用解的問題做了改進。有意思的是,AdaDelta算法沒有學習率這一超參數。

Algorithm

AdaDelta算法也像RMSProp算法一樣,使用了小批量隨機梯度gt\boldsymbol{g}_t按元素平方的指數加權移動平均變量st\boldsymbol{s}_t。在時間步0,它的所有元素被初始化爲0。給定超參數0ρ00 \leq \rho 0,同RMSProp算法一樣計算

stρst1+(1ρ)gtgt. \boldsymbol{s}_t \leftarrow \rho \boldsymbol{s}_{t-1} + (1 - \rho) \boldsymbol{g}_t \odot \boldsymbol{g}_t.

與RMSProp算法不同的是,AdaDelta算法還維護一個額外的狀態變量Δxt\Delta\boldsymbol{x}_t,其元素同樣在時間步0時被初始化爲0。我們使用Δxt1\Delta\boldsymbol{x}_{t-1}來計算自變量的變化量:

gtΔxt1+ϵst+ϵgt, \boldsymbol{g}_t' \leftarrow \sqrt{\frac{\Delta\boldsymbol{x}_{t-1} + \epsilon}{\boldsymbol{s}_t + \epsilon}} \odot \boldsymbol{g}_t,

其中ϵ\epsilon是爲了維持數值穩定性而添加的常數,如10510^{-5}。接着更新自變量:

xtxt1gt. \boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{g}'_t.

最後,我們使用Δxt\Delta\boldsymbol{x}_t來記錄自變量變化量gt\boldsymbol{g}'_t按元素平方的指數加權移動平均:

ΔxtρΔxt1+(1ρ)gtgt. \Delta\boldsymbol{x}_t \leftarrow \rho \Delta\boldsymbol{x}_{t-1} + (1 - \rho) \boldsymbol{g}'_t \odot \boldsymbol{g}'_t.

可以看到,如不考慮ϵ\epsilon的影響,AdaDelta算法與RMSProp算法的不同之處在於使用Δxt1\sqrt{\Delta\boldsymbol{x}_{t-1}}來替代超參數η\eta

AdaDelta實現

AdaDelta算法需要對每個自變量維護兩個狀態變量,即st\boldsymbol{s}_tΔxt\Delta\boldsymbol{x}_t。我們按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\boldsymbol{m}_t和RMSProp算法中小批量隨機梯度按元素平方的指數加權移動平均變量vt\boldsymbol{v}_t,並在時間步0將它們中每個元素初始化爲0。給定超參數0β1<10 \leq \beta_1 < 1(算法作者建議設爲0.9),時間步tt的動量變量mt\boldsymbol{m}_t即小批量隨機梯度gt\boldsymbol{g}_t的指數加權移動平均:

mtβ1mt1+(1β1)gt. \boldsymbol{m}_t \leftarrow \beta_1 \boldsymbol{m}_{t-1} + (1 - \beta_1) \boldsymbol{g}_t.

和RMSProp算法中一樣,給定超參數0β2<10 \leq \beta_2 < 1(算法作者建議設爲0.999),
將小批量隨機梯度按元素平方後的項gtgt\boldsymbol{g}_t \odot \boldsymbol{g}_t做指數加權移動平均得到vt\boldsymbol{v}_t

vtβ2vt1+(1β2)gtgt. \boldsymbol{v}_t \leftarrow \beta_2 \boldsymbol{v}_{t-1} + (1 - \beta_2) \boldsymbol{g}_t \odot \boldsymbol{g}_t.

由於我們將m0\boldsymbol{m}_0s0\boldsymbol{s}_0中的元素都初始化爲0,
在時間步tt我們得到mt=(1β1)i=1tβ1tigi\boldsymbol{m}_t = (1-\beta_1) \sum_{i=1}^t \beta_1^{t-i} \boldsymbol{g}_i。將過去各時間步小批量隨機梯度的權值相加,得到 (1β1)i=1tβ1ti=1β1t(1-\beta_1) \sum_{i=1}^t \beta_1^{t-i} = 1 - \beta_1^t。需要注意的是,當tt較小時,過去各時間步小批量隨機梯度權值之和會較小。例如,當β1=0.9\beta_1 = 0.9時,m1=0.1g1\boldsymbol{m}_1 = 0.1\boldsymbol{g}_1。爲了消除這樣的影響,對於任意時間步tt,我們可以將mt\boldsymbol{m}_t再除以1β1t1 - \beta_1^t,從而使過去各時間步小批量隨機梯度權值之和爲1。這也叫作偏差修正。在Adam算法中,我們對變量mt\boldsymbol{m}_tvt\boldsymbol{v}_t均作偏差修正:

m^tmt1β1t, \hat{\boldsymbol{m}}_t \leftarrow \frac{\boldsymbol{m}_t}{1 - \beta_1^t},

v^tvt1β2t. \hat{\boldsymbol{v}}_t \leftarrow \frac{\boldsymbol{v}_t}{1 - \beta_2^t}.

接下來,Adam算法使用以上偏差修正後的變量m^t\hat{\boldsymbol{m}}_tm^t\hat{\boldsymbol{m}}_t,將模型參數中每個元素的學習率通過按元素運算重新調整:

gtηm^tv^t+ϵ, \boldsymbol{g}_t' \leftarrow \frac{\eta \hat{\boldsymbol{m}}_t}{\sqrt{\hat{\boldsymbol{v}}_t} + \epsilon},

其中η\eta是學習率,ϵ\epsilon是爲了維持數值穩定性而添加的常數,如10810^{-8}。和AdaGrad算法、RMSProp算法以及AdaDelta算法一樣,目標函數自變量中每個元素都分別擁有自己的學習率。最後,使用gt\boldsymbol{g}_t'迭代自變量:

xtxt1gt. \boldsymbol{x}_t \leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{g}_t'.

Adam實現

我們按照Adam算法中的公式實現該算法。其中時間步tt通過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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章