Lesson 19 · 約 25 分鐘
RL 深入
Q-Learning → DQN / Policy gradient → PPO / 一條鏈到 RLHF + DPO
19 ・ RL 深入:Policy Gradient / PPO / DQN
Lesson 06 跑過 Q-learning,知道「state → action → reward」 那條主線。 但現代 RL = PPO + DQN + policy networks — Q-learning 只是 入口。這一講把 RL 的兩條主軸(value-based vs policy-based)講清楚, 加 PPO(GPT 對齊用)的核心想法 + DQN(AlphaGo 之父)的關鍵技巧。
0. RL 的二分法
1. 名詞速查
| 符號 | 中文 | 一句話 |
|---|---|---|
s |
state | 當下世界長什麼樣 |
a |
action | 你能做的選擇 |
r |
reward | 做完這個 action 拿到的分數 |
π(a|s) |
policy | 給 state,選哪個 action 的機率分布 |
V(s) |
state value | 從 s 開始,期望總分(走完一整 episode) |
Q(s,a) |
action value | s 下做 a,再之後最佳 policy 拿到的期望總分 |
A(s,a) = Q(s,a) - V(s) |
advantage | 做這 action 比平均好多少 |
γ |
discount factor | 未來 reward 折扣率(0.99 常見) |
episode |
一輪 | 從開始到 done 的一整段軌跡 |
2. Value-based: Q-Learning → DQN
2.1 Q-Learning(lesson 06 已跑過)
更新規則:
Q(s,a) ← Q(s,a) + α · [r + γ · max_a' Q(s', a') - Q(s,a)]
└─────── target ──────┘ └ current ┘
怎麼讀:「現在 Q 是 X,我這步觀察到的真實 reward + 折扣後的未來最佳 Q = Y,Y - X 是估計誤差,往 Y 移動一小步 α」。
致命缺點:Q 是 state × action 的 table,state 連續就爆炸(像 Atari 遊戲畫面 84×84×3 = 21168 個值,每個 256 可能值)。
2.2 DQN:把 Q 表變成神經網路
核心想法:用 NN 估計 Q(s,a) — Q_θ(s) 輸出每個 action 的 Q 值。
class DQN(nn.Module):
def __init__(self, n_actions):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(4, 32, 8, stride=4), nn.ReLU(),
nn.Conv2d(32, 64, 4, stride=2), nn.ReLU(),
nn.Conv2d(64, 64, 3, stride=1), nn.ReLU(),
nn.Flatten(),
nn.Linear(7*7*64, 512), nn.ReLU(),
nn.Linear(512, n_actions)
)
def forward(self, s):
return self.net(s) # → Q values for each action但naive DQN 訓練不穩定。論文(Mnih 2015)用了兩個關鍵 trick:
2.3 兩個救命 trick
Trick 1: Experience Replay Buffer
- 把
(s, a, r, s')存進大 buffer(~1M) - 每步從 buffer 隨機抽 batch 訓練
- 為什麼:連續轉移 highly correlated → 違反 SGD「樣本獨立」 假設 → 訓不穩。隨機抽就獨立化。
Trick 2: Target Network
- 維護兩個 NN:
Q_θ(online)+Q_θ⁻(target) - 用 target 算 max:
y = r + γ · max_a' Q_θ⁻(s', a') - 每 N steps(典型 1k-10k)把 θ 複製到 θ⁻
- 為什麼:沒這個 → target 跟 online 同時動 → 像追自己尾巴,發散
2.4 DQN 後續變體(直接用就好)
| 變體 | 改進什麼 |
|---|---|
| Double DQN | argmax 跟 target 用不同 network → 緩解 over-estimation |
| Dueling DQN | 把 Q 拆 V + A 兩個 head → 學更高效 |
| Prioritized Replay | 從 buffer 抽「TD error 大」 的優先訓 → 收斂快 |
| Rainbow | 上面 + 一堆組合 → SOTA value-based |
3. Policy-based: REINFORCE → A2C → PPO
3.1 REINFORCE(最樸素)
核心想法:直接學 π_θ(a|s)。如果某條 trajectory 累積 reward 高 → 把「使這條 trajectory 出現」的機率推大;反之推小。
梯度:
∇θ J(θ) = E[ ∇θ log π_θ(a|s) · R(τ) ]
直觀:好結果的 action,提高它的 log probability;爛結果壓低。
問題:variance 超大(一條軌跡的 reward 取決於整個未來)→ 訓得超慢。
3.2 Actor-Critic(A2C / A3C):加 baseline 降 variance
核心想法:用一個 V(s) 網路(critic)估計「平均 reward」,然後算 advantage:
A(s, a) = R(τ) - V(s)
∇θ J(θ) = E[ ∇θ log π_θ(a|s) · A(s,a) ]
直觀:做 advantage > 0 的 action(比平均好)就推大,advantage < 0 就壓小。
兩個 head 同時學:
- Actor =
π_θ(a|s),policy - Critic =
V_φ(s),value baseline
3.3 PPO(GPT 對齊用、業界主流)
PPO 解決 A2C 的「一步走太大」 問題:policy 更新得太猛 → 下次採樣的軌跡完全變樣 → 訓不穩。
Clipped objective:
L(θ) = E[ min(r_t(θ) · A_t, clip(r_t(θ), 1-ε, 1+ε) · A_t) ]
其中 r_t(θ) = π_θ(a|s) / π_θ_old(a|s) # 新 policy / 舊 policy 機率比
直觀:新 policy 機率比舊 policy 不能差太遠(±ε = 0.2),防止一步走太大。
# PPO 訓練核心(完整版用 stable-baselines3 / cleanrl)
for epoch in range(K_EPOCHS):
for batch in dataset:
ratio = new_log_prob - old_log_prob
ratio = torch.exp(ratio)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1-EPS, 1+EPS) * advantages
policy_loss = -torch.min(surr1, surr2).mean()
value_loss = ((values - returns) ** 2).mean()
entropy_bonus = -dist.entropy().mean()
loss = policy_loss + 0.5 * value_loss + 0.01 * entropy_bonus
loss.backward()
optimizer.step()3.4 PPO vs DQN 怎麼選
| DQN | PPO | |
|---|---|---|
| Action space | 離散(Atari、棋盤) | 連續 + 離散都行 |
| Sample efficiency | 高(replay buffer 多次用) | 低(on-policy,每批用一次就丟) |
| 訓練穩定性 | 中(需 replay + target net) | 高(clip 機制保護) |
| 用在 LLM RLHF | 不適合(action = 整個 vocab 太大) | 標配 |
| 機器人 / 自駕 | 不適合(連續 action) | 標配 |
4. 進階方向:超越 PPO
4.1 DPO(Direct Preference Optimization)
PPO 需要 reward model + actor + critic 三個 NN,工程複雜。DPO 證明你可以直接從偏好對齊:
L_DPO = -log σ(β · (log π(y_chosen|x) - log π(y_rejected|x)
- log π_ref(y_chosen|x) + log π_ref(y_rejected|x)))
不需要 reward model,不需要 RL loop。2024 後新 LLM 對齊很多直接 DPO。
4.2 GRPO(Group Relative Policy Optimization)
DeepSeek 的 R1 推理模型用的方法 — 不用 critic、直接從同一 prompt 的多個 response 之間做 advantage 估計。節省 ~50% 訓練算力,但需要 group sampling 設計。
4.3 從 RL 看 LLM 的學習方式
| LLM 階段 | RL 對應 |
|---|---|
| Pretraining | 自監督(非 RL) |
| SFT | 模仿學習(supervised, 非 RL) |
| RLHF (PPO) | Actor-Critic RL |
| DPO | 直接從偏好,不走 RL loop |
| GRPO / R1-style | RL with group-relative reward |
5. Hands-on:挑一個目標
# 30 行學會 CartPole(用 cleanrl 風格)
import gymnasium as gym
from stable_baselines3 import PPO
env = gym.make("CartPole-v1")
model = PPO("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=10_000)
# 跑 demo
obs, _ = env.reset()
for _ in range(1000):
action, _ = model.predict(obs)
obs, _, done, _, _ = env.step(action)
if done: obs, _ = env.reset()6. 推薦資源
- OpenAI Spinning Up(英文,免費)— RL 最好的單一 reference
- CleanRL(GitHub)— PPO / DQN 等核心算法的 single-file 實作,讀程式碼學算法
- Sutton & Barto《Reinforcement Learning: An Introduction》(免費 PDF)— 經典教科書
- 論文:〈Playing Atari with Deep RL〉(DQN)、〈Proximal Policy Optimization〉(PPO)、〈Direct Preference Optimization〉(DPO)、〈DeepSeek-R1〉(GRPO)
7. 自我檢測
接下來
- 下一講 20 · 生成模型統一視角 — VAE / GAN / Diffusion 怎麼互相比較
- 想動手:跑 §5 的 CartPole,然後改成 cleanrl 的單檔 PPO 自己讀懂每一行