1. 为什么要直接优化策略?

Value-based 方法(如 DQN)虽然强大,但有局限:

  1. 无法处理连续动作:如果动作是“方向盘转动角度(0度到360度)”,Q-learning 需要求 $\arg\max_a Q(s, a)$,这在连续空间是很难的。
  2. 无法学习随机策略:Value-based 最终给出的通常是确定性策略(选 Q 最大的)。但在石头剪刀布游戏中,最好的策略是随机的。

Policy Gradient (PG) 方法直接对策略 $\pi(a|s, \theta)$ 进行参数化(比如用神经网络),然后通过调整 $\theta$ 来最大化期望回报 $J(\theta)$。


2. 策略梯度定理 (Policy Gradient Theorem)

我们想最大化目标函数 $J(\theta) = \mathbb{E}[G_0]$。
我们需要求梯度 $\nabla J(\theta)$ 来进行梯度上升(Gradient Ascent)。

神奇的 Policy Gradient Theorem 告诉我们:
$$ \nabla J(\theta) \propto \sum_{s} d_\pi(s) \sum_{a} q_\pi(s, a) \nabla_\theta \pi(a|s, \theta) $$

利用 Log-Derivative Trick ($\nabla \pi = \pi \nabla \log \pi$),可以写成期望形式:
$$ \nabla J(\theta) = \mathbb{E}{\pi} [ \nabla\theta \ln \pi(A_t | S_t, \theta) \cdot Q_\pi(S_t, A_t) ] $$

直观解释

  • $\nabla \ln \pi$:告诉我们如何调整参数能让动作 $A_t$ 的概率变大。
  • $Q(S_t, A_t)$:这个动作好不好?
  • 乘起来:如果动作好(Q大),就多增加它的概率;如果不好(Q小),就少增加甚至减少(如果 Q 是负的)。

3. REINFORCE 算法

REINFORCE 是最基础的 PG 算法。它使用 Monte Carlo 方法来估计 $Q(S_t, A_t)$,即直接用实际回报 $G_t$ 代替 $Q$。

更新公式:
$$ \theta \leftarrow \theta + \alpha \gamma^t G_t \nabla_\theta \ln \pi(A_t | S_t, \theta) $$

3.1 缺点

由于 $G_t$ 是单次采样的回报,方差极大。这导致 REINFORCE 训练非常不稳定。
改进:引入 Baseline,减去一个基线 $b(s)$(通常是 $V(s)$),变成 $G_t - V(S_t)$,这不改变期望但能显著降低方差。


4. PyTorch 代码实战:REINFORCE

我们用 PyTorch 实现一个简单的 REINFORCE 来玩 CartPole

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions import Categorical

class PolicyNetwork(nn.Module):
def __init__(self, state_dim, action_dim):
super(PolicyNetwork, self).__init__()
self.fc = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, action_dim),
nn.Softmax(dim=-1) # 输出概率分布
)

def forward(self, x):
return self.fc(x)

class REINFORCE:
def __init__(self, state_dim, action_dim):
self.policy = PolicyNetwork(state_dim, action_dim)
self.optimizer = optim.Adam(self.policy.parameters(), lr=1e-3)
self.gamma = 0.99
self.log_probs = []
self.rewards = []

def select_action(self, state):
state = torch.from_numpy(state).float().unsqueeze(0)
probs = self.policy(state)
m = Categorical(probs) # 创建分布
action = m.sample() # 采样动作

# 保存 log_prob 用于后续求导: ln π(a|s)
self.log_probs.append(m.log_prob(action))
return action.item()

def update(self):
R = 0
policy_loss = []
returns = []

# 1. 计算每个时刻的 Return G_t
for r in self.rewards[::-1]:
R = r + self.gamma * R
returns.insert(0, R)

returns = torch.tensor(returns)
# 归一化 returns 可以稳定训练
returns = (returns - returns.mean()) / (returns.std() + 1e-9)

# 2. 计算损失: - sum( log_prob * G_t )
# 注意: 我们要最大化 J,也就是最小化 -J
for log_prob, R in zip(self.log_probs, returns):
policy_loss.append(-log_prob * R)

# 3. 梯度下降
self.optimizer.zero_grad()
policy_loss = torch.cat(policy_loss).sum()
policy_loss.backward()
self.optimizer.step()

# 清空数据
self.log_probs = []
self.rewards = []

5. 总结

本章我们解锁了 Policy-based 方法。

  • Policy Gradient Theorem 是核心基石。
  • REINFORCE 是最简单的实现,虽然方差大,但它展示了 PG 的基本逻辑。

REINFORCE 的方差问题如何彻底解决?
答案是结合 Value-based 和 Policy-based 的优点,这就引出了下一章的终极形态:Actor-Critic 方法


上一章:第8章 - 值函数近似 | 下一章:第10章 - Actor-Critic 方法 >>