推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

無論是什么優(yōu)化算法,最后都可以用一個簡單的公式抽象:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是參數(shù),而

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是參數(shù)的增量,而各種優(yōu)化算法的主要區(qū)別在于對

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的計算不同,本文總結(jié)了下面十個優(yōu)化算法的公式,以及簡單的Python實現(xiàn):

  1. SGD
  2. Momentum
  3. Nesterov Momentum
  4. AdaGrad
  5. RMSProp
  6. AdaDelta
  7. Adam
  8. AdaMax
  9. Nadam
  10. NadaMax

SGD

雖然有湊數(shù)的嫌疑,不過還是把SGD也順帶說一下,就算做一個符號說明了。常規(guī)的隨機(jī)梯度下降公式如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

其中

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是學(xué)習(xí)率,

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是損失關(guān)于參數(shù)的梯度(有的資料中會寫成

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

等形式),不過相比SGD,用的更多的還是小批量梯度下降(mBGD)算法,不同之處在于一次訓(xùn)練使用多個樣本,然后取所有參與訓(xùn)練樣本梯度的平均來更新參數(shù),公式如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

其中

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是第

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

次訓(xùn)練中

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

個樣本損失關(guān)于參數(shù)梯度的均值,如無特別聲明,下文所出現(xiàn)

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

也遵循該定義。

另外

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

或者

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

在下面的優(yōu)化算法中,只是作為一個傳入的變量,其具體的計算是由其他模塊負(fù)責(zé),可以參考下面兩個鏈接:

Numpy實現(xiàn)神經(jīng)網(wǎng)絡(luò)框架(3)——線性層反向傳播推導(dǎo)及實現(xiàn):

https://zhuanlan.zhihu.com/p/67854272

卷積核梯度計算的推導(dǎo)及實現(xiàn):

https://zhuanlan.zhihu.com/p/64248652

Momentum

Momentum,也就是動量的意思。該算法將梯度下降的過程視為一個物理系統(tǒng),下圖是在百度圖片中找的(侵刪)。

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

圖片來自網(wǎng)絡(luò)

如上圖所示,在該物理系統(tǒng)中有一個小球(質(zhì)點(diǎn)),它所處的水平方向的位置對應(yīng)為

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的值,而垂直方向?qū)?yīng)為損失。設(shè)其質(zhì)量

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,在第

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

時刻,在單位時間內(nèi),該質(zhì)點(diǎn)受外力而造成的動量改變?yōu)椋?/span>

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

(1.1)到(1.2)是因為

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,所以約去了。另外受到的外力可以分為兩個分量:重力沿斜面向下的力

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

和粘性阻尼力

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代入(1.2)式中:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

然后對“位置”進(jìn)行更新:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

所以這里

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,另外

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的方向與損失的梯度方向相反,并取系數(shù)為

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代入(1.4),得到速度的更新公式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

進(jìn)一步的,將(1.6)式展開,可以得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

可以看出來是一個變相的等比數(shù)列之和,且公比小于1,所以存在極限,當(dāng)

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

足夠大時,

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

趨近于

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

實現(xiàn)代碼:

import numpy as npclass Momentum(object): def __init__(self, alpha=0.9, lr=1e-3): self.alpha = alpha # 動量系數(shù) self.lr = lr # 學(xué)習(xí)率 self.v = 0 # 初始速度為0 def update(self, g: np.ndarray): # g = J'(w) 為本輪訓(xùn)練參數(shù)的梯度 self.v = self.alpha * self.v - self.lr * g # 公式 return self.v # 返回的是參數(shù)的增量,下同

以上是基于指數(shù)衰減的實現(xiàn)方式,另外有的Momentum算法中會使用指數(shù)加權(quán)平均來實現(xiàn),主要公式如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

不過該方式因為

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,剛開始時

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

會比期望值要小,需要進(jìn)行修正,下面的Adam等算法會使用該方式

Nesterov Momentum

Nesterov Momentum是Momentum的改進(jìn)版本,與Momentum唯一區(qū)別就是,Nesterov先用當(dāng)前的速度

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

更新一遍參數(shù),得到一個臨時參數(shù)

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,然后使用這個臨時參數(shù)計算本輪訓(xùn)練的梯度。相當(dāng)于是小球預(yù)判了自己下一時刻的位置,并提前使用該位置的梯度更新 :

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

為了更加直觀,還是上幾個圖吧,以下是Momentum算法

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的更新過程:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

假設(shè)下一個位置的梯度如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

那么Nesterov Momentum就提前使用這個梯度進(jìn)行更新:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

整體來看Nesterov的表現(xiàn)要好于Momentum,至于代碼實現(xiàn)的話因為主要變化的是

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,所以可以之前使用Momentum的代碼

AdaGrad

AdaGrad全稱為Adaptive Subgradient,其主要特點(diǎn)在于不斷累加每次訓(xùn)練中梯度的平方,公式如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

其中

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是一個極小的正數(shù),用來防止除0,而

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是矩陣的哈達(dá)瑪積運(yùn)算符,另外,本文中矩陣的平方或者兩矩陣相乘都是計算哈達(dá)瑪積,而不是計算矩陣乘法

從公式中可以看出,隨著算法不斷迭代,

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

會越來越大,整體的學(xué)習(xí)率會越來越小。所以,一般來說AdaGrad算法一開始是激勵收斂,到了后面就慢慢變成懲罰收斂,速度越來越慢

對于代碼實現(xiàn),首先將

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

展開得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

通常

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,所以在第一次訓(xùn)練時(2.2)式為:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

因為每次訓(xùn)練

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的值是不確定的,所以要防止處0,但是可以令

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,這樣就可以在(2.2)式中去掉

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代入(2.3)式,可以得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

可知

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

恒大于0,因此不必在計算

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

中額外加入

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,代碼如下:

class AdaGrad(object): def __init__(self, eps=1e-8, lr=1e-3): self.r = eps # r_0 = epsilon self.lr = lr def update(self, g: np.ndarray): r = r np.square(g) return -self.lr * g / np.sqrt(r)

RMSProp

RMSProp是AdaGrad的改進(jìn)算法,其公式和AdaGrad的區(qū)別只有

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的計算不同,先看公式

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

可以看出,與AdaGrad不同,RMSProp只會累積近期的梯度信息,對于“遙遠(yuǎn)的歷史”會以指數(shù)衰減的形式放棄。

并且AdaGrad算法雖然在凸函數(shù)(Convex Functions)上表現(xiàn)較好,但是當(dāng)目標(biāo)函數(shù)非凸時,算法梯度下降的軌跡所經(jīng)歷的結(jié)構(gòu)會復(fù)雜的多,早期梯度對當(dāng)前訓(xùn)練沒有太多意義,此時RMSProp往往表現(xiàn)更好

以下是將

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

展開后的公式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

與AdaGrad一樣,令

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,從而去掉計算

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

時的

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,實現(xiàn)代碼:

class RMSProp(object): def __init__(self, lr=1e-3, beta=0.999, eps=1e-8): self.r = eps self.lr = lr self.beta = beta def update(self, g: np.ndarray): r = r * self.beta (1-self.beta) * np.square(g) return -self.lr * g / np.sqrt(r)

AdaDelta

AdaDelta是與RMSProp相同時間對立發(fā)展出來的一個算法,在實現(xiàn)上可以看作是RMSProp的一個變種,先看公式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

可以看到該算法不需要設(shè)置學(xué)習(xí)率

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,這是該算法的一大優(yōu)勢。除了同樣以

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

來累積梯度的信息之外,該算法還多了一個

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

以指數(shù)衰減的形式來累積

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的信息

與前面相同,令:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

然后去掉(3.1)中的

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

這樣的話可以減少一些計算,代碼如下:

class AdaDelta(object): def __init__(self, beta=0.999, eps=1e-8): self.r = eps self.s = eps self.beta = beta def update(self, g: np.ndarray): g_square = (1-self.beta) * np.square(g) # (1-beta)*g^2 r = r * self.beta g_square frac = s / r res = -np.sqrt(frac) * g s = s * self.beta frac * g_squaretmp # 少一次乘法。。。 return res

關(guān)于以上幾個算法的對比:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

其中NAG是Nesterov Momentum

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

更多關(guān)于AdaDelta的信息,可以參考這篇文章:自適應(yīng)學(xué)習(xí)率調(diào)整:AdaDelta(https://www.cnblogs.com/neopenx/p/4768388.html)

Adam

Adam的名稱來自Adaptive Momentum,可以看作是Momentum與RMSProp的一個結(jié)合體,該算法通過計算梯度的一階矩估計和二階矩估計而為不同的參數(shù)設(shè)計獨(dú)立的自適應(yīng)性學(xué)習(xí)率,公式如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

(4.1)和(4.2)在Momentum和RMSProp中已經(jīng)介紹過了,而不直接使用

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

計算

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

卻先經(jīng)過(4.3)和(4.4)式是因為通常會設(shè)

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,所以此時梯度的一階矩估計和二階矩估是有偏的,需要進(jìn)行修正。

雖然沒辦法避免修正計算,但是還是可以省去一些計算過程,初始化時令:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

然后(4.5)式變?yōu)椋?/span>

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

因為

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,可知當(dāng)

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

足夠大時修正將不起作用(也不需要修正了):

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代碼如下:

class Adam(object): def __init__(self, lr=1e-3, alpha=0.9, beta=0.999, eps=1e-8): self.s = 0 self.r = eps self.lr = lr self.alpha = alpha self.beta = beta self.alpha_i = 1 self.beta_i = 1 def update(self, g: np.ndarray): self.s = self.s * self.alpha (1-self.alpha) * g self.r = self.r * self.beta (1-self.beta) * np.square(g) self.alpha_i *= self.alpha self.beta_i *= self.beta_i lr = -self.lr * (1-self.beta_i)**0.5 / (1-self.alpha_i) return lr * self.s / np.sqrt(self.r)

AdaMax

首先回顧RSMSProp中

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的展開式并且令

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

可以看到這相當(dāng)于是一個

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

范數(shù),也就是說

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

的各維度的增量是根據(jù)該維度上梯度的

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

范數(shù)的累積量進(jìn)行縮放的。如果用

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

范數(shù)替代就得到了Adam的不同變種,不過其中

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

范數(shù)對應(yīng)的變種算法簡單且穩(wěn)定

對于

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

范數(shù),第

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

輪訓(xùn)練時梯度的累積為:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

然后求無窮范數(shù):

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

由此再來遞推

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

需要注意,這個max比較的是梯度各個維度上的當(dāng)前值和歷史最大值,具體可以結(jié)合代碼來看,最后其公式總結(jié)如下:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

另外,因為

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

是累積的梯度各個分量的絕對值最大值,所以直接用作分母且不需要修正,代碼如下:

class AdaMax(object): def __init__(self, lr=1e-3, alpha=0.9, beta=0.999): self.s = 0 self.r = 0 self.lr = lr self.alpha = alpha self.alpha_i = 1 self.beta = beta def update(self, g: np.ndarray): self.s = self.s * self.alpha (1-self.alpha) * g self.r = np.maximum(self.r*self.beta, np.abs(g)) self.alpha_i *= self.alpha lr = -self.lr / (1-self.alpha_i) return lr * self.s / self.r

Nadam

Adam可以看作是Momentum與RMSProp的結(jié)合,既然Nesterov的表現(xiàn)較Momentum更優(yōu),那么自然也就可以把Nesterov Momentum與RMSProp組合到一起了,首先來看Nesterov的主要公式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

為了令其更加接近Momentum,將(5.1)和(5.2)修改為:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

然后列出Adam中Momentum的部分:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

將(5.5)和(5.6)式代入到(5.7)式中:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

將上式中標(biāo)紅部分進(jìn)行近似:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代入原式,得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

接著,按照(5.4)式的套路,將

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

替換成

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

整理一下公式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

同樣令

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

,消去(5.8)式種的

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代碼如下:

class Nadam(object): def __init__(self, lr=1e-3, alpha=0.9, beta=0.999, eps=1e-8): self.s = 0 self.r = eps self.lr = lr self.alpha = alpha self.beta = beta self.alpha_i = 1 self.beta_i = 1 def update(self, g: np.ndarray): self.s = self.s * self.alpha (1-self.alpha) * g self.r = self.r * self.beta (1-self.beta) * np.square(g) self.alpha_i *= self.alpha self.beta_i *= self.beta_i lr = -self.lr * (1-self.beta_i)**0.5 / (1-self.alpha_i) return lr * (self.s * self.alpha (1-self.alpha) * g) / np.sqrt(self.r)

NadaMax

按照同樣的思路,可以將Nesterov與AdaMax結(jié)合變成NadaMax,回顧以下(5.8)式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

然后是AdaMax的二階矩估計部分:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

用(6.2)式替換掉(6.1)式中標(biāo)紅部分,得到:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

最后,整理公式:

推薦20個開源的前端低代碼項目(前端 低代碼開發(fā))_1

代碼實現(xiàn):

class NadaMax(object): def __init__(self, lr=1e-3, alpha=0.9, beta=0.999): self.s = 0 self.r = 0 self.lr = lr self.alpha = alpha self.alpha_i = 1 self.beta = beta def update(self, g: np.ndarray): self.s = self.s * self.alpha (1-self.alpha) * g self.r = np.maximum(self.r*self.beta, np.abs(g)) self.alpha_i *= self.alpha lr = -self.lr / (1-self.alpha_i) return lr * (self.s * self.alpha (1-self.alpha) * g) / self.r參考資料:

[1]: 《機(jī)器學(xué)習(xí)算法背后的理論與優(yōu)化》 ISBN 978-7-302-51718-4

[2]: Adam: A Method for Stochastic Optimization(https://arxiv.org/abs/1412.6980)

[3]: Incorporating Nesterov Momentum into Adam(https://openreview.net/forum?id=OM0jvwB8jIp57ZJjtNEZ?eId=OM0jvwB8jIp57ZJjtNEZ)

[4]: An overview of gradient descent optimization algorithms(https://ruder.io/optimizing-gradient-descent/index.html)

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
公眾號
公眾號
在線咨詢
分享本頁
返回頂部