Policy Gradient
Gradient chính sách
Robot cần điều khiển cánh tay (góc xoay 0–360 độ — liên tục). Q-Learning cần discretize thành 36 bins → mất độ chính xác. Có cách nào tốt hơn?
Hình minh họa
Agent bắt đầu ở ô 0, cần đến ô 5. Mỗi bước sai trừ 0.04, chạm goal được +1. Bấm Take Action để agent đi một bước theo π(a|s). Bấm Chạy episode để agent tự đi tối đa 20 bước. Sau đó bấm Cập nhật policy để REINFORCE chạy gradient ascent — bạn sẽ thấy phân phối π dịch chuyển.
Trajectory hiện tại
0 bước · R=0.00
Vị trí: 0 · π greedy: right
Tổng episodes
0
Chưa có episode nào — hãy "Chạy episode" rồi "Cập nhật policy".
Q-Learning: học bản đồ giá trị rồi suy ra đường đi. Policy Gradient: học trực tiếp cách đi. Giống học lái xe: Q-Learning = học giá trị mỗi ngã tư rồi tính đường. PG = học trực tiếp phản xạ (bao nhiêu độ, nhanh/chậm). PG tự nhiên hơn cho hành động liên tục!
REINFORCE có vấn đề: variance cao vì dùng toàn bộ episode return. Cách đơn giản nhất để giảm variance?
Chính sách hội tụ về π(right)=0.98. Bạn có nên xoá hẳn p(left) = 0 không?
Giải thích
Policy Gradient tối ưu trực tiếp policy bằng gradient ascent trên expected return. Trái ngược với Q-Learning (học value rồi suy ra policy), PG học policy trực tiếp. Kết hợp với một critic ta được Actor-Critic, và biến thể PPO/GRPO hiện là xương sống của RLHF cho các mô hình ngôn ngữ.
Policy Gradient Theorem:
REINFORCE với baseline:
Advantage function là lựa chọn baseline phổ biến nhất:
-1: đặt loss = , rồi gọi loss.backward(). Gradient descent trên loss tương đương gradient ascent trên reward.import torch
import torch.nn as nn
import torch.distributions as dist
class PolicyNetwork(nn.Module):
def __init__(self, state_dim: int, action_dim: int):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128), nn.ReLU(),
nn.Linear(128, 128), nn.ReLU(),
nn.Linear(128, action_dim),
)
def forward(self, state: torch.Tensor) -> torch.Tensor:
logits = self.net(state)
return torch.softmax(logits, dim=-1)
def train(env, episodes: int = 1000, gamma: float = 0.99, lr: float = 3e-4):
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
policy = PolicyNetwork(state_dim, action_dim)
optim = torch.optim.Adam(policy.parameters(), lr=lr)
for ep in range(episodes):
state, _ = env.reset()
log_probs, rewards = [], []
done = False
while not done:
s = torch.as_tensor(state, dtype=torch.float32)
probs = policy(s)
d = dist.Categorical(probs)
a = d.sample()
log_probs.append(d.log_prob(a))
state, r, terminated, truncated, _ = env.step(a.item())
rewards.append(r)
done = terminated or truncated
# Tính discounted returns
returns, G = [], 0.0
for r in reversed(rewards):
G = r + gamma * G
returns.insert(0, G)
returns_t = torch.tensor(returns, dtype=torch.float32)
# Baseline subtraction: trừ mean + chuẩn hoá → variance reduction
returns_t = (returns_t - returns_t.mean()) / (returns_t.std() + 1e-8)
# Policy gradient loss — dấu âm vì ta gradient-DESCENT trên -J
loss = -(torch.stack(log_probs) * returns_t).sum()
optim.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(policy.parameters(), 1.0)
optim.step()
if ep % 50 == 0:
print(f"ep={ep:4d} return={sum(rewards):7.2f} "
f"len={len(rewards):3d} grad_norm_clipped=1.0")import numpy as np
def run_episode(policy, corridor_len=11, goal=5, max_steps=20):
pos, traj = 0, []
for _ in range(max_steps):
a = np.random.choice(2, p=policy) # 0=left, 1=right
new_pos = max(0, pos - 1) if a == 0 else min(corridor_len - 1, pos + 1)
r = 1.0 if new_pos == goal else -0.04
traj.append((pos, a, r))
pos = new_pos
if pos == goal:
break
return traj
def reinforce_update(policy, traj, lr=0.35, gamma=0.95):
# Tính return từng step
G, returns = 0.0, []
for _, _, r in reversed(traj):
G = r + gamma * G
returns.insert(0, G)
returns = np.array(returns)
advantages = returns - returns.mean() # baseline = mean
p = policy.copy()
for (_, a, _), adv in zip(traj, advantages):
# Gradient của log-softmax 2 lớp
p[a] += lr * adv * (1 - p[a])
p[1-a] -= lr * adv * p[1-a]
p = np.clip(p, 0.02, 0.98)
p /= p.sum()
return p
policy = np.array([0.5, 0.5])
for ep in range(200):
traj = run_episode(policy)
policy = reinforce_update(policy, traj)
if ep % 20 == 0:
print(f"ep={ep:3d} π(right)={policy[1]:.3f} steps={len(traj)}")Vì sao gọi là "log-prob trick"? Ta biến gradient của một kỳ vọng (khó tính) thành kỳ vọng của một gradient (sample-able). Điểm mấu chốt:
Nhờ đó ta có thể sample action theo policy, lấy gradient của log-prob rồi cân bằng bằng weight (chính là return hoặc advantage). Đây là cách duy nhất để huấn luyện khi environment không khả vi — tức gần như mọi bài toán RL thực tế (game engines, simulators vật lý, LLM decoding, ...).
Khi nào Policy Gradient thất bại? Khi reward cực kỳ thưa thớt (ví dụ Montezuma's Revenge — phần thưởng chỉ xuất hiện sau hàng trăm bước), mean return ≈ 0, advantage cũng ≈ 0, và policy không có tín hiệu để học. Giải pháp: thêm intrinsic reward (curiosity), hierarchical RL, hoặc imitation learning để bootstrap.
Liên hệ với Maximum Likelihood: nếu bạn gán weight cho mỗi trajectory theo return của nó và cực đại hoá log-likelihood có trọng số, bạn sẽ ra đúng công thức REINFORCE. Đây là góc nhìn "RL = weighted supervised learning" mà nhiều paper RLHF khai thác (ví dụ RWR, AWR, DPO đều dựa trên ý tưởng này).
Tại sao không dùng thẳng gradient của reward? Reward thường không khả vi theo — environment là hộp đen. Ta chỉ quan sát samples chứ không có . Policy Gradient Theorem là mẹo toán để đẩy gradient vào trong kỳ vọng: . Mẹo này còn gọi là "log-derivative trick" hoặc "score function estimator" — xuất hiện ở khắp nơi: variational inference, reparameterization trick, REBAR, Gumbel-softmax...
Gợi ý đọc thêm (pháp lý tinh thần): Sutton & Barto chương 13, Spinning Up của OpenAI, và paper gốc PPO. Với LLM: InstructGPT paper, DeepSeek-R1 technical report, và "Secrets of RLHF" series. Nếu thích thực hành, CleanRL (repo của Shengyi Huang) có các file single-file cho REINFORCE, PPO, GRPO — đọc một lần hiểu toàn bộ pipeline.
Công thức bạn nên thuộc lòng: . Mọi thuật toán PG hiện đại (A2C, PPO, SAC-discrete, GRPO) đều bắt đầu từ đây rồi chỉ thay đổi cách ước lượng advantage và cách giới hạn bước update.
- Policy Gradient học TRỰC TIẾP π(a|s), không qua Q-value — tự nhiên cho action liên tục.
- REINFORCE: sample episode → tính return G_t → update θ theo ∇ log π · G_t.
- Baseline subtraction: trừ mean return, giảm variance 50–90% mà KHÔNG thay đổi expected gradient.
- On-policy: mỗi gradient step chỉ dùng dữ liệu từ policy hiện tại — kém sample-efficient hơn off-policy (DQN).
- Actor-Critic: dùng Critic V_φ(s) làm baseline — biến REINFORCE thành A2C/PPO, ổn định hơn nhiều.
- PPO/GRPO (với clipping và trust-region) là xương sống của RLHF hiện đại cho ChatGPT, Claude, Gemini.
Kiểm tra hiểu biết
Policy Gradient khác Q-Learning ở điểm nào cơ bản?