PyTorch 優化器
## 1. PyTorch 優化器的用途
神經網路訓練時,通常會先計算損失函數:
L = loss\(y, e\)
接著利用反向傳播取得每個參數的梯度:
G = backward\(L\)
優化器則根據梯度更新模型參數:
optimizer\(G, LR\)
其中:
- G:損失對參數的梯度
- LR:學習率 learning rate
在 PyTorch 中,優化器主要負責:
1. 儲存模型參數
2. 根據梯度更新參數
3. 清除上一輪累積的梯度
4. 可選擇使用 momentum、weight decay 等機制
---
## 2. SGD 與 Adam 比較
### SGD
SGD,Stochastic Gradient Descent,PyTorch 中常見的 SGD 會搭配 momentum:
```python
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.1,
momentum=0.9
)
```
#### 優點
- 演算法簡單
- 計算量較低
- 適當調整學習率後,泛化能力常常不錯
- 訓練大型模型時常被使用
#### 缺點
- 對學習率較敏感
- 可能在狹長或複雜的損失曲面上收斂較慢
- 通常需要手動調整 learning rate
---
### Adam
Adam 結合了 momentum 與自適應學習率,會為每個參數估計梯度的一階與二階動量。
```python
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001
)
```
#### 優點
- 通常收斂速度較快
- 對初始學習率比較不敏感
- 適合快速建立模型與處理稀疏梯度
- 常作為深度學習的預設選擇
#### 缺點
- 每個參數需要額外儲存動量資訊,因此記憶體使用量較高
- 有些任務中泛化能力不如調整良好的 SGD
- 不代表一定比 SGD 找到更好的最終模型
### 簡單比較
| 特性 | SGD | Adam |
|---|---|---|
| 更新方式 | 固定學習率搭配梯度 | 自適應調整各參數步長 |
| 常用 learning rate | `0.01 ~ 0.1` | `0.0001 ~ 0.001` |
| 收斂速度 | 通常較慢 | 通常較快 |
| 對學習率敏感度 | 較高 | 較低 |
| 額外記憶體 | 少 | 較多 |
| 常見用途 | 最終精調、影像分類 | 快速實驗、一般深度模型 |
---
## 3. PyTorch 如何使用優化器與學習率
一個典型的訓練流程如下:
```python
for x, y in dataloader:
optimizer.zero_grad() # 清除上一輪梯度
output = model(x) # 前向傳播
loss = loss_fn(output, y)
loss.backward() # 反向傳播,計算梯度
optimizer.step() # 根據梯度更新參數
```
### 為什麼要呼叫 `zero_grad()`?
PyTorch 預設會累積梯度。如果不清除上一輪的梯度,新的梯度會加到舊梯度上:
```python
optimizer.zero_grad()
```
### `learning rate` 的作用
學習率決定每次參數更新的幅度:
- 太大:可能震盪,甚至無法收斂
- 太小:訓練很慢,可能停在不佳的位置
- 合適:損失穩定下降
可以在訓練過程中使用 scheduler 動態調整學習率:
```python
scheduler = torch.optim.lr_scheduler.StepLR(
optimizer,
step_size=100,
gamma=0.5
)
```
每個 epoch 結束後呼叫:
```python
scheduler.step()
```
這會讓學習率每 100 個 epoch 乘以 `0.5`。
---
## 4. 使用 PyTorch 訓練簡單神經網路
以下範例使用神經網路學習 XOR 分類問題:
| 輸入 | 標籤 |
|---|---|
| `[0, 0]` | 0 |
| `[0, 1]` | 1 |
| `[1, 0]` | 1 |
| `[1, 1]` | 0 |
XOR 不是線性可分,因此需要隱藏層。
```python
import torch
import torch.nn as nn
# 固定隨機種子,方便重現結果
torch.manual_seed(42)
# XOR 訓練資料
X = torch.tensor([
[0.0, 0.0],
[0.0, 1.0],
[1.0, 0.0],
[1.0, 1.0]
])
y = torch.tensor([
[0.0],
[1.0],
[1.0],
[0.0]
])
# 建立簡單的多層感知器
model = nn.Sequential(
nn.Linear(2, 8),
nn.ReLU(),
nn.Linear(8, 1)
)
# BCEWithLogitsLoss 內部會處理 sigmoid
loss_fn = nn.BCEWithLogitsLoss()
# 使用 Adam 優化器
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.05
)
# 訓練模型
for epoch in range(2000):
# 1. 清除上一輪累積的梯度
optimizer.zero_grad()
# 2. 前向傳播
logits = model(X)
# 3. 計算損失
loss = loss_fn(logits, y)
# 4. 反向傳播
loss.backward()
# 5. 更新模型參數
optimizer.step()
if (epoch + 1) % 200 == 0:
print(f"Epoch {epoch + 1}, Loss: {loss.item():.4f}")
# 測試模型
with torch.no_grad():
logits = model(X)
probabilities = torch.sigmoid(logits)
predictions = (probabilities >= 0.5).float()
print("\n預測機率:")
print(probabilities)
print("\n預測結果:")
print(predictions)
print("\n正確答案:")
print(y)
```
可能得到類似結果:
```text
預測結果:
tensor([[0.],
[1.],
[1.],
[0.]])
```
---
## 5. 將 Adam 改成 SGD
只需替換優化器:
```python
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.5,
momentum=0.9
)
```
相較於 Adam,SGD 通常需要較仔細地尋找合適的 `lr`。例如可以嘗試:
```python
lr=0.1
lr=0.5
lr=1.0
```
而 Adam 常見的起始值則是:
```python
lr=0.001
```
---
## 6. 完整訓練概念
整個流程可以概括為:
```text
建立模型
↓
指定損失函數
↓
將模型參數交給優化器
↓
前向傳播
↓
計算損失
↓
反向傳播
↓
優化器依照 learning rate 更新參數
↓
重複多個 epoch
```
實務上可以先使用 Adam 快速確認模型與資料流程是否正確;如果需要更好的最終泛化表現,再嘗試 SGD、調整 learning rate,或搭配 learning rate scheduler。
相關學習地圖、教學課程
Python 人工智慧