# Deep Learning Presentation 0408 -2
Continue from volumn 1
Here -> [Click me](https://hackmd.io/@tOcOYk2uSKyDq-IaFI8RNQ/rklQ15keR)
*Time: 0407*
*Editor: Wang Xuan-Wei*
## 7. Learning rate
:::info
這個章節主要探討如何尋找learning rate,以及提供許多optimizer的選擇
:::
找learning rate是個困難的任務,通常都是trial-and-error的方式去試。最多可以確定的一點是,learning rate 會落在$[10^{-6},1.0]$區間裡。
### Finding LR
先從建立簡單的**LR Range Test**開始。這是一個開始。他嘗試多個learning rate並記錄相對應的loss,一次在小範圍的mini-batch評估loss,然後更改learning rate再轉到下一個mini-batch計算loss。可以在同一個training loop中完成,是省計算的一種方法。
有幾個必須設定的條件:
1. test boundaries: *(start_lr, end_lr)*
2. #'s of iterations: *num_iter*
3. increment way: linearly or exponentially
比方說創造了以下function `make_lr_fn(start_lr, end_lr, num_iter, step_mode='exp')`

可以看見我們test boundaries 是 (0.01,0.1), num. of iteration 是 10次,並且採用exponentially的方式找下個learning rate。上圖的output是應用於從0到10的一系列迭代次數,而我們再將全部數值乘上*start_lr=0.01*就可以得到以下所有的learning rate:

完成learning rate的架構以後,就要安裝這個function到optimizer上。我們可以叫一個"Scheduler"安置在我們的optimizer上面,使其遵循類似上面的值序列即可。像下圖一樣:

*LambdaLR Scheduler* 接受一個optimizer和一個custom function作為參數,並相應地修改該optimizer的learning rate。然而,要實現這一點,我們需要調用scheduler的 `step()` 方法,但是只能在調用optimizer自己的 `step()` 方法之後才能進行:

以上是基礎的練習,接下來建立實際測試。
我們創建屬於class `setattr(StepByStep, 'lr_range_test', lr_range_test)`中的 `lr_range_test(self, data_loader, end_lr, num_iter=100, step_mode='exp', alpha=0.05, ax=None)`函數完成以下任務:
* 存儲model跟optimizer的初始狀態,以便在最後可以恢復它們。
* 創建custom function和相應的scheduler,就像上面的片段中一樣。
* (重新)實現對mini-batch的training loop,以便我們可以在每一步記錄learning rate和loss。
* 恢復model和optimizer的狀態。
並且,因為對於不同的mini-batch評估loss可能導致loss值波動大,我們用**指數加權移動平均EWMA**來平滑loss曲線。
以上省略`lr_range_test()`函數的建構細節,直接看測試結果:


可以觀察到有U型的結構,表示在$0.01$附近可以找到好的learning rate。這意味著我們可以使用更高的學習率,比如$0.005$,來訓練我們的模型。但這也意味著我們需要重新創建優化器並在 sbs_new 中進行更新。

>在textbook中的**LRFinder**一欄中我們建立了完整的測試流程,並得到以下結果:

由此得知,$10^{-2}$是個好的start point。
**Note**: 即使一開始就選中良好的learning rate,也可能因為loss surface混亂導致訓練到後期需要重新整learning rate,所以無論如何這些嘗試的步驟都是必要的。
### Adaptive Learning Rate
這就是 Adam optimizer實際上做的事情 - 它從提供的learning rate作為參數開始,但隨著訓練的進行,調整learning rate,對模型中的每個參數進行不同方式的微調。與其說是調整learning rate,Adam事實上是在調整gradient。但是,由於參數更新是由兩個項的乘法給出的,即learning rate和gradient,這是一個毫無區別的區分。
Adam 綜合了另外兩種optimizers的特徵:
1. **SGD (with momentum)**: 使用moving average of gradients
2. **RMSProp**: 使用moving average of squared gradients 去scale the gradients
> **Moving Average (MA)**
要計算一個給定特徵 $x$ 在一定數量的期間內的移動平均,我們只需要將觀察到的值平均起來,也就是從觀察到的initial value開始,往上加到current value,總共 $periods-1$ 個步驟:$$MA_t(periods, x)=\frac{1}{periods}(x_t+x_{t-1}+...+x_{t-periods+1})$$
並且我們計算MA的平均age:$$average\ age_{MA}=\frac{1+2+...+periods}{periods}=\frac{periods+1}{2}$$
> **Exponential Weighted Moving Average(EWMA)**
EWMA比傳統MA更實用,因為它只有兩個輸入:previous value的EWMA和正在進行平均的變量的current value。有以下兩種表達形式:

我們利用上式進行迭代之後可以得到:$$EWMA_t(\alpha,x)=\sum^{T-1}_{lag=0}(1-\alpha)^{lag}x_{t-lag}$$
前項$(1-\alpha)^{lag}$即為"weight"。換句話說,若$\alpha$上升,則weight呈指數下降,意味著old value將趨於無影響。雖然EWMA仍考慮了每個值。

從上圖可以觀察到,雖然兩者的weight大小不太一樣,但平均值好像差不多。而我們進一步計算會得到:$$average\ age_{EWMA}=\alpha\sum^{T-1}_{lag=0}(1-\alpha)^{lag}(lag+1)\cong\frac{1}{\alpha}$$
所以可以得到需要計算的期數及相對應的alpha值,也就是:$$periods=\frac{2}{\alpha}-1$$
比方說,要計算$19$期的EWMA,就設定$\alpha=0.1$。
但還有一個地方要注意,就是在最初幾步當中,「平均」會離得很遠/有偏差,所以我們創造了修正過後的EWMA:$$Bias\ Corrected\ EWMA_t(x,\beta)=\frac{1}{1-\beta^t}EWMA_t(x,\beta)$$這邊的$\beta=1-\alpha$。
最後再來比較一次MA跟校正前、校正後的EWMA:

Temperature是我們測試的溫度原始資料。正如預期的那樣,沒有校正的EWMA(紅色虛線)在開始時偏離得很遠,而MA(黑色虛線)更接近實際值。儘管如此,經過校正的EWMA從一開始(紅色實線)就很好地跟蹤實際值。確實,在19天後,這兩個指數加權移動平均幾乎無法區分。
> **EWMA meets Gradients**
在Adam optimizer 中,我們用以下式子計算adapted gradients:

其中:
* $\beta_1=0.9$, 也就是$period=19$, 短期的用作 smoothing the gradients.
* $\beta_2=0.999$, 也就是$period=1999$, 長期的用作 scaling the gradients.

這邊的$\eta$是learning rate。(還沒討論到)
> **Adam**
在PyTorch中的Adam optimizer會有以下幾個arguments:
* *params* : model'sparameters
* *lr=$10^{-3}$* : learning rate
* *betas=(beta_1, beta_2)* : tuple containing beta1 and beta2 for EWMA
* *eps=$10^{-8}$* : the epsilon value for the denominator
* *weight_decay* : L2 penalty
* *amsgrad* : if the AMSGrad varient should be used
簡單來說,*weight_decay*機制是對於較大的權重進行懲罰,也就是從上個權重減去gradients x learning rate,以防止overfitting發生。而amsgrad,使得optimizer與同名varient相容。簡而言之,它修改了用於計算適應梯度的公式,放棄了偏差校正,而是使用平方梯度的 EWMA 的峰值。
> **Visualizing Adapted Gradients**
這邊我們省略了一些coding的細節,得到了以下的比較結果:

在左圖中,我們可以看到,經過偏差校正的梯度的 EWMA(紅色)正在平滑梯度。在中間,經過偏差校正的平方梯度的 EWMA 被用來對平滑的梯度進行縮放。在右邊,這兩個 EWMA 被結合在一起來計算適應梯度。
終於可以比較SGD跟Adam了:

我們建了兩個*StepByStep*的instances,分別用SGD跟Adam做optimizer,train 10個epoch,捕捉(capture)bias跟weights畫出上面的圖。
* 在左圖中,我們可以看到典型的well=behaved(and slow)的SGD所採取的路徑。可以看到它由於使用mini-batches引入的noise而有些抖動(wiggling)。
* 在右圖中,我們可以看到使用EWMA的效果:
* 一方面,它更smooth且移動速度更快
* 另一方面,它會超過目標並不斷改變方向,以接近目標。你可以說它在適應loss surface。

然後我們比較兩者的training loss跟validation loss。這邊loss是在每個 epoch 結束時通過average mini-baches的 loss來計算的。
* 在左圖中,即使 SGD 有些抖動,我們也可以看到每個 epoch 的loss都比前一個更低。
* 在右圖中,training loss的增加表示有些overfitting的現象。但顯然,Adam 實現了較低的loss,因為它更接近optimized value(前一個圖中的紅點)。
### Stochastic Gradient Decent (SGD)
這邊我們來探討帶有momentum的SGD。PyTorch中的SGD會帶有以下的arusments:
* *params* : model's parameters
* *lr* : learning rate
* *weight_decay* : L2 penalty
* *momentum* : momentum factor, SGD's own bet arguments, is the topic of the next section
* *dampening* : dampening factor for momentum
* *nesterov* : enables Nesterov momentum, which is a smarter version of the regular momentum, and also has its own section.
> **Momentum**
看起來有點像EWMA說的momentum但有點不一樣:

它運行的是"折扣"gradients的cumulative sum。也就是可以寫成下列式子:

也就是需要完整的gradients歷史記錄,而前一個公式只取決於gradient的current value和momentum的latest value。
而**dampening value**是一種用來“減弱(dampen)”最新gradient的影響的方式。最新gradient的貢獻被dampening factor減小,而不是完全添加。因此,如果dampening factor是0.3,則最新gradient的70%被添加到momentum中。其公式如下所示:

**Note**: *如果dampening factor等於 beta,那其實就是EWMA!*
即使舊gradients慢慢消失,對總和的貢獻越來越少,但非常新的gradient幾乎是按照它們的實際值進行考慮。這意味著,對於一系列全部為正(或全部為負)的gradients,它們的總和,也就是momentum,將以**非常快的速度增加**(絕對值)。大的動量被轉換為大的更新,因為動量取代了梯度在參數更新中的作用:

由下圖可以清晰地觀察到:

像 Adam optimizer一樣,SGD with momentum **移動更快**且會**超過目標**。但它似乎會被這種速度帶走,以至於它超過目標並不得不從不同的方向接近它,可以想像是一顆球沿著山坡往下滑然後在谷底震盪這樣。
Adam可能是個好方法,因為他可以很快接近optimized value(target),但也可能忽略掉其他target,比方說在一些有許多minima的問題裡他就不是那麼好的方法。所以各有優劣,取決於欲解決的問題本身。但Adam跟SGD with momentum都是蠻常用的方法。
> **Nesterov**
***Nesterov accelerated gradient(NAG)*** 是SGD with momentum的變型。

看起來沒甚麼變化,但注意的是計算 *mo_t+1* 的時候會用到的 *grad_t+1* 必須用估計的!這就是NAG在做的事情。


然後我們展開 *param_t*,得到

> **Flavors of SGD**
來比較看看三種SGD方法!在計算公式方面我們有:

SGD with Nesterov可以說是結合了以上兩者。
再來看看畫圖比較的差別!

中間可以看見有些震盪,就像是之前模擬出來的:當它超過目標時,它必須改變方向,並且通過重複這樣做,產生了這些震蕩。而在Nesterov這裡震盪似乎又更減小了。
並且,雖然underlying gradients都是相同的,但因為計算上迭代更新的方式不同,以至於上圖黑色部分三者會有些差別。

例如,請看左下角黑線的第三個點:它在每個圖中的位置相當不同,因此對應的梯度也是不同的。
新圖就在右邊,dampening of oscillations非常明顯,但Nesterov的momentum仍然超過了target,必須稍微回退一下,以從相反的方向接近它。提醒一下:這是所有最簡單
的loss surface之一!

右邊的圖也很直觀,顯示Nesterov的動量迅速找到了一個更低的損失並緩慢地接近最佳值。
中間的圖有點更有趣:即使常規的momentum在loss surface上產生了一個波動很大的路徑(每個黑點對應一個mini-batches),它的loss trajectory波動比Adam的小。然而這只是這個簡單的線性回歸問題(即碗狀的loss surface)的一個特徵,不能被視為典型行為的代表。
### Learning Rate Schedulers
:::info
我們接下來探討Learning rate 的 schedulars。一共有三種:
1. Epoch Schedulars
2. Validation Schedulars
3. Mini-batch Schedulars
:::
可以在訓練過程中安排learning rate的變化,而不是調整gradients。假設你想要每T個epoch將learning rate降低一個數量級(即乘以0.1),這樣訓練在開始時更快,一段時間後減慢以嘗試避免收斂問題。因此,schedular的一個parameter是optimizer本身。對於optimizer設置的learning rate將是schadular的initial learning rate。
作為一個示例,讓我們採用最簡單的schadular之一:`StepLR`,它只是將learning rate乘以因子 *gamma* 每 *step_size* 個 *epochs*。

**Note**: 每次呼叫一定要先呼叫**schadular**的`step()` method,再呼叫**optimizer**的`step()` method。
結果會像下圖一樣,每2個step_size後就會更新learning rate。

不一定所有的schadulars都是縮小learning rate,也有循環learning rate的,各式各樣的schadulars設計都有。而我們將schadular分成三種:
1. 每 T 個 epoches 更新一次的 (T=1也可以)
2. 當 validation loss 好像 stuck 的時候更新一次
3. 每個 mini-batch 更新一次
> **Epoch Schadular**
這樣的schadular會在每一個epoch結束時呼叫他們的`step()`。常見的有下列幾種:
* `StepLR` : 每個 *step_size*都把learning rate 乘上一個gamma factor
* `MultiStepLR` : 它在list of milestones中指示的epochs將學習率乘以因子 gamma。
* `ExponentialLR` : 它每個epoch將學習率乘以因子 gamma
* `LambdaLR` : 它接受customized function,該函數應以epoch為參數並返回相應的乘法factor(相對於initial learning rate)。
* `ConsineAnnealingLR` : 它使用一種稱為cosine annealing的技術來更新學習率,但我們這裡不會深入探討細節。
下圖是`LambdaLR`的樣子:

> **Validation Loss Schadulars**
`ReduceLROnPlateau` schadular有自己的循環時間,也就是對loss的tolerate space,這個空間滿了才會更新learning rate(乘上一個gamma值)。比方說我們可以設定epoch=4,這邊的epoch數量就是tolerate space(patience)的意思,也就是如果4個epoches之間loss都沒有改變的話,就會更新learning rate。

> **Mini-Batch Schadulars**
這樣的schadulars都會在每個mini-batch的最後呼叫自己的`step()` method。常見的有下面幾種:
* `CyclicLR` : 有三種mode:
* `mode=traingular` : 在*base_lr*跟*max_lr*(利用LR Range Test找到的)之間循環,使用*step_size_up*更新區間裡的learning rate,並使用*step_size_down*返回。
* `mode=traingular2` : 將在每個循環後將振幅減半。
* `mode=exp_range` : 將使用gamma作為底數,循環的數量作為指數,指數函數地縮小振幅。
* `OneCycleLR` : 這使用一種稱為退火的方法來更新learning rate,從其初始值提高到一個定義的最大學習率(max_lr),然後在總更新數(total_steps)的過程中降至一個更低的學習率,從而完成一個循環。
* `CosineAnnealingWarmRestarts`
我們來看看`CyclicLR` 的三種模式各有什麼差別:

**Note**: 在實戰中,根據Leslie N. Smith的論文,一個cyclic應包含2到10個epochs,因此您需要弄清楚您的trainig set包含多少個mini-batches(這就是數據加載器的長度),並將其乘以循環中所需的epochs數來獲得一個循環中的總步數。
> **Schadular Paths**
要仔細比較schadular之前,必須先透過LR Range Test找 *max_lr*:

結果表明學習率在0.01到0.1之間(對應於曲線下降部分的中心)是合適的。我們確切知道學習率0.1是有效的。例如,更保守的選擇值可能是0.025,因為它是曲線下降部分的中點。
接著我們選取要比較的schadulars:
* `step_scheduler = StepLR(optimizer, step_size=20, gamma=0.5)`
* `cyclic_scheduler = CyclicLR(optimizer, base_lr=0.025, max_lr=0.1, step_size_up=10, mode='triangular2')`
得到以下的圖表:

**Note**: 使用schadular的一般想法是允許optimizer在探索loss surface(高學習率階段)和尋找minima(低學習率階段)之間進行交替。

由上述結果觀察到,在同一row中很難區分曲線之間的差異,除了Nesterov的momentum和cycle schadular的組合,它產生了training loss的smooth reduction。
### Adaptive vs Cycling
都是很好的方法,可以針對不同題目做調度使用。
## 8. Conclusion
:::danger
本章節學到的知識:數據準備、模型配置(configulation)跟訓練。
:::
```mermaid
graph LR;
Dataset-preparation-->Standardization;
Standardization-->Dropout_Layer;
Dropout_Layer-->Training;
Training-->Learning_rate-->Finding_methods;
Training-->Optimizer-->Capturing_gradients/parameters;
Training-->Schadulars-->Updating_LR;
```
---
## References
1. [<機器學習ML NOTE> SGD, Momentum, AdaGrad, Adam Optimizer](https://medium.com/%E9%9B%9E%E9%9B%9E%E8%88%87%E5%85%94%E5%85%94%E7%9A%84%E5%B7%A5%E7%A8%8B%E4%B8%96%E7%95%8C/%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92ml-note-sgd-momentum-adagrad-adam-optimizer-f20568c968db)
2. [使用 TensorFlow 了解 overfitting 與 underfitting](https://medium.com/%E6%89%8B%E5%AF%AB%E7%AD%86%E8%A8%98/%E4%BD%BF%E7%94%A8-tensorflow-%E4%BA%86%E8%A7%A3%E9%81%8E%E6%93%AC%E5%90%88%E8%88%87%E6%AC%A0%E6%93%AC%E5%90%88-a75a26cc87e0)
3. [【深度學習】AI影像處理中最重要的基礎-CNN](https://jason-chen-1992.weebly.com/home/ai-cnn)
4. [What are the best practices for standardizing image data?](https://www.linkedin.com/advice/0/what-best-practices-standardizing-image-data-qanye)
5. [Standardization v/s Normalization](https://medium.com/@meritshot/standardization-v-s-normalization-6f93225fbd84)
6. Daniel Voigt Godoy, *Deep Learning With PyTorch Step-By-Step*, ver. 1.1.1.