---
# System prepended metadata

title: 【Deep Learning 1 - L1 ~ 4】感知器與神經網路

---

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 的位置上，損失函數對每個變數的偏導數
```




