# 【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 | 將數值縮放到合理範圍 |