---
# System prepended metadata

title: Deep Learning Presentation 0408 -2

---

# 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')`

![image](https://hackmd.io/_uploads/SkxGaBGlg0.png)

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

完成learning rate的架構以後，就要安裝這個function到optimizer上。我們可以叫一個"Scheduler"安置在我們的optimizer上面，使其遵循類似上面的值序列即可。像下圖一樣：
![image](https://hackmd.io/_uploads/BJP8dGggR.png)

*LambdaLR Scheduler* 接受一個optimizer和一個custom function作為參數，並相應地修改該optimizer的learning rate。然而，要實現這一點，我們需要調用scheduler的 `step()` 方法，但是只能在調用optimizer自己的 `step()` 方法之後才能進行：
![image](https://hackmd.io/_uploads/S1ZZFGxe0.png)

以上是基礎的練習，接下來建立實際測試。

我們創建屬於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()`函數的建構細節，直接看測試結果：
![image](https://hackmd.io/_uploads/H1keiGgg0.png)
![image](https://hackmd.io/_uploads/Syw-ofleR.png)

可以觀察到有U型的結構，表示在$0.01$附近可以找到好的learning rate。這意味著我們可以使用更高的學習率，比如$0.005$，來訓練我們的模型。但這也意味著我們需要重新創建優化器並在 sbs_new 中進行更新。
![image](https://hackmd.io/_uploads/B1bG2MlxR.png)

>在textbook中的**LRFinder**一欄中我們建立了完整的測試流程，並得到以下結果：
![image](https://hackmd.io/_uploads/ByW9TzxgR.png)
由此得知，$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。有以下兩種表達形式：

![image](https://hackmd.io/_uploads/BJy-z7gx0.png)

我們利用上式進行迭代之後可以得到：$$EWMA_t(\alpha,x)=\sum^{T-1}_{lag=0}(1-\alpha)^{lag}x_{t-lag}$$

前項$(1-\alpha)^{lag}$即為"weight"。換句話說，若$\alpha$上升，則weight呈指數下降，意味著old value將趨於無影響。雖然EWMA仍考慮了每個值。

![image](https://hackmd.io/_uploads/BkwQmmlgC.png)

從上圖可以觀察到，雖然兩者的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：
![image](https://hackmd.io/_uploads/B1XpLQel0.png)

Temperature是我們測試的溫度原始資料。正如預期的那樣，沒有校正的EWMA（紅色虛線）在開始時偏離得很遠，而MA（黑色虛線）更接近實際值。儘管如此，經過校正的EWMA從一開始(紅色實線)就很好地跟蹤實際值。確實，在19天後，這兩個指數加權移動平均幾乎無法區分。

> **EWMA meets Gradients**

在Adam optimizer 中，我們用以下式子計算adapted gradients：
![image](https://hackmd.io/_uploads/SkkDdQeeC.png)
其中：
* $\beta_1=0.9$, 也就是$period=19$, 短期的用作 smoothing the gradients.
* $\beta_2=0.999$, 也就是$period=1999$, 長期的用作 scaling the gradients.

![image](https://hackmd.io/_uploads/ryLyt7xlA.png)

這邊的$\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的細節，得到了以下的比較結果：
![image](https://hackmd.io/_uploads/SydE3QglC.png)
在左圖中，我們可以看到，經過偏差校正的梯度的 EWMA（紅色）正在平滑梯度。在中間，經過偏差校正的平方梯度的 EWMA 被用來對平滑的梯度進行縮放。在右邊，這兩個 EWMA 被結合在一起來計算適應梯度。

終於可以比較SGD跟Adam了：
![image](https://hackmd.io/_uploads/HkyRlVexR.png)
我們建了兩個*StepByStep*的instances，分別用SGD跟Adam做optimizer，train 10個epoch，捕捉(capture)bias跟weights畫出上面的圖。

* 在左圖中，我們可以看到典型的well=behaved（and slow）的SGD所採取的路徑。可以看到它由於使用mini-batches引入的noise而有些抖動(wiggling)。
* 在右圖中，我們可以看到使用EWMA的效果：
    * 一方面，它更smooth且移動速度更快
    * 另一方面，它會超過目標並不斷改變方向，以接近目標。你可以說它在適應loss surface。

![image](https://hackmd.io/_uploads/BJUzfExeC.png)
然後我們比較兩者的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但有點不一樣：
![image](https://hackmd.io/_uploads/SyA0V4glA.png)
它運行的是"折扣"gradients的cumulative sum。也就是可以寫成下列式子：
![image](https://hackmd.io/_uploads/S1BIS4lxA.png)
也就是需要完整的gradients歷史記錄，而前一個公式只取決於gradient的current value和momentum的latest value。

而**dampening value**是一種用來“減弱(dampen)”最新gradient的影響的方式。最新gradient的貢獻被dampening factor減小，而不是完全添加。因此，如果dampening factor是0.3，則最新gradient的70％被添加到momentum中。其公式如下所示：
![image](https://hackmd.io/_uploads/BkpH8ExgR.png)

**Note**: *如果dampening factor等於 beta，那其實就是EWMA！*

即使舊gradients慢慢消失，對總和的貢獻越來越少，但非常新的gradient幾乎是按照它們的實際值進行考慮。這意味著，對於一系列全部為正（或全部為負）的gradients，它們的總和，也就是momentum，將以**非常快的速度增加**（絕對值）。大的動量被轉換為大的更新，因為動量取代了梯度在參數更新中的作用：
![image](https://hackmd.io/_uploads/BJvGDVeeA.png)
由下圖可以清晰地觀察到：
![image](https://hackmd.io/_uploads/rJLNDVllR.png)

像 Adam optimizer一樣，SGD with momentum **移動更快**且會**超過目標**。但它似乎會被這種速度帶走，以至於它超過目標並不得不從不同的方向接近它，可以想像是一顆球沿著山坡往下滑然後在谷底震盪這樣。

Adam可能是個好方法，因為他可以很快接近optimized value(target)，但也可能忽略掉其他target，比方說在一些有許多minima的問題裡他就不是那麼好的方法。所以各有優劣，取決於欲解決的問題本身。但Adam跟SGD with momentum都是蠻常用的方法。

> **Nesterov**

***Nesterov accelerated gradient(NAG)*** 是SGD with momentum的變型。
![image](https://hackmd.io/_uploads/B1c0OExxA.png)
看起來沒甚麼變化，但注意的是計算 *mo_t+1* 的時候會用到的 *grad_t+1* 必須用估計的！這就是NAG在做的事情。

![image](https://hackmd.io/_uploads/HyNDt4eeR.png)
![image](https://hackmd.io/_uploads/BJiOF4le0.png)

然後我們展開 *param_t*，得到
![image](https://hackmd.io/_uploads/rkEsYEeeR.png)

> **Flavors of SGD**

來比較看看三種SGD方法！在計算公式方面我們有：
![image](https://hackmd.io/_uploads/ByBeqNexC.png)
SGD with Nesterov可以說是結合了以上兩者。

再來看看畫圖比較的差別！
![image](https://hackmd.io/_uploads/SkxUcVeg0.png)

中間可以看見有些震盪，就像是之前模擬出來的：當它超過目標時，它必須改變方向，並且通過重複這樣做，產生了這些震蕩。而在Nesterov這裡震盪似乎又更減小了。

並且，雖然underlying gradients都是相同的，但因為計算上迭代更新的方式不同，以至於上圖黑色部分三者會有些差別。

![image](https://hackmd.io/_uploads/H1WviExgA.png)
例如，請看左下角黑線的第三個點：它在每個圖中的位置相當不同，因此對應的梯度也是不同的。

新圖就在右邊，dampening of oscillations非常明顯，但Nesterov的momentum仍然超過了target，必須稍微回退一下，以從相反的方向接近它。提醒一下：這是所有最簡單
的loss surface之一！

![image](https://hackmd.io/_uploads/rkT0jVglC.png)

右邊的圖也很直觀，顯示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*。
![image](https://hackmd.io/_uploads/HkMSC4xlR.png)

**Note**: 每次呼叫一定要先呼叫**schadular**的`step()` method，再呼叫**optimizer**的`step()` method。 

結果會像下圖一樣，每2個step_size後就會更新learning rate。
![image](https://hackmd.io/_uploads/BkypR4el0.png)

不一定所有的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`的樣子：
![image](https://hackmd.io/_uploads/B1ZmbrxgC.png)

> **Validation Loss Schadulars**

`ReduceLROnPlateau` schadular有自己的循環時間，也就是對loss的tolerate space，這個空間滿了才會更新learning rate(乘上一個gamma值)。比方說我們可以設定epoch=4，這邊的epoch數量就是tolerate space(patience)的意思，也就是如果4個epoches之間loss都沒有改變的話，就會更新learning rate。
![image](https://hackmd.io/_uploads/rJw1GBgl0.png)

> **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` 的三種模式各有什麼差別：
![image](https://hackmd.io/_uploads/SJhLESlxC.png)

**Note**: 在實戰中，根據Leslie N. Smith的論文，一個cyclic應包含2到10個epochs，因此您需要弄清楚您的trainig set包含多少個mini-batches（這就是數據加載器的長度），並將其乘以循環中所需的epochs數來獲得一個循環中的總步數。

> **Schadular Paths**

要仔細比較schadular之前，必須先透過LR Range Test找 *max_lr*：
![image](https://hackmd.io/_uploads/SkEUHBggC.png)
結果表明學習率在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')`


得到以下的圖表：
![image](https://hackmd.io/_uploads/Skim8rgl0.png)

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

![image](https://hackmd.io/_uploads/Hy_cUBgx0.png)
由上述結果觀察到，在同一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. 




