PID 控制器
🔥 Vibe Prompt
"模擬 PID 溫度控制。目標溫度 100°C。調整 Kp、Ki、Kd 來最小化超調量。"
import numpy as np
class PID:
def __init__(self, Kp, Ki, Kd, setpoint):
self.Kp, self.Ki, self.Kd = Kp, Ki, Kd
self.setpoint = setpoint
self.prev_error = 0
self.integral = 0
def update(self, measurement, dt):
error = self.setpoint - measurement
self.integral += error * dt
derivative = (error - self.prev_error) / dt
self.prev_error = error
return self.Kp*error + self.Ki*self.integral + self.Kd*derivative
pid = PID(Kp=2.0, Ki=0.5, Kd=1.0, setpoint=100)
temp = 25.0
dt = 0.01
time = 0
while time < 10:
output = pid.update(temp, dt)
temp += output * dt * 10
temp -= (temp - 25) * 0.01 * dt
if abs(pid.setpoint - temp) < 0.5 and time > 2:
print(f"在 t={time:.2f}s 時達到穩定溫度 {temp:.1f}°C")
break
time += dt
PID 控制原理
三個控制項的直覺理解
PID 控制器由三個部分組成,每個部分負責不同的控制任務:
| 項 | 名稱 | 直覺理解 | 影響 | |:--:|:----:|---------|------| | P | 比例項 (Proportional) | 「現在有多遠?」— 誤差越大,修正力道越大 | 加快響應,但可能超調 | | I | 積分項 (Integral) | 「過去累積了多少?」— 消除長期誤差 | 消除穩態誤差,但可能不穩定 | | D | 微分項 (Derivative) | 「未來會如何?」— 預測誤差趨勢 | 減少超調,提升穩定性 |
$$u(t) = K_p \cdot e(t) + K_i \cdot \int_0^t e(\tau) d\tau + K_d \cdot \frac{de(t)}{dt}$$
其中 $e(t) = setpoint - measurement$ 是誤差。
PID 參數調整指南
各參數的影響
| 效果 | Kp ↑ | Ki ↑ | Kd ↑ | |:----:|:----:|:----:|:----:| | 上升時間 | ✅ 縮短 | — | 微小變化 | | 超調量 | ❌ 增加 | ❌ 增加 | ✅ 減少 | | 穩定時間 | — | ❌ 增加 | ✅ 減少 | | 穩態誤差 | ✅ 減少 | ✅ 消除 | 無影響 | | 穩定性 | ❌ 下降 | ❌ 下降 | ✅ 提升 |
Ziegler-Nichols 調整法
這是最經典的 PID 參數調整方法:
- 設定 Ki = 0, Kd = 0
- 增加 Kp 直到系統產生穩定振盪(記錄此時的 K_u 為最終增益)
- 測量振盪週期 P_u
- 根據下表設定參數:
| 控制器類型 | Kp | Ki | Kd | |:----------:|:--:|:--:|:--:| | 僅 P | 0.5 × K_u | — | — | | PI | 0.45 × K_u | 0.54 × K_u / P_u | — | | PID | 0.60 × K_u | 1.20 × K_u / P_u | 0.075 × K_u × P_u |
實戰:溫度控制模擬
import numpy as np
import matplotlib.pyplot as plt
class PID:
def __init__(self, Kp, Ki, Kd, setpoint):
self.Kp, self.Ki, self.Kd = Kp, Ki, Kd
self.setpoint = setpoint
self.prev_error = 0
self.integral = 0
def update(self, measurement, dt):
error = self.setpoint - measurement
self.integral += error * dt
derivative = (error - self.prev_error) / dt
self.prev_error = error
return self.Kp*error + self.Ki*self.integral + self.Kd*derivative
# 測試不同參數組合
configs = [
("預設", PID(Kp=2.0, Ki=0.5, Kd=1.0, 目標=100)),
("激進", PID(Kp=5.0, Ki=2.0, Kd=0.5, 目標=100)),
("保守", PID(Kp=0.5, Ki=0.1, Kd=2.0, 目標=100)),
]
plt.figure(figsize=(12, 6))
for name, pid in configs:
temp = 25.0
temps = [temp]
times = [0]
dt = 0.01
t = 0
while t < 10:
output = pid.update(temp, dt)
temp += output * dt * 10
temp -= (temp - 25) * 0.01 * dt
t += dt
temps.append(temp)
times.append(t)
overshoot = (max(temps) - 100) / 100 * 100
steady_error = abs(temps[-1] - 100)
print(f"{name}: 超調={overshoot:.1f}%, 穩態誤差={steady_error:.2f}°C")
plt.plot(times, temps, label=f"{name}", linewidth=2)
plt.axhline(y=100, color='r', linestyle='--', label='目標溫度')
plt.xlabel('時間 (秒)')
plt.ylabel('溫度 (°C)')
plt.title('PID 溫度控制模擬')
plt.legend()
plt.grid(True)
plt.show()
PID 的應用場景
| 領域 | 應用 | |:----:|------| | 🚁 無人機 | 飛控穩定、懸停、航點追蹤 | | 🤖 機器人 | 馬達位置/速度控制、機械臂精確定位 | | 🏭 工業 | 溫度/壓力/流量/液位控制 | | 🚗 汽車 | 定速巡航、電子穩定程式 | | 🔌 電源 | 開關電源穩壓、逆變器控制 | | 🌡️ 家電 | 空調溫度控制、烤箱恆溫 |
PID 的進階變體
| 變體 | 說明 | 適用場景 | |:----:|------|---------| | PI 控制器 | 去掉微分項 | 雜訊大的系統(Kd 會放大雜訊) | | PD 控制器 | 去掉積分項 | 不需要消除穩態誤差的系統 | | 串級 PID | 兩個 PID 串聯(內環+外環) | 無人機飛控(角度環+角速度環) | | 模糊 PID | 用模糊邏輯動態調整參數 | 高度非線性系統 | | 自適應 PID | 根據系統狀態自動調整 | 系統參數隨時間變化的場合 |
關鍵要點
- ✅ P(比例)根據當前誤差進行修正,是控制的基礎
- ✅ I(積分)消除長期累積的穩態誤差
- ✅ D(微分)預測誤差趨勢,減少超調與振盪
- ✅ Ziegler-Nichols 法是經典的 PID 參數調整方法
- ✅ 實際應用中需考慮抗飽和 (Anti-windup)、濾波器等機制
- ✅ PID 不需要系統模型,適用於絕大多數的控制場景
梯度下降實戰要點
梯度下降是機器學習中最核心的最佳化演算法。理解它的變體(SGD、Momentum、Adam)對於訓練深度學習模型至關重要。
核心概念
- 梯度方向是函數增加最快的方向
- 學習率決定每一步的大小
- SGD 用隨機樣本近似梯度
- Momentum 加速收斂
- Adam 結合 Momentum + RMSProp
PID 控制器:不用梯度的控制
PID(比例-積分-微分)是工業界最廣泛使用的控制演算法。它不需要梯度、不需要模型——只根據目標值和實際值的誤差來計算控制訊號。
| 項 | 作用 | |:---|:----| | P(比例) | 誤差越大,輸出越強 | | I(積分) | 累積過去誤差,消除穩態誤差 | | D(微分) | 預測未來,減少 overshoot |
跟梯度下降的比較
| 比較 | 梯度下降 | PID | |:----|:--------|:---| | 目標 | 最小化 loss | 追蹤目標值 | | 需要模型 | 需要可微分 | 不需要 | | 應用 | ML 訓練 | 機器人、無人機 |
下一章預告:卡爾曼濾波
從帶有雜訊的感測器資料中估計真實狀態。