# 深度學習 01:深度學習基礎
###### tags: `Deep Learning`
## 隱藏層
1. 隱藏層數量
處理複雜問題時,**深度網路的參數效率比淺層網路高得多**,意指可以用多層深度網路取代淺層多神經元網路,這樣可以降低指數量級的神經元數量來模擬複雜的函數,因此可以用相同規模的訓練資料取得好很多的效果。
2. 神經元數量
以前流行將隱藏層的神經元排成金字塔形狀,如 300、200、100,理論基礎是許多低階特徵可以合併成數量少的高階特徵。<font color="#f00">**但這作法已經被徹底捨棄了,多數情況下讓所有隱藏層擁有相同數量的神經元效果會和原本相同甚至更好**</font>。
實務上,先讓模型的層數和神經元數量比真正需要的多很多,之後再用 early stopping 和其他正則化技術來防止過擬通常比較簡單和高效。
---
## 學習速率(Learning rate)
一般最好的學習速率大約是最大學習速率(超過它時就會發散)的一半。
1. **Power scheduling**
<font size=4>$$η = η_0 \cdot \frac{1}{(1+ \frac{t}{s})^c}$$</font>
$η_0$ 為初始學習速率,次方 c 通常設為 1,s 為步數。
- $t = 0$,$η = η_0$
- $t = s$,$η = \frac{η_0}{2}$
- $t = 2s$,$η = \frac{η_0}{3}$
- $t = 3s$,$η = \frac{η_0}{4}$
以 keras 實現:
```python=
# 只要建立 opt 時設定 decay就可以
opt = keras.optimizers.SGD(lr=0.01, decay=1e-4)
```
<br>
2. **Exponential scheduling**
<font size=4>$$η = η_0 \cdot 0.1^{\frac{t}{s}}$$</font>
每 s 步降低 10 倍。
- $t = 0$,$η = η_0$
- $t = s$,$η = \frac{η_0}{10}$
- $t = 2s$,$η = \frac{η_0}{100}$
- $t = 3s$,$η = \frac{η_0}{1000}$
以 keras 實現:
```python=
def exponential_decay_fn(epoch):
return 0.01 * 0.1**(epoch / 20)
def exponential_decay(lr0, s):
def exponential_decay_fn(epoch):
return lr0 * 0.1**(epoch / s)
return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)
# build model
# ...
lr_scheduler = keras.callbacks.LearningRateScheduler(exponential_decay_fn)
# add callback
# ...
```
<br>
3. **Piecewise contant scheduling**
在前幾次 epoch 使用固定的學習速率,接著在另外幾次 epoch 使用較小的速率,雖然這種做法表現很好,但須不斷嘗試才可找出正確的序列。
- $t = 0 \to 5$,$η = η_0$
- $t = 5 \to 55$,$η = \frac{η_0}{10}$
以 keras 實現:
```python=
# 以相同的方式放入 callback
# method 1
def piecewise_constant_fn(epoch):
if epoch < 5:
return 0.01
elif epoch < 15:
return 0.005
else:
return 0.001
#method 2
def piecewise_constant(boundaries, values):
boundaries = np.array([0] + boundaries)
values = np.array(values)
def piecewise_constant_fn(epoch):
return values[np.argmax(boundaries > epoch) - 1]
return piecewise_constant_fn
piecewise_constant_fn = piecewise_constant([5, 15], [0.01, 0.005, 0.001])
```
<br>
4. **Preformance scheduling**
每 N step 就評估一次誤差,並在誤差停止下降時降低學習速率 x 倍
以 keras 實現:
```python=
# 連續五個 epoch無法改善最佳 loss,將學習速率乘上 0.5
lr_scheduler = keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)
```
5. **1cycle scheduling**
1cycle 會先以初始學習速率 $η_0$,在訓練前半段提升到 $η_1$,接著在訓練後半段將學降低回 $η_0$,最後幾的 epoch 時,以線性的方式將學習速率下降好幾個數量級。

---
## 優化器(Optimizer)
1. **隨機梯度下降(SGD)**
$$W = W - η\frac{\partial L}{{\partial W}}$$
當 lr 較大時,SGD 會鋸齒狀得更新,較沒效率,因 SGD 的梯度方向不是指向最小值,而是函數值減少最多的方向;lr 較小時,則會導致收斂變慢。
2. **Momentum**
Momentum 改進自 SGD 算法,讓每一次的參數更新方向不僅僅取決於當前位置的梯度,還受到上一次參數更新方向的影響:
$$\begin{align} & \mathtt v_{(t)} \leftarrow \alpha \mathtt v_{(t-1)} - η \space g(θ_{(t-1)}) \\\\ & \mathtt v_{(t)} = \begin{cases} t=0, -η\space g(θ_{(t-1)}) \\ t \geq 1, \space \alpha \mathtt v_{(t-1)} - η\space g(θ_{(t-1)}) \end{cases} \\\\ & θ_{(t+1)} \leftarrow θ_{(t)} +\mathtt v_{(t)}
\end{align}$$
W 為更新的權重參數,$g(θ)$ 為在 θ 處的梯度,$\alpha$ 可想像成摩擦力,通常設成 0.9 有不錯的表現
- Nesterov 加速梯度法
既然都知道我這一次一定會走 $\alpha \mathtt v_{(t-1)}$ 的量,那麼我何必還用現在這個位置的梯度呢?我直接先走到 $\alpha \mathtt v_{(t-1)}$,然後再根據那裡的梯度再前進一下,豈不美哉?
$$\begin{align} & \mathtt v_{(t)} \leftarrow \alpha \mathtt v_{(t-1)} - η\space g(θ_{(t-1)} + \alpha \mathtt v_{(t-1)}) \\\\ & θ_{(t+1)} \leftarrow θ_{(t)} +\mathtt v_{(t)}
\end{align}$$
可以導出:
$$θ_{(t+1)} \leftarrow θ_{(t)} + \alpha \mathtt v_{(t-1)} - η\space g(θ_{(t-1)})$$
跟上面 Momentum 公式的唯一區別在於,梯度不是根據當前參數位置 θ,而是根據先走了本來計劃要走的一步後,達到的參數位置 $θ_{(t-1)} + \alpha \mathtt v_{(t-1)}$ 計算出來的。
實驗結果表明,Nesterov 收斂的速度比 Momentum 要快。
3. **AdaGrad**
AdaGrad 引進學習率遞減的概念。當更新參數時,會乘上 $\frac{1}{\sqrt{h}}$ 來調整每個元素學習規模,白話來說就是經常變動的元素,學習率會變小,這被稱為適應性學習速度(adaptive learning rate)。
$$\begin{align} &h \leftarrow h + g(θ) \otimes g(θ) \\\\ & W \leftarrow W - η\frac{1}{\sqrt{h}}g(θ)\end{align}$$
AdaGrad 的缺點在於訓練神經網路時經常太早停止,因為學習速度下降太多,因此作者建議**不要用它來訓練深度神經網路**。
4. **RMSProp**
這是 AdaGrad 的改良版,使用指數移動平均減少過去梯度的規模。
$$\begin{align} &h \leftarrow \beta h + (1-\beta)\space g(θ) \otimes g(θ) \\\\ & W \leftarrow W - η\frac{1}{\sqrt{h}}g(θ)\end{align}$$
$\beta$ 為衰減率,通常預設值為 0.9,用以降低梯度的影響。
5. **Adam**
結合 Momentum 和 RMSProp 的概念所產生。
---
## 正則化 Regularization
**Regularization 是指經由一些限制讓 model 不至於過於複,以防止過擬合**
1. **$L_1$ 和 $L_2$ 正則化**

假設只有兩個特徵要學,藍色線代表 loss function,每一條藍線的誤差都一樣,橘色線代表額外加入的正規項,目標就是讓 loss function 最小化,也就是藍色及橘色線的交點。
- **$L_1$ 正規化**
$$L_1 = \sum\limits_{i=1}^n{θ_i}$$
$L_1$ 正規化就是在 loss function 之後加入每個參數的值,如上圖所示,很可能在尋求最小誤差時只保留住 $θ_1$ 的特徵,導致網路權重矩陣稀疏。**故 $L_1$ 常用於特徵篩選**。
此外由於 $L_1$ 的邊是直線,每個邊上的 loss 都差不多,因此會造成 **$L_1$ 的解不穩定**(邊上的白點誤差都差不多)。
- **$L_2$ 正規化**
$$L_2 = \sum\limits_{i=1}^n{θ_i}^2$$
$L_2$ 正規化就是在 loss function 之後加入每個參數的平方值,常用於**降低整體參數的大小**。且 $L_2$ 的解也比 $L_1$ 穩定許多。
2. **Dropout**
書中提到通常被設為 10~50%,在遞迴神經網路接近 20~30%,在卷積神經網路接近 40~50%。在實務上,通常只會對上面三層神經元進行 Dropout。
**Dropout 不僅只有卸除神經元。若 p = 50%,在測試期間,一個神經元接收到的資訊量平均會是訓練期間的兩倍,因此在訓練後,需要在每個神經元輸入連結權重時乘上保留機率(keep probability),也就是 1-p,或是在訓練期間將各神經元的輸出除以保留機率(tf、keras 會自動除)。**
3. **Monte Carlo Dropout(MC Dropout)**
在預測時將 Dropout 打開(training=True),預測多次後再平均。
```python=
#model = keras.models.Sequential([
# keras.layers.Flatten(input_shape=[28, 28]),
# keras.layers.Dropout(rate=0.2),
# keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
# keras.layers.Dropout(rate=0.2),
# keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
# keras.layers.Dropout(rate=0.2),
# keras.layers.Dense(10, activation="softmax")
# ])
# ...
y_probas = np.stack([model(X_test_scaled, training=True)
for sample in range(100)])
y_proba = y_probas.mean(axis=0)
y_pred = np.argmax(y_proba, axis=1)
```
如果模型有做其他特殊事情導致設定 training=True 時會跟著開啟(如 Batch Normalization),則不可使用剛才的方式,而要將原 Dropout 換成自訂的 MCDropout類別
```python=
class MCDropout(keras.layers.Dropout):
def __init__(self, inputs):
return super().call(inputs, training=True)
```
4. **Max-Norm 正則化**(和 Batch Norm 擇一)
限制每個神經元的輸入連結權重 W,讓 $||W||_2 \leq r$,其中 r 是一個超參數,在必要時以下式調整權重:
$$W \leftarrow W \frac{r}{||W||_2}$$
實際用法是將各隱藏層的 kernal_constraint 引數設為 max_norm(),並設定 r。
```python=
keras.layers.Dense(100, activation="selu", kernel_initializer="lecun_normal",
kernel_constraint=keras.constraints.max_norm(1.))
```
另外 max_norm() 有個 axis=0 的預設引數。Dense 層的權重通常長成 **[輸入數量,神經元數量]**,所以 axis=0 代表 max-norm 會分別套用至各個神經元的權重向量;如果卷積層要使用 max-norm,則務必設定 axis=[0, 1, 2]。
---
## 正規化 Normalization
$N$ 表示樣本軸,$C$ 表示通道軸,$F$ 是每個通道的特徵數量。
- **Batch Normalization 如右側所示,它是取不同樣本、同一通道、所有特徵一起做正規化。**
- **Layer Normalization 如左側所示,它取的是同一個樣本、不同通道、所有特徵一起做正規化。**

<br>
1. **Batch Normalization**(和 Max-Norm 擇一)
把每一個 batch 內的所有資料所有資料一起算出一個平均值和變異數,再拿去做 Norm,轉變成平均為 0,標準差為 1 的狀態。**在測試時,會將訓練時用的平均和變異數做正規化**。如果設在模型的第一層,就等於將輸入的資料做正規化。
- 可快速學習
- 不過度依賴權重預設值
- 控制過擬合
- 大降低梯度消失 / 梯度爆炸的風險
Batch Normalization 是按照樣本數計算歸一化統計量的,當樣本數很少時,比如說只有 4 個。這四個樣本的均值和方差便不能反映全局的統計分佈息,所以**基於少量樣本的 Batch Normalization 的效果會變得很差**。
在一個 RNN batch 中,通常各個樣本的長度都是不同的,當統計到比較靠後的時間片時,例如下圖中 $t\gt 4$ 時,這時只有一個樣本還有數據,基於這個樣本的統計資訊不能反映全局分佈,所以**在 RNN 使用 Batch Normalization 的效果並不好**。

2. **Layer Normalization**
不是橫跨批次維度做正規化,而是橫跨特徵維度。在一資料中所有特徵間做 Norm。
下圖左為 batchsize=128 時做的測試,BN 和 LN 均能取得加速收斂的效果;而下圖右為 batchsize=8 時做的測是,說明了 LN 在小尺度批量上的有效性。(**不適用 CNN**)

```python=
class LNSimpleRNNCell(keras.layers.Layer):
def __init__(self, units, activation="tanh", **kwargs):
super().__init__(**kwargs)
self.state_size = units
self.output_size = units
self.simple_rnn_cell = keras.layers.SimpleRNNCell(units,
activation=None)
self.layer_norm = LayerNormalization()
self.activation = keras.activations.get(activation)
def call(self, inputs, states):
outputs, new_states = self.simple_rnn_cell(inputs, states)
# 先 LN 再 activation
norm_outputs = self.activation(self.layer_norm(outputs))
return norm_outputs, [norm_outputs]
model = keras.models.Sequential([
keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True,
input_shape=[None, 1]),
keras.layers.RNN(LNSimpleRNNCell(20), return_sequences=True),
# ...
])
```
---
## 權重初始化
| 初始化 | 激活函數 | 標準差 |
|:------:|:----------------------------:|:---------------------------:|
| Glorot | None, tanh, sigmoid, softmax | $\frac{1}{\sqrt {n_{avg}}}$ |
| He | ReLU | $\frac{2}{\sqrt {n_{in}}}$ |
| LeCun | SeLU | $\frac{1}{\sqrt {n_{in}}}$ |
1. 線性激活函數
- **Xavier 初始化(Glorot 初始化)**
若激活函數是**線性**,假設上層節點有 $n_{in}$ 個,下層節點有 $n_{out}$ 個,用 $\frac{1}{\sqrt {n_{avg}}}$ 標準差初始化。
使用常態分佈標準差 = 1 的預設值,隨著 sigmoid 輸出趨近於 0 或 1,微分值也會趨近 0,**反向傳播的梯度越來越小,造成梯度消失**:

使用常態分佈標準差 = 0.01 的預設值,梯度集中在 0.5,每個神經元幾乎都輸出相同數值,會有**表現力受限**的問題:

使用 Xavier 初始化的預設值,分布變廣:

2. ReLU
- **He 預設值**
假設上層節點有 $n_{in}$ 個,用 $\frac{2}{\sqrt {n_{in}}}$ 標準差初始化,可以直覺解釋成,為了變得更廣泛,需要加倍的係數。
```python=
keras.layers.Dense(10, activation='relu', kernel_initializer='he_normal')
#若想要使用 f_avg
he_avg_init = keras.initializers.VarianceScaling(scale=2, mode='fan_avg', distribution='uniform')
keras.layers.Dense(10, activation='relu', kernel_initializer=he_avg_init)
```
---
## 激活函數(activation function)
一般而言:SELU > ELU > leaky ReLU > ReLU > tanh >sigmoid
當無法自動正規化時:ELU > SELU
在乎會不會跑很久:leaky > ELU
因為 ReLU 是最常用的激活函數,所以很多硬體和函式庫都會對它做優化,如果在乎速度:ReLU
1. **ReLU**

ReLU 有可能會遇到**死亡 ReLU** 問題(dying ReLU):當使用大的學習速率時,W' << 0,因此會導致 relu(W') = 0,它就死掉了,進而導致訓練組所有實例會持續輸出 0(梯度下降可能會調整底下的隱藏層,讓死亡神經元的輸入加權總和變回正數。)
$$W' = W - lr \cdot g(θ)$$
2. **leaky ReLU**
leaky ReLU 為 ReLU 的變體,在 y < 0 時會乘上一個超參數 $\alpha$(斜率),通常設成 0.01。
$$f(y) = \begin{cases}y, \space if \space y > 0 \\\\ \alpha y,\space if \space y \leq 0 \end{cases}$$
```python=
keras.layers.LeakyReLU(alpha=0.01)
```
3. **ELU**(指數線性單元,exponential linear unit)
- 超參數 $\alpha$ 是當 z 是一大負數時,f(z) 接近於哪個值
- 它在 z < 0 是負值,可讓平均輸出接近 0,有助於緩解梯度消失
- z < 0,輸出不是 0,可避免神經元死亡
- $\alpha$ = 1 時,任何地方都是平滑的,這有助於提高梯度下降的速度
$$f(z) = \begin{cases}z, \space if \space z > 0 \\\\ \alpha (e^z -1),\space if \space z \leq 0 \end{cases}$$
雖然它在訓練時有更快的收斂速度,但由於是指數函數,所以 ELU 比 ReLU 慢。
4. **SELU**
SELU 的尺度縮放版,當建立一個只使用 Dense 的模型,且所有隱藏層都用 SELU 時,**網路會自動正規化**:載運練期間,各層的輸出都傾向保持平均為 0、標準差 1,可解決梯度爆炸/梯度消失問題,但需要以下條件才會發生:
- 輸入特徵必須正規化
- 隱藏層權重必須進行 initializer='lecun_normal' 權重初始化
- 網路必須是依序的(不可用 RNN 或殘差網路),否則自動正規化不保證發生
- 論文只保證當模型都是 Dense 層組成時會發生,但有研究發現 SELU 可改善卷積網路效果
```python=
keras.layers.Dense(10, activation='selu', kernal_initializer='lecun_normal')
```
---
### reference
1. 精通機器學習,使用 Scikit-Learn, Keras 與 Tensorflow-Aurelien Greon
2. Deep Learning:用 Python 進行深度學習的基礎理論實作 - 齋藤康毅
3. Deep Learning 2:用 Python 進行自然語言處理的基礎理論實作 - 齋藤康毅
4. [比Momentum更快:揭开Nesterov Accelerated Gradient的真面目](https://zhuanlan.zhihu.com/p/22810533)
5. [什么是 L1 L2 正规化 正则化 Regularization (深度学习 deep learning)](https://www.youtube.com/watch?v=TmzzQoO8mr4&ab_channel=%E8%8E%AB%E7%83%A6Python)
6. [模型优化之Layer Normalization](https://zhuanlan.zhihu.com/p/54530247)