日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

梯度下降优化算法综述与PyTorch实现源码剖析

發布時間:2023/11/28 生活经验 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 梯度下降优化算法综述与PyTorch实现源码剖析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

現代的機器學習系統均利用大量的數據,利用梯度下降算法或者相關的變體進行訓練。傳統上,最早出現的優化算法是SGD,之后又陸續出現了AdaGrad、RMSprop、ADAM等變體,那么這些算法之間又有哪些區別和聯系呢?本文試圖對比的介紹目前常用的基于一階梯度的優化算法,并給出它們的(PyTorch)實現。

SGD

算法描述

隨機梯度下降法(Stochastic Gradient Descent,SGD)是對傳統的梯度下降算法(Gradient Descent,GD)進行的一種改進。在應用GD時,我們需要對整個訓練集進行一次反向傳播計算梯度后再進行參數更新,對系統的計算能力和內存的需求較高,而SGD在計算梯度更新參數時剛好相反,每次只使用整個訓練集中的一個樣本,因此具有更快地計算速度和較少的內存占用。同時,因為每次只使用一個樣本更新參數,使得參數更新更加頻繁,更新的參數間具有更高的方差,損失函數會朝不同的方向有較大的波動,這有助于發現新的極值點,避免優化器陷入一個局部極值點。但是也由于這種頻繁的震蕩,出現了一種折中的方法,即小批量(mini-batch)梯度下降法,每次只取訓練集中一個batch的樣本進行梯度的計算與參數更新,一般batch的大小為4的倍數。原始SGD的更新法則如下:θ=θ?η??θJ(θ)(1)(1)θ=θ?η??θJ(θ)

傳統SGD面臨的問題

傳統的SGD在訓練的過程中主要存在以下幾個問題:

  1. 很難選擇一個合適的學習速率,太小的學習速率導致算法收斂很慢,而太大的學習速率會導致在極值點附近震蕩甚至錯過,因此需要經過多次嘗試。
  2. Learning rate schedules往往實現定義一個學習速率衰減表,比如每過多少step對學習速率進行decay,但是這些策略往往沒法按照某個數據集的具體參數特性進行定制。
  3. 對于比較稀疏的數據,不同的特征出現的頻率差別很大,如果所有的參數均使用一個相同的學習速率進行更新,這樣做是不合理的。對于出現頻率的特征,我們應該使用一個較大的學習速率。
  4. 深度神經網絡之所以難以訓練,并不是因為容易陷入局部最小值,而是在學習的過程中陷入到鞍點(saddle point),此時往各個方向的梯度幾乎均為0。如果以二維平面為例,y=x3y=x3中x=0處即為一個鞍點。對于傳統的SGD而言,一旦優化的過程中進入鞍點,就很難再離開這一位置。

Momentum

針對以上提到的第四點問題,可以通過增加動量(Momentum)的SGD進行緩解,加速優化函數的收斂。vt=γvt?1?η??θJ(θ)θ=θ+vt(2)(2)vt=γvt?1?η??θJ(θ)θ=θ+vt所謂的添加動量項,即在一定程度上保留上一次梯度更新的方向,γ,ηγ,η分別用來控制上次梯度方向和本次梯度方向對最終更新方向的貢獻程度,其中γ∈(0,1]γ∈(0,1]在開始階段常常被設置為0.5,當學習趨向穩定后,逐漸增加到0.9甚至更高。 可以把待優化的目標函數想象成一座山,在山頂將一個小球推下,小球在山坡上滾動的位置即系統的loss值,在往下滾動的過程中小球的動量不斷增加,由于動量的存在,當小球滾動到山坡中較為平坦的地帶時,小球將更容易越過這片地帶繼續往下滾而不是陷在這一區域停滯不前,并最終到達山谷。

圖1 左:原始SGD 右:SGD+Momentum

?

Nesterov Accelerated Gradient

Its better to correct a mistake after you have made it!

目前我們有了一個帶有動量的小球,但是這個小球在滾動的過程中總是隨著山勢的變化滾動,因此其行進的路徑極不穩定。因此我們希望有一個更加“聰明”的小球,它不但擁有動量,而且能夠知道自己將要去哪,這樣當前面出現上坡小球能夠進行減速。比如說,當接近坡底時,小球應該提前減速避免錯過坡底。vt=γvt?1?η?θJ(θ+γvt?1)θ=θ+vt(3)(3)vt=γvt?1?η?θJ(θ+γvt?1)θ=θ+vt具體的實現也非常的直接,就是將傳統的Momentum方法對θθ計算梯度變為對θ+γvt?1θ+γvt?1求梯度,這一項可以看做對小球下一步將會往哪運動的一個粗略估計。也就是說,我們的小球有了一定的對未來的“預測”能力。就像本節開頭說的,如果我們知道了小球之后會犯什么錯誤,那么是否更容易更正錯誤呢?下圖上半部分是傳統Momentum求下一次梯度更新方向,下半部分則是使用NAG求下一次更新方向的方法。

圖2 Momentum與NAG更新的區別

當然,在具體實現時,直接計算θ+γvt?1θ+γvt?1項的梯度比較麻煩,希望更新參數時計算能和傳統的SGD或者Momentum方法類似,因此需要對上式的計算步驟做一些改進。

?

v_prev = v  #備份vt-1項
v = mu*v - lr * g  #這一步和傳統的Momentum計算一樣
p += -mu*v_prev + (1+mu)*v  #更新時真實的p應該為p-mu*v_prev,更新后為p-mu*v_prev+v,但是為了方便計算加上上次動量項的梯度,這里的p直接保存為p-mu*v_prev+v+mu*v,也就是p(小球)的“未來位置”。

PyTorch實現

Momentum/NAG的實現和原始論文中的實現有些許的不用,具體的,在PyTorch實現中按照如下的公式更新梯度,其中ηη為learning rate,gg為θθ的梯度。目前尚不清楚為什么要做出這樣的改變?vt=γvt?1+gθ=θ?η?vt(4)(4)vt=γvt?1+gθ=θ?η?vt具體代碼如下: > class torch.optim.SGD(params, lr=required, momentum=0, dampening=0, weight_decay=0, nesterov=False)

def step(self, closure=None):"""Performs a single optimization step.Arguments:closure (callable, optional): A closure that reevaluates the modeland returns the loss."""loss = Noneif closure is not None:loss = closure()for group in self.param_groups:weight_decay = group['weight_decay']momentum = group['momentum']dampening = group['dampening']nesterov = group['nesterov']for p in group['params']:if p.grad is None:continued_p = p.grad.dataif weight_decay != 0:d_p.add_(weight_decay, p.data)if momentum != 0:   #動量項添加param_state = self.state[p]if 'momentum_buffer' not in param_state:buf = param_state['momentum_buffer'] = d_p.clone()else:buf = param_state['momentum_buffer']buf.mul_(momentum).add_(1 - dampening, d_p)if nesterov:    #如果使用NAG,則為t+1步先保存可能到達的“位置”d_p = d_p.add(momentum, buf)else:d_p = bufp.data.add_(-group['lr'], d_p)return loss

AdaGrad

算法描述

AdaGrad為的是解決傳統的SGD對所有參數使用相同的學習速率的問題(即1.2節中提到的第三點問題)。它使用參數的歷史梯度累計和去歸一化該參數對應的學習速率。具體的,對于經常出現的參數,那么其梯度累積和較大,歸一化的學習速率就較小。而對于不常見的參數,往往包含更多關于特征的信息,累積和較小,歸一化后的學習速率較大,也即是學習算法應該更加關注這些罕見的特征的出現。Gt,ii=Gt?1,ii+g2t,iθt+1,i=θt,i?η√Gt,ii+??gt,i(5)(5)Gt,ii=Gt?1,ii+gt,i2θt+1,i=θt,i?ηGt,ii+??gt,i當然,通過觀察式(5),我們也發現AdaGrad在學習速率的調整上存在過于激進的問題,隨著時間的累積,Gt,iiGt,ii這一項會越來越大,導致歸一化的學習速率越來越小,這有可能導致優化函數在收斂之前就停止更新。

PyTorch實現

class torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0)

def step(self, closure=None):"""Performs a single optimization step.Arguments:closure (callable, optional): A closure that reevaluates the modeland returns the loss."""loss = Noneif closure is not None:loss = closure()for group in self.param_groups:for p in group['params']:if p.grad is None:continuegrad = p.grad.datastate = self.state[p]state['step'] += 1if group['weight_decay'] != 0:if p.grad.data.is_sparse:raise RuntimeError("weight_decay option is not compatible with sparse gradients ")grad = grad.add(group['weight_decay'], p.data)clr = group['lr'] / (1 + (state['step'] - 1) * group['lr_decay'])if p.grad.data.is_sparse:grad = grad.coalesce()  # the update is non-linear so indices must be uniquegrad_indices = grad._indices()grad_values = grad._values()size = torch.Size([x for x in grad.size()])def make_sparse(values):constructor = type(p.grad.data)if grad_indices.dim() == 0 or values.dim() == 0:return constructor()return constructor(grad_indices, values, size)state['sum'].add_(make_sparse(grad_values.pow(2)))std = state['sum']._sparse_mask(grad)std_values = std._values().sqrt_().add_(1e-10)p.data.add_(-clr, make_sparse(grad_values / std_values))else:state['sum'].addcmul_(1, grad, grad)    #更新核心部分std = state['sum'].sqrt().add_(1e-10)p.data.addcdiv_(-clr, grad, std)return loss

Adadelta

為了避免AdaGrad存在的學習過早停止的問題,Adadelta不再保存過去所有時刻的梯度和,而是采用decaying average的方法平滑過去的梯度值和參數值。

算法描述

圖3 Adadelta偽代碼描述

其中E[g2]tE[g2]t存儲的是歷史梯度平方的平滑值,此外,這里還需要對歷史的參數值的平方進行decaying average,也就是E[Δx2]t=ρE[Δx2]t?1+(1?ρ)Δx2tE[Δx2]t=ρE[Δx2]t?1+(1?ρ)Δxt2,然后分別進行開方處理得到RMS[Δx]t=√E[Δx2]t+?RMS[Δx]t=E[Δx2]t+?和RMS[g]t=√E[g2]t+?RMS[g]t=E[g2]t+?。最后按照xt

總結

以上是生活随笔為你收集整理的梯度下降优化算法综述与PyTorch实现源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。