# 機器學習-神經網路 ## 甚麼是神經網路 神經網路(Neural Network,簡稱 NN)是一種模仿生物神經系統運作的計算模型,用於學習資料中的模式與規律。它透過多層神經元進行訊號傳遞,並能夠自動從資料中學習特徵,適用於回歸、分類、影像處理、自然語言處理等多種任務。 ### 神經網路的基本構成 1. **輸入層(Input Layer)** - 接收原始資料,例如一個數值、圖像或文字的特徵向量。 2. **隱藏層(Hidden Layers)** - 進行訊號的非線性轉換,提取資料的高階特徵。 - 每個神經元會計算加權和並套用激活函數,例如 `ReLU`, `Sigmoid`, `Tanh`。 3. **輸出層(Output Layer)** - 產生模型預測結果,對回歸問題輸出數值,對分類問題輸出機率。 ### 神經元的運作公式 每個神經元的輸出可表示為: $$ a = f\Big(\sum_{i} w_i x_i + b\Big) $$ - `x_i`:輸入 - `w_i`:權重(Weight) - `b`:偏置(Bias) - `f`:激活函數(Activation Function) - `a`:神經元輸出 ### 神經網路訓練流程 1. **前向傳播(Forward Pass)** - 計算每層神經元的輸出,得到模型預測值 `y_pred`。 2. **損失計算(Loss Function)** - 衡量模型預測與真實值的差距,例如均方誤差(MSE): $$ MSE = \frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2 $$ 3. **反向傳播(Backward Pass)** - 利用鏈式法則計算每個權重對損失的梯度。 4. **權重更新(Gradient Descent)** - 根據梯度更新權重與偏置,使損失下降。 5. **重複迴圈** - 持續訓練直到損失收斂或達到設定的停止條件。 ### 範例 (python) 這是一個用於預測方程式的簡易神經網路範例 ```python= import numpy as np import matplotlib.pyplot as plt np.random.seed(5) # 建立資料 x_raw = np.linspace(-50, 50, 200).reshape(-1, 1) x = (x_raw - np.mean(x_raw)) / np.std(x_raw) y = 5*x + 1 + np.random.randn(200, 1) * 0.3 # structure layer_sizes = [1, 64, 64, 1] lr = 0.01 # initialize weights = [] biases = [] for i in range(len(layer_sizes) - 1): w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * np.sqrt(2 / layer_sizes[i]) b = np.zeros((1, layer_sizes[i+1])) weights.append(w) biases.append(b) # 激活函數 tanh def tanh(x): return np.tanh(x) def tanh_derivative(x): return 1 - np.tanh(x)**2 loss = 1 epoch = 0 while loss>0.1: # forward activations = [x] zs = [] a = x for w, b in zip(weights[:-1], biases[:-1]): z = a.dot(w) + b zs.append(z) a = tanh(z) activations.append(a) z = activations[-1].dot(weights[-1]) + biases[-1] zs.append(z) y_pred = z loss = np.mean((y_pred - y)**2) # backward dy = 2 * (y_pred - y) / len(y) deltas = [dy] for i in reversed(range(len(layer_sizes) - 2)): dz = deltas[-1].dot(weights[i+1].T) * tanh_derivative(zs[i]) deltas.append(dz) deltas.reverse() for i in range(len(weights)): dw = activations[i].T.dot(deltas[i]) db = np.sum(deltas[i], axis=0, keepdims=True) weights[i] -= lr * dw biases[i] -= lr * db lr = 0.01 * (0.99 ** (epoch // 100)) if epoch % 500 == 0: print(f"Epoch {epoch}, Loss={loss:.5f}") # 預測 a = x for w, b in zip(weights[:-1], biases[:-1]): a = tanh(a.dot(w) + b) y_pred = a.dot(weights[-1]) + biases[-1] # render plt.figure(figsize=(8,5)) plt.scatter(x_raw, y, s=10, label="True data", alpha=0.6) plt.plot(x_raw, y_pred, color="red", linewidth=2, label="NN prediction") plt.xlabel("x (original scale)") plt.ylabel("y") plt.legend() plt.title("Neural Network Fit (x ∈ [-50, 50])") plt.grid(alpha=0.3) plt.tight_layout() plt.savefig("nn_predict.png", dpi=300) ``` >專案連結(附有預測結果圖) >https://github.com/Neox9487/nns-equation-predict **程式碼解釋** 1. 建立資料 ```python= x_raw = np.linspace(-50, 50, 200).reshape(-1, 1) x = (x_raw - np.mean(x_raw)) / np.std(x_raw) y = 5*x + 1 + np.random.randn(200, 1) * 0.3 ``` - `x_raw`:原始輸入,範圍從 -50 到 50,總共 200 個點 - `x`:標準化後的輸入,使平均值為 0、標準差為 1這樣神經網路訓練更穩定,不會出現梯度爆炸或消失 - `y`:目標輸出(真實資料)線性函數 5*x + 1 - 加上高斯噪聲 `np.random.randn(...) * 0.3` 模擬真實資料的隨機性 2. 定義神經網路結構與學習率 ```python= layer_sizes = [1, 64, 64, 1] lr = 0.01 ``` - `[1, 64, 64, 1]` 表示1個輸入神經元(`x`)-> 兩層隱藏層,每層64個神經元 -> 1個輸出神經元(`y_pred`) - `lr`:初始學習率(learning rate) 3. 初始化權重與偏置 ```python= weights = [] biases = [] for i in range(len(layer_sizes) - 1): w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * np.sqrt(2 / layer_sizes[i]) b = np.zeros((1, layer_sizes[i+1])) weights.append(w) biases.append(b) ``` - `weights`:每層的權重矩陣,大小 (上一層神經元數, 下一層神經元數) - `biases`:每層的偏置向量,初始化為 0 - 權重初始化方式為 **He初始化**,適合 tanh 或 ReLU 激活函數,公式為 `np.sqrt(2 / fan_in)`,讓輸入方差保持穩定 4. 定義激活函數 ```python= def tanh(x): return np.tanh(x) def tanh_derivative(x): return 1 - np.tanh(x)**2 ``` - `tanh(x)`:雙曲正切函數,輸出範圍 (-1, 1) - `tanh_derivative(x)`:tanh 的導數,用於反向傳播計算梯度 5. 訓練迴圈 ```python= loss = 1 epoch = 0 while loss>0.1: ... ``` > 我常用的激活函數: https://hackmd.io/86IM4PiAS46HpTUUp7uXdQ 5-1. 前向傳播(Forward Pass) ```python= activations = [x] zs = [] a = x for w, b in zip(weights[:-1], biases[:-1]): z = a.dot(w) + b zs.append(z) a = tanh(z) activations.append(a) z = activations[-1].dot(weights[-1]) + biases[-1] zs.append(z) y_pred = z ``` - `z = a.dot(w) + b`:線性組合(加權和 + 偏置) - `a = tanh(z)`:激活函數,加入非線性 - 最後一層不加激活函數(因為是回歸問題) - `activations`:儲存每層的輸出,用於反向傳播 - `zs`:儲存每層的線性輸入(`z`) 5-2. 計算損失(Loss) ```python= loss = np.mean((y_pred - y)**2) ``` - 使用高中教的均方差 $MSE = \frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2$ 計算 Loss 5-3. 反向傳播(Backward Pass) ```python= dy = 2 * (y_pred - y) / len(y) deltas = [dy] for i in reversed(range(len(layer_sizes) - 2)): dz = deltas[-1].dot(weights[i+1].T) * tanh_derivative(zs[i]) deltas.append(dz) deltas.reverse() ``` - 計算每層的 誤差項 `delta` - 從輸出層往輸入層反傳 - 使用鏈式法則乘上 `tanh_derivative(z)` 5-4. 更新權重、偏置跟學習率 ```python= for i in range(len(weights)): dw = activations[i].T.dot(deltas[i]) db = np.sum(deltas[i], axis=0, keepdims=True) weights[i] -= lr * dw biases[i] -= lr * db lr = 0.01 * (0.99 ** (epoch // 100)) ``` - 計算每層權重梯度 dw 和偏置梯度 db - 使用梯度下降更新權重與偏置 - 每 100 個 epoch,學習率衰減 1%,讓訓練後期更穩定 (不一定要加!) 6. 預測(Forward Pass) ```python= a = x for w, b in zip(weights[:-1], biases[:-1]): a = tanh(a.dot(w) + b) y_pred = a.dot(weights[-1]) + biases[-1] ``` - 使用訓練好的權重做預測 - 與訓練時前向傳播相同 7. 畫圖 ```python= plt.figure(figsize=(8,5)) plt.scatter(x_raw, y, s=10, label="True data", alpha=0.6) plt.plot(x_raw, y_pred, color="red", linewidth=2, label="NN prediction") plt.xlabel("x (original scale)") plt.ylabel("y") plt.legend() plt.title("Neural Network Fit (x ∈ [-50, 50])") plt.grid(alpha=0.3) plt.tight_layout() plt.savefig("nn_predict.png", dpi=300) ``` ### 補充說明 #### 1. **偏置(Bias)** 偏置(Bias)讓神經元在輸入為 0 或沒有特徵信號時,仍能產生非零輸出讓模型能夠靈活地學習資料中的平移關係。 舉個栗子: ![image](https://hackmd.io/_uploads/BkHbcsD0ee.png) 如果**沒有偏置項:** $z=Wx$ 那當 $x=0$(輸入全為 0)時,就必定有 $z=0$ 輸出 經過激活函數後(例如 ReLU、sigmoid),輸出也是固定值 (0)。 這表示神經元在輸入沒有訊號時「完全無法動作」。 網路的學習能力受到限制,尤其在學習**非過原點 non-zero intercept** 的函數時。 **加上偏置之後:** $z=Wx+b$ 現在即使 $x=0$ 輸出仍是 $z=b$ 這表示神經元能夠「自動調整激活起點」,學習更廣的資料分佈。 **也就是說:** Bias 會讓網路「即使在輸入值很小或為 0 時,也能學會非零輸出」 (防踩雷)我之前認為 Bias 能讓網路「在沒有資料時自我訓練」,這是錯的!!! #### 2. **鏈式法則 (Chain Rule)** 鏈式法則,也稱連鎖律,是微積分中求複合函數導數的一種規則。它指出一複合函數的導數是其組成函數在相應點的導數的乘積,就像鎖鏈一環套一環。這種方法可以透過最外層的函數開始,逐層向內求導,直到最內層。