---
# System prepended metadata

title: 【Pytorch 深度學習筆記】機器如何學習
tags: [python, 程式設計, DL, Python, 深度學習, 程式語言, AI]

---

# 【Pytorch 深度學習筆記】機器如何學習

[TOC]

哈囉大家好我是 LukeTseng，感謝您點進本篇筆記，該篇筆記主要配合讀本 《Deep Learning with pytorch》 進行學習，另外透過網路資料作為輔助。本系列筆記是我本人奠基深度學習基礎知識的開始，若文章有誤煩請各位指正，謝謝！

本篇為 《Deep Learning with pytorch》 這本書第五章 The mechanics of learning 的相關筆記。

## 學習僅是參數估計（Learning is just parameter estimation）

在書中的章節 5.2，所想表達的是：所謂學習，在 ML / DL 中，從機制上看就是在參數估計，用資料去把模型裡未知的參數調到最合理，讓模型對新資料也能預測得準。

在書中把學習描述成一個反覆迭代的流程。

給模型一批輸入資料、模型用目前參數算出輸出（Forward）、再把輸出跟正確答案（Ground Truth）相比得到誤差，接著用誤差去決定參數該往哪個方向更新（Backward），重複到誤差夠小為止。

在這邊最重要的觀念是模型不是靠人手寫規則學會，而是靠調參數讓輸入輸出關係越來越符合資料。

![image](https://hackmd.io/_uploads/rJpNFaw7-g.png)

Source："Deep Learning with PyTorch" P. 107 Figure 5.2

* 基準真相（Ground Truth）：希望模型產出的正確答案。
* 權重（Weights）：模型中的參數，初始值通常是隨機的。
* 前向傳播（Forward Propagation）：將數據輸入模型，根據當前的權重產生輸出。
* 誤差評估（Error Evaluation）：就是損失函數（Loss Function），透過比較模型的預算輸出與 Ground Truth，計算出一個誤差衡量值。
* 反向傳播（Backward Propagation）：計算誤差對權重的導數（梯度），了解如果權重改變一個單位，誤差會如何變化。
* 權重更新：朝著減少誤差的方向調整權重，並重複此過程直到誤差降至可接受的水準。

### 例子：校正溫度計

假設你有一個好看但沒標記單位指針式溫度計。

為了解決單位的問題，所以就來建立一個模型，將溫度計的讀數轉換為大家所熟悉的攝氏溫度。

那這模型裡面要做什麼事情？透過記錄一組讀數（未知單位 $t_u$ ）與相對應的攝氏溫度（ $t_c$ ），來估計兩者之間的轉換參數。

這也是監督式學習最典型的資料形式，`(輸入, 正確輸出)` 的配對。

#### 資料預處理

收集好相關資料後，來將這些資料轉換成 tensor（Code Source：[p1ch5
/1_parameter_estimation.ipynb](https://github.com/deep-learning-with-pytorch/dlwpt-code/blob/master/p1ch5/1_parameter_estimation.ipynb)）：

```python=
t_c = [0.5,  14.0, 15.0, 28.0, 11.0,  8.0,  3.0, -4.0,  6.0, 13.0, 21.0]
t_u = [35.7, 55.9, 58.2, 81.9, 56.3, 48.9, 33.9, 21.8, 48.4, 60.4, 68.4]
t_c = torch.tensor(t_c)
t_u = torch.tensor(t_u)
```

這些數據包含了噪點（Noise），可能是讀數誤差或設備本身的不精確。

#### 資料視覺化

如果進一步將資料視覺化，採用 matplotlib 做繪圖的話，讀數與攝氏溫度看起來會有呈現線性趨勢的樣子：

```python=
%matplotlib inline
from matplotlib import pyplot as plt

fig = plt.figure(dpi=600)
plt.xlabel("Measurement")
plt.ylabel("Temperature (°Celsius)")
plt.plot(t_u.numpy(), t_c.numpy(), 'o')

plt.savefig("temp_data_plot.png", format="png")
```

![temp_data_plot](https://hackmd.io/_uploads/B1xxJRvm-g.png)

#### 模型選擇

藉由剛才觀察到的線性趨勢，假設最簡單的模型是： $$t_c = w \times t_u + b$$

- $w$ （weight, 權重）：表示縮放比例。
- $b$ （bias, 偏差值）：表示常數的偏移量。

在此的學習任務就是要根據現有資料對參數 $w$ 跟 $b$ 來做估計這件事。如果能找到一組參數，使得輸入 $t_u$ 得到的預測溫度與實際測量的 $t_c$ 非常接近，那就完成了擬合（Fitting）或學習。

接著就可以定義模型了：

```python=
def model(t_u, w, b):
    return w * t_u + b
```

當中 $w$ 跟 $b$ 都是 0 維 tensor，即為純量的意思。

雖然這兩個是純量，但與 $t_u$ tensor 做相乘或相加的時候，PyTorch 會自動用廣播機制來產生 tensor 的結果。

## 減少損失是我們所想的（Less loss is what we want）

### 損失函數（Loss Function）

有時也稱為 Cost Function（成本函數）。

Loss Function 是一個將模型表現量化為單一數字的函數，學習過程的目標就是最小化這個數值。

基本上就是告訴訓練模型的人，這個模型的表現有多好，如果值越大表示模型表現差（預測與目標差距很大），越小表示越好（與目標差距小）。

計算方式：通常是模型產出的預測值（ $t_p$ ）與期望的目標值（ $t_c$，Ground Truth）之間的差。

### 數學運算式的選擇：絕對值 vs. 平方

為了保證損失值不論在預測過高或過低時都是正數，書中提出兩種比較常看到的選擇（接續溫度計的例子來舉例）：

1. 絕對差（ $|t_p - t_c|$ ）：這是比較簡單且直接的方式，但在預測等於目標的地方（最小值處），其導數（斜率）是未定義的。
2. 平方差（ $(t_p - t_c)^2$ ）：書中所採用的方式。

#### 為何選擇平方差

- 導數性質：平方差在接近最小值時表現更為平滑，在當 $t_p = t_c$ 時，其導數為 0，有助於數值的優化。
- 懲罰離群值（Outliers）：讓模型產生許多「微小的錯誤」比產生少數幾個「巨大的錯誤」要好，因此平方運算會放大那些錯的離譜的結果，這正是所樂見的。
- 凸函數（Convex）：對線性模型來說，這兩種 Loss Function 都是凸的，因此有明確的單一最小值，可以很容易就找到。

補充：所謂凸函數指的是二階導數 $f''(x) \ge 0$ 的情況，根據二階導數判斷法，這種情況會產生極小值。

接下來看圖可能會更好理解：

![image](https://hackmd.io/_uploads/S17-V0vmZg.png)

Source："Deep Learning with PyTorch" P. 110 Figure 5.4

左邊是絕對差的部分，右邊是平方差。

絕對值函數（ $|x - \bar{x}|$ ）在 x = 0 的地方導數不存在，這會有個問題，就是不能做 gradient descent，因為梯度的原理就是求導數，然後剛好那邊不存在，也就沒有辦法計算梯度。

而平方差函數（ $(x - \bar{x})^2$ ） 恰好相反，在 x = 0 附近曲線平滑，可微分（導數 = 0）。

### 繼續溫度計例子：定義 loss function

先前有將模型定義好，接著就來定義一個 Loss Function，書中採取的方式是均方誤差（Mean Square Error, MSE） 的方式。

```python=
def loss_fn(t_p, t_c):
    squared_diffs = (t_p - t_c)**2
    return squared_diffs.mean()
```

`(t_p - t_c)**2` 會對 tensor 中的每個元素計算差異並平方，產生一個與輸入相同形狀的新 tensor。

最後呼叫 `.mean()` 方法，將 tensor 中所有元素的平方差取平均值，最終輸出一個單一的純量損失值。

由於 loss function 的輸出是要一個純量值，因而這邊用 `.mean()` 求純量值，而非直接 `return squared_diffs`，因為這東西是個向量，看不出模型的好壞程度。

### 初始化參數及檢查損失值

將 $w$ 權重設定為 1.0，偏差值 $b$ 設定為 0.0。

接下來呼叫模型 `model(t_u, w, b)`，而這時候的模型僅僅只是把輸入的資料原封不動的做輸出（ $1 \times x + 0 = x$ ）

```python=
w = torch.ones(())
b = torch.zeros(())

t_p = model(t_u, w, b)
t_p
```

Output：

```
tensor([35.7000, 55.9000, 58.2000, 81.9000, 56.3000, 48.9000, 33.9000,
        21.8000, 48.4000, 60.4000, 68.4000])
```

而在這個步驟就已經算是前向傳播的部分了。將數據輸入模型，根據當前的權重產生輸出，就是前向傳播的定義。

接下來做檢查損失值的動作：

```python=
loss = loss_fn(t_p, t_c)
loss
```

Output：

```
tensor(1763.8846)
```

可以發現得出的損失值非常大，表示當前的參數跟實際值的差異極大，因此需要透過後續的優化過程來降低這個數值。

### 廣播機制（Broadcastings）的補充

廣播機制會從最後一個維度（末尾維度）開始向後比對。

以下是匹配 tensor 元素的規則：

1. 對於由後往前數的每個索引維度，如果其中一個運算元在該維度上的大小為 1，則 PyTorch 會將該維度上的單一元素與另一個 tensor 在該維度上的每個元素進行配對。
2. 如果兩個 tensor 的大小都大於 1，則它們必須相同，並使用自然比對。
3. 如果一個 tensor 的索引維度比另一個 tensor 多，則另一個 tensor 的所有元素都會用來符合這些維度上的每個元素。

書中用一個示意圖來表示廣播機制的運作原理：

![image](https://hackmd.io/_uploads/rksDJ-OmZl.png)

Source："Deep Learning with PyTorch" P. 112

另外書中也舉了這樣的例子：

```python=
x = torch.ones(())
y = torch.ones(3,1)
z = torch.ones(1,3)
a = torch.ones(2, 1, 1)
print(f"shapes: x: {x.shape}, y: {y.shape}")
print(f"        z: {z.shape}, a: {a.shape}")
print("x * y:", (x * y).shape)
print("y * z:", (y * z).shape)
print("y * z * a:", (y * z * a).shape)
```

Output：

```
shapes: x: torch.Size([]), y: torch.Size([3, 1])
        z: torch.Size([1, 3]), a: torch.Size([2, 1, 1])
x * y: torch.Size([3, 1])
y * z: torch.Size([3, 3])
y * z * a: torch.Size([2, 3, 3])
```

形狀為 `()`（純量，零維張量）與形狀為 `(3, 1)` 的張量相乘後，形狀的結果會是 `(3, 1)`。

這個結果主要遵循上面的第三條規則。

接下來 `(3, 1)` 跟 `(1, 3)` 做相乘，得到的形狀會是 `(3, 3)`。這部分遵循上面第一條規則，兩個 tensor 在該維度的長度完全一致、其中一個維度長度為 1。如果一個張量的維度比另一個少，則會自動將缺失的維度視為長度 1 並進行擴展，這也是為什麼結果會是 `(3, 3)` 的原因。

## 沿著梯度下降（Down along the gradient）

在上一節有提及到如何優化來降低 loss function 的產生值，那這個優化的方法就叫做梯度下降（Gradient Descent）。

書中提供了轉鈕作為梯度下降的比喻。

想像面前有一台機器，上面有兩個標示為 $w$ （權重）和 $b$ （偏差值）的轉鈕，螢幕上顯示著損失值（Loss）。

目標是要把這個 Loss 降到最低，那因為不知道轉扭會對損失這件事情會有多大的影響，所以會微調轉扭，觀察 Loss 是增加還是減少，從而決定旋轉方向。

當接近最小值（損失變化變慢）時，會縮小調整幅度，避免錯過最低點或讓損失再次攀升。

總之，Gradient Descent 是在計算損失函數相對於每個參數的變化率，並朝著損失減少的方向修改參數的方法。

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

Source："Deep Learning with PyTorch" P. 113 Figure 5.5

### 數值估計變化率（求導數）

為了知道如何轉動轉鈕（調整參數 $w$ 和 $b$），得要知道微調參數後損失會如何變化。在這邊書中的做法是給參數加一個極小值 `delta` $\delta$ ，觀察損失在附近的變化。

```python=
delta = 0.1

loss_rate_of_change_w = \
    (loss_fn(model(t_u, w + delta, b), t_c) - 
     loss_fn(model(t_u, w - delta, b), t_c)) / (2.0 * delta)
```

這段程式碼在計算當 $w$ 改變時，Loss Function 的變化幅度。

其實就是在求導數啦，這一段可以寫成這樣的數學公式： $$f'(w) \approx \frac{f(w + \delta) - f(w - \delta)}{2\delta}$$

這種叫做中心差分，就是前向差分 + 後向差分，可以想像往左一步也往右一步的感覺。

前向差分（導數定義）： $$f'(w) \approx \frac{f(w + \delta) - f(w)}{\delta}$$

後向差分： $$f'(w) \approx \frac{f(w) - f(w - \delta)}{\delta}$$

中心差分在步長一樣小的情況下，近似通常會更準，因此書中採用中心差分的方式計算 Loss 值在附近的變化。

### 確定調整方向與幅度

得到變化率後，要決定調整參數的方向：

- 方向選擇：
    - 變化率是正的，代表增加 $w$ 會讓損失增加，所以要減少 $w$。
    - 變化率是負的，則要增加 $w$ 以最小化損失。
- 比例調整：更新的幅度應與損失的變化率成比例，這樣對損失影響較大的參數會得到較大的調整。

### 學習率（Learning Rate）

在實際更新參數時，不能直接看變化率調整，因為變化率在遠處可能會與當前的 $w$ 完全不同，~~不然怎麼叫做變化率嘛~~。因此需要一個較小的（可能 0.001）縮放因子（scaling factor）乘上這個變化率，在機器學習中稱為學習率（learning_rate：用 $\eta$ eta 表示）。

簡言之，Learning Rate 是要讓模型每一步要跨多大步的設定，專門控制它更新權重時的步伐大小。如果步伐跨的很大，則有可能會錯失掉最小值的點，在最小值附近亂跳或產生震盪，反之跨的太小，那這個模型不知道是要跑掉民國幾年才跑得完。

在這邊撰寫程式碼：

```python=
learning_rate = 1e-2

w = w - learning_rate * loss_rate_of_change_w
```

等號前面的 $w$，代表著下一個 $w$；後面的 $w$ 則為當前的 $w$。

$w$ 後面的負號是為了要讓 $w$ 做增加或減少的事情，例如得到的導數 `loss_rate_of_change_w` < 0，這時候 $w$ 是要做增加，因此前面加上一個負號讓式子變正的。

然後同樣的邏輯也適用於 $b$：

```python=
loss_rate_of_change_b = \
    (loss_fn(model(t_u, w, b + delta), t_c) - 
     loss_fn(model(t_u, w, b - delta), t_c)) / (2.0 * delta)

b = b - learning_rate * loss_rate_of_change_b
```

經過反覆迭代這些數值評估，且假設 learning rate 選得夠小，最後肯定收斂到參數的最優值，使得最後估計出的 loss 最小。

### 由數值估計轉為「分析法」（Analytical）

原本的數值估計法是給 $w$ 增加一個微小的 $\delta$ 值，並觀察 Loss 的變化率來估計梯度。但這種方法有兩個主要問題：

1. 擴展性差：當模型有數百萬個參數時，對每個參數重複執行模型來探測行為是非常低效的行為。
2. 精準度受限於 delta：擾動的大小（如 0.1）會影響估計的準確性。如果 Loss 變化太快，這種方法無法給出最正確的下降方向。

分析法（Analytical）則透過數學公式直接求導，相當於讓這個擾動量趨近於無窮小。

那分析法怎麼做呢？利用 chain rule（連鎖律），在此之前先對損失函數對參數的變化拆解為兩個部分：

1. Loss 對其輸入（即模型輸出 $t_p$ ）的變化率。
2. 模型輸出 $t_p$ 對參數（如 $w$）的變化率。

公式表示為：$$\frac{dLoss}{dw}=\frac{dLoss}{dt_p} \cdot \frac{dt_p}{dw}$$

這邊所對應的是書上這條：`d loss_fn / d w = (d loss_fn / d t_p) * (d t_p / d w)`

#### 第一部分：Loss 對模型輸出的導數

先前的 Loss 定義為均方誤差 MSE：

```python=
def loss_fn(t_p, t_c):
    squared_diffs = (t_p - t_c)**2
    return squared_diffs.mean()
```

然後對某一個 $t_{p,i}$ 求偏導，得到其導函數為：

```python=
def dloss_fn(t_p, t_c):
    # 除以 t_p.size(0) 是因為 loss 取了平均值 (mean)
    dsq_diffs = 2 * (t_p - t_c) / t_p.size(0)
    return dsq_diffs
```

原本 loss_fn 的公式是 $$loss = \frac{1}{N}\sum^N_{i=1}(t_{p,i} - t_{c,i})^2$$

當中 $N$ 是樣本數，除以 $N$ 就是取平均值。

對 $loss$ 求偏導後得到： $$\frac{\partial loss}{\partial t_{p,i}} = \frac{1}{N} \cdot 2(t_{p,i} - t_{c,i})$$

`t_p.size(0)` 就等同於那個 $N$ 。

#### 第二部分：模型輸出對參數的導數

模型是一個線性函數，長這樣：$t_p = w \cdot t_u + b$

對 $w$ 和 $b$ 分別求導：

* 對 $w$ 求導：只有 $t_u$ 項與 $w$ 相關，結果為 $t_u$。
* 對 $b$ 求導：$b$ 的係數是 1.0，結果為 1.0。

寫成程式碼則會是：

```python=
def model(t_u, w, b):
    return w * t_u + b
```

```python=
def dmodel_dw(t_u, w, b):
    return t_u
```

```python=
def dmodel_db(t_u, w, b):
    return 1.0
```

#### 定義完整的梯度函數

將上述部分合起來，就可以得到 `grad_fn` 了。

這個函數會計算所有資料點上的偏導數，並做加總（廣播機制的逆向過程），最後回傳一個包含所有參數梯度的 tensor。

```python=
def grad_fn(t_u, t_c, t_p, w, b):
    dloss_dtp = dloss_fn(t_p, t_c)
    dloss_dw = dloss_dtp * dmodel_dw(t_u, w, b)
    dloss_db = dloss_dtp * dmodel_db(t_u, w, b)
    # 將每個樣本的梯度加總，得到整批數據的梯度
    return torch.stack([dloss_dw.sum(), dloss_db.sum()])
```

將這個函數寫成數學式子就會是：

![image](https://hackmd.io/_uploads/H15R3QOX-l.png)

Source："Deep Learning with PyTorch" P. 116 Figure 5.7

## 透過迭代去擬合模型（Iterating to fit the model）

在這邊會結合先前學到的損失函數與梯度計算做整合，再透過迭代讓模型真正學習。

### 什麼是訓練迴圈（Training Loop）？

當有了模型、損失函數和梯度函數後，還需要一個自動化的過程來不斷更新參數，直到損失降到最低為止，這就是訓練迴圈存在的意義。

這個過程包含以下兩個要素：

- Epoch（訓練週期）：機器學習術語，把資料集（data set）裡面的資料全部訓練過一次就是一個 Epoch。
- 終止條件：迭代通常會持續到預設的 Epoch 結束，或是參數 $w$ 與 $b$ 不再發生顯著變化為止。

完整的 training_loop 程式碼：

```python=
def training_loop(n_epochs, learning_rate, params, t_u, t_c):
    for epoch in range(1, n_epochs + 1):
        w, b = params
        
        # 1. 前向傳播 (Forward pass)
        t_p = model(t_u, w, b)
        # 2. 計算損失值
        loss = loss_fn(t_p, t_c)
        # 3. 反向傳播 (Backward pass / 計算梯度)
        grad = grad_fn(t_u, t_c, t_p, w, b)
        # 4. 更新參數 (梯度下降)
        params = params - learning_rate * grad
        # 打印進度
        print('Epoch %d, Loss %f' % (epoch, float(loss)))
            
    return params
```

- 前向傳播（`model(t_u, w, b)`）：將輸入數據 `t_u` 丟進模型，得到預測值 `t_p`。
- 損失函數（`loss_fn(t_p, t_c)`）：比較預測值與實際觀測值 `t_c` 的差距。
- 反向傳播（`grad_fn(t_u, t_c, t_p, w, b)`）：計算損失函數對參數的偏導數（梯度），找出讓損失下降的方向。
- 更新參數（gradient descent）：沿著梯度的反方向移動一小步（由 `learning_rate` 決定步長）。

### overtraining : learning rate 設得太大

書中作者用 `learning_rate = 1e-2` 也就是 0.01 下去跑 100 Epoch 的時候，結果發現 Loss 不減反增，變成超大的 `inf`。

如下程式碼及其輸出結果：

```python=
training_loop(
    n_epochs = 100, 
    learning_rate = 1e-2, 
    params = torch.tensor([1.0, 0.0]), 
    t_u = t_u, 
    t_c = t_c)
```

Output：

```
Epoch 1, Loss 1763.884644
    Params: tensor([-44.1730,  -0.8260])
    Grad:   tensor([4517.2969,   82.6000])
Epoch 2, Loss 5802485.500000
    Params: tensor([2568.4014,   45.1637])
    Grad:   tensor([-261257.4219,   -4598.9712])
Epoch 3, Loss 19408035840.000000
    Params: tensor([-148527.7344,   -2616.3933])
    Grad:   tensor([15109614.0000,   266155.7188])
...
Epoch 10, Loss 90901154706620645225508955521810432.000000
    Params: tensor([3.2144e+17, 5.6621e+15])
    Grad:   tensor([-3.2700e+19, -5.7600e+17])
Epoch 11, Loss inf
    Params: tensor([-1.8590e+19, -3.2746e+17])
    Grad:   tensor([1.8912e+21, 3.3313e+19])
tensor([-1.8590e+19, -3.2746e+17])
```

原因是過度訓練（overtraining） or 發散的問題，就是參數的更新幅度太大，導致模型在最小值的兩側劇烈擺動，每一次更新都越過了最小值並跳得更遠。

如圖：

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

Source："Deep Learning with PyTorch" P. 118 Figure 5.8

解決方法就是把 learning rate 再調小一點，調成 `learning_rate = 1e-4`。

調整完後再執行一次：

```python=
training_loop(
    n_epochs = 100, 
    learning_rate = 1e-4, 
    params = torch.tensor([1.0, 0.0]), 
    t_u = t_u, 
    t_c = t_c)
```

Output：

```
Epoch 1, Loss 1763.884644
    Params: tensor([ 0.5483, -0.0083])
    Grad:   tensor([4517.2969,   82.6000])
Epoch 2, Loss 323.090546
    Params: tensor([ 0.3623, -0.0118])
    Grad:   tensor([1859.5493,   35.7843])
Epoch 3, Loss 78.929634
    Params: tensor([ 0.2858, -0.0135])
    Grad:   tensor([765.4667,  16.5122])
...
Epoch 10, Loss 29.105242
    Params: tensor([ 0.2324, -0.0166])
    Grad:   tensor([1.4803, 3.0544])
Epoch 11, Loss 29.104168
    Params: tensor([ 0.2323, -0.0169])
    Grad:   tensor([0.5781, 3.0384])
...
Epoch 99, Loss 29.023582
    Params: tensor([ 0.2327, -0.0435])
    Grad:   tensor([-0.0533,  3.0226])
Epoch 100, Loss 29.022669
    Params: tensor([ 0.2327, -0.0438])
    Grad:   tensor([-0.0532,  3.0226])
tensor([ 0.2327, -0.0438])
```

在這裡有個問題，就是雖然 Loss 穩定了，但是訓練到 Epoch 100 的時候，梯度還是很大，幾乎沒什麼降，就表示離最優解還很遠。

### 參數規模不一致

書中的作者觀察梯度發現了問題，就是權重 $w$ 的梯度比偏差值 $b$ 的梯度大了約 50 倍。

> We can see that the first-epoch gradient for the weight is about 50 times larger than the gradient for the bias. 

也表示說 $w$ 跟 $b$ 處於不同的空間尺度（scale）。

如果學習率大到足以讓 $b$ 更新，就會讓 $w$ 不穩定（爆炸）；如果學習率小到能讓 $w$ 穩定，對 $b$ 來說就太小了，更新幾乎停滯。

## 輸入正規化（Normalizing inputs）

為了解決「學習率太高會導致損失值 `inf`」、「太低則會讓訓練極其緩慢甚至停滯」的問題，因此需要對輸入做正規化（Normalizing）的動作。

具體來說怎麼做呢？就是要把輸入資料的範圍大致落在 `-1.0` 到 `1.0` 之間。

在溫度計例子中，原始資料 `t_u` 的數值較大，於是乘以 `0.1`，得到了正規化後的資料 `t_un`：

```python
t_un = 0.1 * t_u
```

使用正規化後的資料 `t_un` 後，再執行一次 training_loop，把 learning rate 設回先前會導致不穩的 1e−2。

結果發現：

- 穩定性大幅提升：不像之前 learning rate 調高的時候，會出現 `inf` 的狀況。
- 梯度趨於平衡

最後再用這個正規化後的資料，執行 5000 輪的 Epoch 時，損失值最終降到了約 `2.927`。得到的權重 w 為 `5.3671`，偏差值 b 為 `-17.3012`。

雖說這種規模的問題可能不用用到正規化的方式去優化，也可以在那邊手動調參數，但日後訓練龐大的神經網路，那些參數量是大的可怕，總不可能一個一個手動調，因此需要用到正規化的技巧來優化參數。

## 總整理

### 學習就是在做參數估計

在 ML / DL 中，「學習」本質上不是學規則，而是用資料反覆調整模型參數，讓輸入與輸出關係最符合資料分佈。

整個學習流程可理解成這樣：

> Forward → 計算 Loss → Backward → 更新參數 → 重複前面步驟

模型本身只是函數形式，真正讓模型變聰明的是參數（weights, bias）的調整。

### 損失函數（Loss Function）：把「好壞」變成可視化的數字

為什麼需要 Loss Function？因為電腦沒有辦法去理解一個模型的好壞，只能最小化一個數值。

為何選擇平方差（MSE）？

相較於絕對差：

1. 可微：在最小值處導數為 0，適合梯度下降法。
2. 平滑：數值穩定，利於優化。
3. 懲罰離群值：鼓勵整體誤差變小。
4. 凸函數（對於線性模型來說）：保證只有一個全域最小值。

### 梯度下降（gradient descent）：沿著最陡的下降方向走

梯度下降就是在算出「參數變一點點，Loss 會變多少」，然後往 Loss 下降的方向移動。

做到梯度下降基本上有三個要點：

1. 梯度（Gradient）：方向。
2. 學習率（Learning Rate）：步伐大小。
3. 反覆迭代：逐步逼近最小值。

### 數值法 vs. 分析法

數值估計（差分法）：

- 原理直觀
- 不具擴展性
- 精度受 delta 影響

分析法（Chain Rule）：

- 直接算偏導
- 高效、精準
- 是 Backpropagation 的數學基礎

### 訓練迴圈（Training Loop）：學習自動化

一個完整訓練流程固定包含：

1. Forward
2. 計算 Loss
3. Backward（算梯度）
4. 更新參數

### learning rate 陷阱

1. 太大 → 發散、Loss 爆炸（inf）
2. 太小 → 幾乎不動，訓練停滯

### 輸入正規化（Normalization）

目的：讓所有特徵落在相近的數值尺度

效果：

- 梯度平衡
- 可用較大的 learning rate
- 收斂更穩定、更快

### 專有名詞對照表

| 中文名詞     | 英文名詞                               | 說明       |
| -------- | ---------------------------------- | ----------------- |
| 學習       | Learning                           | 用資料估計參數，使模型表現變好   |
| 參數估計     | Parameter Estimation               | 尋找最佳參數值的過程        |
| 模型       | Model                              | 描述輸入與輸出關係的函數      |
| 參數       | Parameters                         | 模型中可被學習的數值        |
| 權重       | Weight                             | 控制輸入影響力大小         |
| 偏差值      | Bias                               | 用來平移輸出的常數         |
| 擬合       | Fitting                            | 模型成功貼近資料          |
| 監督式學習    | Supervised Learning                | 使用 (輸入, 正確輸出) 訓練  |
| 資料集      | Dataset                            | 用於訓練或測試的資料        |
| 輸入資料     | Input Data                         | 餵給模型的數值           |
| 正確答案     | Ground Truth                       | 希望模型輸出的真實值        |
| 噪聲       | Noise                              | 資料中的誤差或干擾         |
| 廣播機制     | Broadcasting                       | 自動擴展張量形狀以運算       |
| 前向傳播     | Forward Propagation / Forward Pass | 用目前參數計算輸出         |
| 模型輸出     | Prediction / Output                | 模型預測結果            |
| 誤差評估     | Error Evaluation                   | 衡量預測與正解差距         |
| 反向傳播     | Backpropagation                    | 計算誤差對參數的影響        |
| 梯度       | Gradient                           | Loss 對參數的變化率      |
| 損失函數     | Loss Function                      | 將模型表現轉為數字         |
| 成本函數     | Cost Function                      | Loss Function 的別稱 |
| 損失值      | Loss                         | 模型好壞的量化指標         |
| 均方誤差     | Mean Squared Error (MSE)           | 常用回歸損失函數          |
| 絕對差      | Absolute Error                     | 使用差值絕對值           |
| 平方差      | Squared Error                      | 使用差值平方            |
| 離群值      | Outlier                            | 與其他資料差距極大的點       |
| 凸函數      | Convex Function                    | 只有單一全域最小值         |
| 導數       | Derivative                         | 函數的變化率            |
| 偏導數      | Partial Derivative                 | 對單一變數求導           |
| 變化率      | Rate of Change                     | 輸入改變造成輸出變化        |
| 中心差分     | Central Difference                 | 數值估計導數方法          |
| 前向差分     | Forward Difference                 | 往前估計導數            |
| 後向差分     | Backward Difference                | 往後估計導數            |
| 分析法      | Analytical Method                  | 直接用公式算導數          |
| 梯度下降     | Gradient Descent                   | 沿梯度反方向降低 Loss     |
| 最小值      | Minimum                            | Loss 最低的位置        |
| 收斂       | Convergence                        | 參數趨於穩定            |
| 發散       | Divergence                         | 參數失控、不穩定          |
| 學習率      | Learning Rate                      | 控制更新步伐大小          |
| 縮放因子     | Scaling Factor                     | 縮小梯度的倍率           |
| 訓練       | Training                           | 調整參數的過程           |
| 訓練迴圈     | Training Loop                      | 自動化訓練流程           |
| 訓練週期     | Epoch                              | 全資料訓練一次           |
| 過度訓練 | Overtraining                       | 步伐過大導致發散          |
| 正規化      | Normalization                      | 將數值縮放到合理範圍        |
