Python 是一種開放原始碼、簡單好記、使用自由的程式語言。Python 支援多種執行模式,包括「直譯器」與「Script 檔案」,使用者可依需求選擇適合的方式來執行程式。此外,Python 能統一執行各類函數或模組,提升開發效率。在外部函式庫方面,NumPy 與 Matplotlib 是常見的搭配工具,前者提供操作多維陣列的方便方法,後者則常用於資料視覺化,兩者皆能大幅提升資料處理與分析的效率。 # 1. 感知器 ## 1.1. 感知器的架構 感知器是一種具有輸入與輸出的演算法,當給予輸入後,會根據演算結果輸出對應的值,能模擬「開」與「關」等邏輯電路的運作方式。感知器的參數設定包含「權重」與「偏權值」,透過調整參數可以達成多種效果。 單層(單純)感知器無法處理非線性區域,如互斥或閘 (XOR) 。多層感知器(神經網路)則可透過組合多種邏輯閘,達成處理非線性區域的效果,模擬出電腦的功能。 感知器對輸入進行加權後,加上偏移值,以閾值 0 為界進行二元分類。算式如下: $y = \begin{cases} 0 & \text{,} b + w_1 x_1 + w_2 x_2 \leq 0 \\ 1 & \text{,} b + w_1 x_1 + w_2 x_2 > 0 \end{cases}$ 其中,$x_1 \,,\, x_2$ 為輸入值。$w_1 \,,\, w_2$為對應的權重。$b$ 為偏移值。$y$ 為感知器的輸出(輸出 0 或 1)。 ## 1.2. 以邏輯閘實作感知器 ### 1.2.1. 及閘 (AND GATE) 算式:$AND(x_1,x_2)=x_1∧x_2$ 真值表: | $x_1$ | $x_2$ | $x_1 \land x_2$(AND) | | ----- | ----- | -------------------- | | 0 | 0 | 0 | | 0 | 1 | 0 | | 1 | 0 | 0 | | 1 | 1 | 1 | 範例程式碼: ``` def AND(x1, x2): w1, w2, theta = 0.5, 0.5, 0.7 tmp = x1 * w1 + x2 * w2 if tmp <= theta: return 0 elif tmp > theta: return 1 print(AND(0, 0)) # 0 print(AND(0, 1)) # 0 print(AND(1, 0)) # 0 print(AND(1, 1)) # 1 ``` ### 1.2.2. 反及閘 (NAND GATE) 算式:$NAND(x_1,x_2)=¬(x_1∧x_2)$ 真值表: | $x_1$ | $x_2$ | $x_1 \land x_2$ | $\text{NAND}(x_1, x_2)$ | | ----- | ----- | --------------- | ----------------------- | | 0 | 0 | 0 | 1 | | 0 | 1 | 0 | 1 | | 1 | 0 | 0 | 1 | | 1 | 1 | 1 | 0 | 範例程式碼: ``` import numpy as np def NAND(x1, x2): x = np.array([x1, x2]) w = np.array([-0.5, -0.5]) # 權重為負 b = 0.7 # 偏差(bias) tmp = np.sum(w * x) + b if tmp <= 0: return 0 else: return 1 print(NAND(0, 0)) # 1 print(NAND(0, 1)) # 1 print(NAND(1, 0)) # 1 print(NAND(1, 1)) # 0 ``` ### 1.2.3. 或閘 (OR GATE) 算式:$OR (x_1,x_2)= x_1∨x_2$ 真值表: | $x_1$ | $x_2$ | $x_1 \lor x_2$(輸出) | | ----- | ----- | ------------------ | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 1 | 範例程式碼: ``` import numpy as np def OR(x1, x2): x = np.array([x1, x2]) w = np.array([0.5, 0.5]) # 權重 b = -0.2 # 偏差 (bias) tmp = np.sum(w * x) + b if tmp <= 0: return 0 else: return 1 print(OR(0, 0)) # 0 print(OR(0, 1)) # 1 print(OR(1, 0)) # 1 print(OR(1, 1)) # 1 ``` ### 1.2.4. 互斥或閘 (XOR GATE) XOR GATE 為非線性可分,無法以單層感知器表示,僅能透過 AND GATE、NAND GATE 及 OR GATE 組合成多層感知器得出。 算式:$x_1⊕x_2=(x_1∨x_2)∧¬(x_1∧x_2)$。即只能有一個 $x$ 是 1,不能兩個同時是 1 真值表: | $x_1$ | $x_2$ | $x_1 \oplus x_2$ | | ----- | ----- | ---------------- | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 | 範例程式碼: ``` def XOR(x1, x2): s1 = NAND(x1, x2) s2 = OR(x1, x2) y = AND(s1, s2) return y XOR(0, 0) = 0 XOR(0, 1) = 1 XOR(1, 0) = 1 XOR(1, 1) = 0 ``` # 2. 神經網路的架構 神經網路是由許多感知器組成的層狀結構,並透過活化函數將多個感知器的加權總和轉化為非線性結果。 神經網路包含輸入層、隱藏層與輸出層,每層神經元會將其輸出傳遞給下一層。透過誤差反向傳播(Backpropagation)與梯度下降法(Gradient Descent)等學習機制,神經網路可以自動調整內部權重與偏差值,使模型逐漸學會對輸入資料做出正確的預測。 ## 2.1. 活化函數 「近代的活化函數方法」使神經元的加權總合轉換為非線性結果。透過組合多個神經元及活化函數,神經網路具有逼近任何連續函數的能力,稱為通用逼近。 各種方法比較如下: | 特性/函數名稱 | 階梯函數 (Step) | Sigmoid 函數 | ReLU 函數 | | ----------- | -------------------------------------------------------------- | ----------------------------- | ------------------------------- | | **定義公式** | $h(x) = \begin{cases} 1, & x \geq 0 \\ 0, & x < 0 \end{cases}$ | $h(x) = \frac{1}{1 + e^{-x}}$ | $h(x) = \max(0, x)$ | | **輸出範圍** | 0 或 1(離散) | 0 到 1(連續) | 0 到 ∞(分段線性) | | **是否連續** | 不連續 | 連續 | 分段連續 | | **是否可微分** | 不可微(跳躍點) | 可微(但邊界斜率趨近 0) | 可微(x > 0 處斜率為 1,x ≤ 0 處導數為 0) | | **是否有非線性** | 僅是硬邏輯閾值分界 | 有非線性 | 有非線性 | | **梯度問題** | 無法傳遞梯度 | 容易梯度消失(輸入極大極小時導數趨近 0) | 無梯度消失問題,但有「死神經元」風險 | | **數學計算複雜度** | 非常簡單(邏輯判斷) | 較高(包含指數與除法運算) | 非常簡單(取最大值) | | **適合用途** | 早期感知器模型、邏輯閘模擬、回歸問題 | 輸出層、機率預測、分類問題 | 現代神經網路隱藏層的主力活化函數 | | **輸出可導性** | 不可導 | 可導 | 分段可導 | 優缺點比較如下: | 函數 | 優點 | 缺點 | | ----------- | ----------- | ----------------- | | **階梯函數** | 簡單、類似邏輯開關 | 不可微、無法訓練、無法表達細節變化 | | **Sigmoid** | 平滑、可微分、適合機率 | 易梯度消失、計算較慢 | | **ReLU** | 快速、效果好、易收斂 | 負區域會死神經元、不適用輸出層 | ### 2.1.1. 階梯函數 早期的基本活化函數「階梯函數」在輸入值超過某個門檻(通常是 0)時輸出 1,否則輸出 0。不連續,不可導,無梯度。 結構如下: $a = b + w_1 x_1 + w_2 x_2$ $y = h(a) \quad \text{,} \quad h(x) = \begin{cases} 0 & \text{,} x \leq 0 \\ 1 & \text{,} x > 0 \end{cases}$ 其中,$a$ 為加權總和,$h(x)$ 為活化函數,$y$ 為輸出。 基礎範例程式碼: ``` def step_function(x): if x > 0: return 1 else: return 0 ``` 由於 if 判斷僅適用於單個值,但神經網路通常是「一大批資料一起處理」,因此需要使用NumPy。 NumPy 陣列範例程式碼: ``` import numpy as np x = np.array([-1.0, 1.0, 2.0]) def step_function(x): y = x > 0 # 建立布林陣列 return y.astype(np.int) # 轉成 int 型態(True→1, False→0) ``` 繪製階梯函數: 當 x < 0 時 y = 0。當 x ≥ 0 時 y = 1。形成一個「階梯」的形狀。為非線性。 ``` import numpy as np import matplotlib.pylab as plt def step_function(x): return np.array(x > 0, dtype=np.int) x = np.arange(-5.0, 5.0, 0.1) y = step_function(x) plt.plot(x, y) plt.ylim(-0.1, 1.1) plt.show() ``` ### 2.1.2. sigmoid 函數 Sigmoid 函數是一個 S 型曲線,輸入值範圍為實數(−∞ ~ +∞),輸出值介於 0 到 1 之間,可以視為「機率」的轉換器,將任意輸入壓縮到 0~1 的範圍內,並且輸出的總合為 1。 特性如下: | 特性 | 說明 | | ---------- | ----------------------------------------------------------- | | **平滑連續** | 輸出值隨輸入平滑變化,無跳躍 | | **可微分** | 可進行微分(對梯度下降有幫助) | | **輸出範圍** | 介於 $0$ 到 $1$ 之間 | | **中心非對稱** | 不以 $0$ 為中心(但 Tanh 是) | | **漸近行為** | $x \to \infty \Rightarrow 1$, $x \to -\infty \Rightarrow 0$ | | **容易梯度消失** | 當輸入太大或太小時,導數接近 0,學習變慢 | 結構如下: $h(x) = \frac{1}{1 + \exp(-x)}$ 範例程式碼: numpy 的廣播機制使 sigmoid(x) 會自動針對每個元素計算對應的 sigmoid 值,不需寫迴圈。 ``` import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) x = np.array([-1.0, 1.0, 2.0]) sigmoid(x) # 預期輸出:array([0.2689, 0.7310, 0.8808]) # 表示:h(−1.0)≈0.2689$,$h(1.0)≈0.7310$,$h(2.0)≈0.8808 ``` 繪製 sigmoid 函數: x = 0 時 y ≈ 0.5,x 趨近 +∞ 時 y 趨近 1,x 趨近 −∞ 時 y 趨近 0。為一條平滑的 S 型曲線,非線性。 ``` import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) x = np.arange(-5.0, 5.0, 0.1) # 建立 x 資料(從 -5 到 5,間隔 0.1) y = sigmoid(x) # 計算對應的 y 值 plt.plot(x, y) plt.ylim(-0.1, 1.1) # 設定 y 軸顯示範圍 plt.title("Sigmoid Function") plt.xlabel("x") plt.ylabel("sigmoid(x)") plt.grid(True) plt.show() ``` #### 2.1.2.1. softmax 函數的溢位問題: 電腦處理數字時,會將數值限制在 4 位元組或 8 位元組的有效範圍中,產生無法表示超大數值的問題,稱作溢位。 執行 softmax 函數時,會進行指數函數的運算。若指數運算後的數值結果很大,後續進行除法運算時,數值會變得「不穩定」。 將 softmax 改良後與原結果一致,改良算式為: $y_k = \frac{\exp(a_k)}{\sum_{i=1}^{n} \exp(a_i)} = \frac{C \cdot \exp(a_k)}{\sum_{i=1}^{n} C \cdot \exp(a_i)} = \frac{\exp(a_k + \log C)}{\sum_{i=1}^{n} \exp(a_i + \log C)} = \frac{\exp(a_k + C')}{\sum_{i=1}^{n} \exp(a_i + C')} \quad$ 範例程式碼如下: ``` >>> a = np.array([1010, 1000, 990]) >>> np.exp(a) / np.sum(np.exp(a)) # 計算 softmax 函數 array([ nan, nan, nan]) # 無法正確計算 >>> c = np.max(a) # 1010 >>> a - c array([ 0, -10, -20]) >>> np.exp(a - c) / np.sum(np.exp(a - c)) array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09]) ``` ``` def softmax(a): c = np.max(a) # 最大值 exp_a = np.exp(a - c) # 防範溢位 sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y ``` ### 2.1.3. ReLU 函數 ReLU 函數像一個「閘門」,只讓正值通過,負值全部變成 0。輸入大於 0 則原值輸出。輸入小於等於 0 則輸出 0。輸出範圍為 $0 \sim \infty$。非線性、簡單、計算快。 結構如下: $h(x) = \begin{cases} x & \text{if } x > 0 \\ 0 & \text{if } x \leq 0 \end{cases}$ 範例程式碼: 使用 NumPy 的 maximum() 函數對陣列中每個元素與 0 比較,選擇較大的輸出,達到 ReLU 的效果。 ``` def relu(x): return np.maximum(0, x) ``` ## 2.2. 神經網路中的隱藏層與陣列運算 在神經網路中,輸入與權重經由陣列運算後,加上偏移值產生加權總和,再經由活化函數傳遞給下一層。神經網路可設計多層。 實作三層神經網路程式碼如下: ``` import numpy as np # 活化函數:sigmoid def sigmoid(x): return 1 / (1 + np.exp(-x)) # 活化函數:恆等函數(identity function) def identity_function(x): return x # 初始化神經網路的參數(權重與偏移) def init_network(): network = {} network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) network['b1'] = np.array([0.1, 0.2, 0.3]) network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) network['b2'] = np.array([0.1, 0.2]) network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]]) network['b3'] = np.array([0.1, 0.2]) return network # 前向傳播函數 def forward(network, x): W1, W2, W3 = network['W1'], network['W2'], network['W3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 z2 = sigmoid(a2) a3 = np.dot(z2, W3) + b3 y = identity_function(a3) return y # 測試執行 network = init_network() x = np.array([1.0, 0.5]) y = forward(network, x) print(y) # 預期輸出:[0.31682708 0.69627909] # 此範例使用恆等函數作為輸出層的活化函數 ``` ## 2.3. 神經網路中的輸出層 神經網路可以用來解決分類問題與迴歸問題。在輸出層,迴歸問題要使用恆等函數,而分類問題使用的是 softmax 函數。 輸出層的神經元數量根據要解決的問題而定。在分類問題中,會將輸出層的神經元數量設定為想分類的類別數量。例如,針對某個輸入影像,預測該影像是數字 0 到 9 的哪一個;這種 10 個類別分類的問題,會把輸出層的神經元設定為 10 個。並以其中機率最大的神經經元作為分類結果。 # 3. 神經網路的推論 使用神經網路時通常分成兩個步驟:「學習」與「推論」。學習時會準備訓練資料進行權重參數調整。推論則是使用學習過的參數,又稱作「推論處理」或「正向傳播(forward propagation)」。 ## 3.1. 推論流程 MNIST 資料集是經典的手寫數字辨識資料集,廣泛用於圖像分類與神經網路的入門教學。透過將 28×28 的影像攤平成一維向量,可以作為神經網路的輸入。其特性如下: (1). 每筆資料為一張 28×28 灰階影像,代表手寫數字(0~9)。 (2). 每張影像包含 784 個像素點($28 \times 28 = 784$)。 (3). 資料共分為:訓練集 60,000 筆與測試集 10,000 筆。 本例中使用的前饋式神經網路由三層組成,目的為進行十分類,輸出層的每一個節點對應一個數字類別,輸出為機率分布。 | 層級 | 神經元數量 | 說明 | | ----- | ----- | -------------------------------- | | 輸入層 | 784 | 將 $28 \times 28$ 影像攤平成 784 維向量 | | 隱藏層 1 | 50 | 使用 sigmoid 活化函數 | | 隱藏層 2 | 100 | 使用 sigmoid 活化函數 | | 輸出層 | 10 | 使用 softmax,對應數字 0\~9 分類結果 | 資料流示意如下: 輸入 (784 維) → W1 (784×50) → sigmoid → W2 (50×100) → sigmoid → W3 (100×10) → softmax → 預測結果 (10 維) ### 3.1.1. 資料與模型初始化 ``` def get_data(): (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False) return x_test, t_test ``` normalize = True:將影像像素值從 $[0, 255]$ 轉換至 $[0.0, 1.0]$,提升數值穩定性(資料正規化)。 flatten = True:將 $28 \times 28$ 的影像攤平成 $784$ 維向量。 one_hot_label = False:標籤為整數類別(非 one-hot 編碼)。 ``` def init_network(): with open("sample_weight.pkl", 'rb') as f: network = pickle.load(f) return network ``` 載入已訓練完成的權重與偏差參數,其中,$w1, w2, w3$ 為三層全連接層的權重矩陣。$b1, b2, b3$ 為各層對應的偏差向量。並使用 pickle.load() 反序列化儲存的 Python 物件。 ### 3.1.2. 單筆資料推論流程 ``` def predict(network, x): W1, W2, W3 = network['W1'], network['W2'], network['W3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 z2 = sigmoid(a2) a3 = np.dot(z2, W3) + b3 y = softmax(a3) return y ``` 輸入層:a1 = x · W1 + b1 → z1 = sigmoid(a1)。 隱藏層一:a2 = z1 · W2 + b2 → z2 = sigmoid(a2)。 隱藏層二:a3 = z2 · W3 + b3 → y = softmax(a3)。 輸出層:回傳為長度為 10 的機率向量 $y$,代表輸入影像屬於各個數字的機率。 ## 3.2. 推論準確度計算 完成神經網路的前向傳播後,需比對模型預測與真實標籤,評估分類準確度。 ``` accuracy_cnt = 0 for i in range(len(x)): y = predict(network, x[i]) p = np.argmax(y) # y 為 10 維機率向量 if p == t[i]: accuracy_cnt += 1 # 與實際標籤 t[i] 比對是否一致 print("Accuracy:" + str(float(accuracy_cnt) / len(x))) # 輸出:Accuracy: 0.9352 ``` np.argmax(y):回傳最大值的索引,即預測數字。 accuracy_cnt:累計正確預測的數量。 len(x):測試資料總筆數。 ## 3.3. 批次處理 批次處理可使用矩陣運算一次完成多筆推論,有效利用 CPU / GPU 向量化資源。 首先,矩陣維度必須對齊,範例如下: (1). 輸入矩陣 $X$:維度為 $(N, 784)$ (2). 權重矩陣不變:$W_1 \in \mathbb{R}^{784 \times 50}$,$W_2 \in \mathbb{R}^{50 \times 100}$,$W_3 \in \mathbb{R}^{100 \times 10}$ (3). 經過矩陣運算後,輸出矩陣 $Y$ 的維度為 $(N, 10)$ 範例程式碼如下: ``` batch_size = 100 accuracy_cnt = 0 for i in range(0, len(x), batch_size): x_batch = x[i:i+batch_size] # 取出一個批次資料 y_batch = predict(network, x_batch) # 對整批資料進行推論 p = np.argmax(y_batch, axis=1) # 找出每筆資料的預測類別 accuracy_cnt += np.sum(p == t[i:i+batch_size]) # 比對正確預測數量並累加 ``` | 程式段落 | 說明 | | ------------------------------ | ------------------------- | | `range(0, len(x), batch_size)` | 每次步進一個 batch\_size,確保分批處理 | | `x[i:i+batch_size]` | 使用切片方式取出連續的 \$N\$ 筆輸入資料 | | `np.argmax(..., axis=1)` | 對每筆資料的機率分布取最大值的索引 | | `p == t[i:i+batch_size]` | 比對預測結果與真實標籤,產生布林陣列 | | `np.sum(...)` | 計算該批次中預測正確的筆數 | # 4. 神經網路的學習 神經網路的「學習」透過訓練資料自動調整網路中的權重參數,使模型的預測結果能夠更接近正確答案,目的是最小化預測誤差。 特性如下: (1). 以損失函數為依據:衡量預測與真實答案的差距。 (2). 自動化調整權重參數:根據損失函數的梯度方向來微調網路中的權重。 (3). 最終目標:找出使損失函數值最小的權重組合。 傳統機器學習與神經網路的差異在於是否依賴人為設計的特徵。比較如下: | 方法 | 特徵提取方式 | 特徵輸入分類器 | 特點 | | ----------------- | ---------- | ----------------- | ---------------- | | 傳統機器學習(SIFT, HOG) | 人工設計特徵抽取器 | SVM、KNN 等分類器 | 特徵須由人定義,對資料特性要求高 | | 深度學習(神經網路) | 自動從資料中學習特徵 | end-to-end 模型整體學習 | 特徵與分類器一體化,自動化程度高 | ## 4.1. 損失函數 神經網路模型的學習目標是「使預測結果更接近正確答案」,需要使用一個可以量化誤差的指標,即為損失函數。 雖然準確率可以評估預測結果的對錯,但準確率不連續、不可微分,無法用來計算梯度、更新參數。 損失函數的優點如下: (1). 數值連續:隨著模型輸出變動,損失值也會平滑變動。 (2). 可微分性:可用於導數計算,利於使用梯度下降法更新權重。 (3). 提供學習方向:損失值越大,代表模型預測越差,有明確的優化目標。 ### 4.1.1. 均方誤差(Mean Squared Error, MSE) MSE 適用於回歸任務,衡量預測值與實際值的平方差。 算式:$E = \frac{1}{2} \sum_k (y_k - t_k)^2$ 其中,$y_k$ 為預測值,$t_k$ 為正確答案。 範例程式碼如下: ``` def mean_squared_error(y, t): return 0.5 * np.sum((y - t) ** 2) ``` ### 4.1.2. 交叉熵誤差(Cross Entropy Error) Cross Entropy Error 常用於分類任務,特別是搭配 softmax 輸出與 one-hot 標記的場景。 算式:$E = - \sum_k t_k \log y_k$ 其中,$y_k$ 為模型對第 $k$ 類的預測機率,$t_k$ 為 one-hot 編碼下的正確類別。 log 函數對低機率有較高懲罰,當 $y_k$ 趨近 0 時,$\log y_k$ 會趨近 $-\infty$,損失值會變得非常大,有助於模型避免低機率錯誤。 範例程式碼如下: ``` def cross_entropy_error(y, t): delta = 1e-7 return -np.sum(t * np.log(y + delta)) ``` ### 4.2.3. 小批次版的交叉熵 實務上會使用 mini-batch 訓練需計算一批資料的平均損失。 算式:$E = -\frac{1}{n} \sum_{k=1}^{n} \sum_{j} t_{kj} \log y_{kj}$ 範例程式碼如下: ``` def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1, y.size) batch_size = y.shape[0] return -np.sum(t * np.log(y + 1e-7)) / batch_size ``` ## 4.2. 梯度 在某個權重參數的點上,損失函數值對權重參數的變化率稱為「梯度」。梯度為向量,透過計算某個權重參數點上的損失的導數,進而得知調整方向。 導數算式:$\frac{df(x)}{dx} = \lim_{h \to 0} \frac{f(x + h) - f(x)}{h}$ 表示當 $h$ 趨近於 0 時,函數在 $x$ 處的變化率。 ### 4.2.1. 數值微分 數值微分是近似於計算微分的方法。真實微分與近似切線(中心差分)相比更接近函數實際斜率。 單點微分(前向差分): ``` def numerical_diff(f, x): h = 1e-4 return (f(x + h) - f(x)) / h ``` 中心差分(較精確): ``` def numerical_diff(f, x): h = 1e-4 return (f(x + h) - f(x - h)) / (2 * h) ``` ### 4.2.2. 偏微分與多變數函數 當函數有多個輸入變數(如 $x_0, x_1$),需對每個變數分別微分,稱為偏微分。 範例函數:$f(x_0, x_1) = x_0^2 + x_1^2$ 對 $x_0$ 偏微分: ``` def function_tmp1(x0): return x0**2 + 4.0**2 numerical_diff(function_tmp1, 3.0) # 結果為 6 ``` 對 $x_1$ 偏微分: ``` def function_tmp2(x1): return 3.0**2 + x1**2 numerical_diff(function_tmp2, 4.0) # 結果為 8 ``` ### 4.2.3. 整體梯度向量計算 將每個變數的偏微分組合即為梯度向量: ``` def numerical_gradient(f, x): h = 1e-4 grad = np.zeros_like(x) for idx in range(x.size): tmp_val = x[idx] x[idx] = tmp_val + h fxh1 = f(x) x[idx] = tmp_val - h fxh2 = f(x) grad[idx] = (fxh1 - fxh2) / (2 * h) x[idx] = tmp_val # 還原值 return grad ``` 輸出為與 $x$ 相同形狀的向量,例如: ``` numerical_gradient(function_2, np.array([3.0, 4.0])) # 結果為 [6.0, 8.0] # 表示在 x_0 = 3, x_1 = 4 的位置上,損失函數對每個變數的偏導數 ```