【强化学习】Actor-Critic 演员、评论家,20W字总结(五)

😊你好,我是小航,一个正在变秃、变强的文艺倾年。
🔔本文讲解【强化学习】Actor-Critic 演员、评论家,20W字总结(五),期待与你一同探索、学习、进步,一起卷起来叭!
🎯 把我的博客装进你的 Claude Code,它就是你的 AI 学习搭子
想随时搜我的文章、让 AI 帮你深度讲解甚至出面试题?复制下面这段提示词丢进你的 Claude Code——它会自动生成一个本地 SKILL,之后你直接说「搜一下强化学习的文章」就行。RSS 自动同步最新内容,不用手动存任何文件。
请为这个 CSDN 博客创建一个本地 SKILL(存到 .claude/skills/csdn-blog/SKILL.md): RSS 源:https://rss.csdn.net/m0_51517236/rss/map 支持三件事:① 列出最新文章(标题+链接+摘要);② 按关键词搜索; ③ 抓取指定文章全文,作为 AI 学习助手 / 面试官深度讲解并出题考核我。 SKILL.md 里写清楚 RSS URL、调用方式和示例。生成完就能用自然语言搜文章了。一键订阅,长期可用。🚀
上一篇我们用 REINFORCE + 基线把策略梯度的方差压了下来,训练曲线平滑多了。但留了个硬伤:它俩都是蒙特卡洛方法——必须等一整局跑完,才能回头算回报、更新策略。
杆子都倒了,你才告诉它"刚才那步走错了",反馈滞后得离谱。
这篇就来治这个病。主角叫 Actor-Critic(演员-评论家),它让智能体走一步、学一步,不用再苦等一整局。

蒙特卡洛的硬伤:得等一整局
先复习一下带基线 REINFORCE 的梯度公式:
∇ θ J ( θ ) = E [ ∑ t ( G t − V ( S t ) ) ⋅ ∇ θ log π θ ( A t ∣ S t ) ] \nabla_{\theta}J(\theta) = \mathbb{E}\left[\sum_t (G_t - V(S_t)) \cdot \nabla_{\theta}\log\pi_\theta(A_t|S_t)\right] ∇θJ(θ)=E[t∑(Gt−V(St))⋅∇θlogπθ(At∣St)]
权重是 G t − V ( S t ) G_t - V(S_t) Gt−V(St)。问题出在 G t G_t Gt 上——它是"从时刻 t t t 到回合结束"的所有奖励之和。换句话说,不跑到回合结束,你根本不知道 G t G_t Gt 是多少。
这就是蒙特卡洛方法的通病:要凑齐一整条轨迹才能学习。样本效率低,反馈又慢。
TD 方法:走一步就能学
破解的法子叫时序差分(Temporal Difference,TD)。思路很巧:与其等一整局算 G t G_t Gt,不如用"下一步的价值"来估计。
回顾一下价值函数的贝尔曼方程:
V ( S t ) ≈ R t + γ V ( S t + 1 ) V(S_t) \approx R_t + \gamma V(S_{t+1}) V(St)≈Rt+γV(St+1)
当前状态的价值 ≈ 这一步的奖励 + 打折后的"下一个状态的价值"。那 G t G_t Gt(真实的未来回报)就可以用这个估计顶上去——把 G t G_t Gt 换成 R t + γ V ( S t + 1 ) R_t + \gamma V(S_{t+1}) Rt+γV(St+1)。
直观理解:评估一家公司对你的价值,不能只看这个月发的工资(即时奖励 R t R_t Rt),还得看以后公司对你的价值( γ V ( S t + 1 ) \gamma V(S_{t+1}) γV(St+1))打个折扣。眼下的钱更重要,但未来的前景也重要,只是没那么重要。
这个 R t + γ V ( S t + 1 ) R_t + \gamma V(S_{t+1}) Rt+γV(St+1) 叫 TD 目标。它和当前估计 V ( S t ) V(S_t) V(St) 的差,就是 TD 误差:
δ = R t + γ V ( S t + 1 ) − V ( S t ) \delta = R_t + \gamma V(S_{t+1}) - V(S_t) δ=Rt+γV(St+1)−V(St)
关键来了:算 TD 目标只需要走一步——拿到 R t R_t Rt 和下一个状态 S t + 1 S_{t+1} St+1 就够了,不用等回合结束。反馈从"等一整局"压缩到"等一步"。

Actor-Critic:演员 + 评论家
把 TD 误差塞进策略梯度,就得到了 Actor-Critic:
∇ θ J ( θ ) = E [ ∑ t ( R t + γ V ω ( S t + 1 ) − V ω ( S t ) ) ⏟ TD 误差 δ ⋅ ∇ θ log π θ ( A t ∣ S t ) ] \nabla_{\theta}J(\theta) = \mathbb{E}\left[\sum_t \underbrace{(R_t + \gamma V_\omega(S_{t+1}) - V_\omega(S_t))}_{\text{TD 误差 } \delta} \cdot \nabla_{\theta}\log\pi_\theta(A_t|S_t)\right] ∇θJ(θ)=E t∑TD 误差 δ (Rt+γVω(St+1)−Vω(St))⋅∇θlogπθ(At∣St)
这里有两个神经网络同台演出:
- Actor(演员, π θ \pi_\theta πθ):负责选动作——输入状态,输出动作概率。就是前面的策略网络。
- Critic(评论家, V ω V_\omega Vω):负责打分——输入状态,输出"这个状态值多少分"。就是价值网络。
演员负责演,评论家负责评。演员演完一个动作,评论家立刻给出 TD 误差当反馈:这个动作比预期好( δ > 0 \delta > 0 δ>0),增强;比预期差( δ < 0 \delta < 0 δ<0),压制。两个网络边演边学,一起更新。
这里用的是 1 步 TD 误差来估计优势。也可以用 n n n 步 TD(多看几步再评),那叫广义优势估计(GAE),放在番外篇的数学推导里讲。
代码实现
两个网络结构几乎一样,区别在输出层:
class PolicyNet(nn.Module):
def __init__(self, action_size):
super().__init__()
self.l1 = nn.Linear(4, 128)
self.l2 = nn.Linear(128, action_size)
def forward(self, x):
x = F.relu(self.l1(x))
return F.softmax(self.l2(x), dim=1) # 输出动作概率
class ValueNet(nn.Module):
def __init__(self):
super().__init__()
self.l1 = nn.Linear(4, 128)
self.l2 = nn.Linear(128, 1)
def forward(self, x):
x = F.relu(self.l1(x))
return self.l2(x) # 输出状态价值(标量,不激活)
💡 代码解析:PolicyNet 输出走 Softmax(概率),ValueNet 输出不过激活(价值可正可负)。结构对称,像一对双胞胎。
Agent 同时管着这俩网络,update 是核心——每走一步就调用一次:
class Agent:
def __init__(self):
self.gamma = 0.98
self.lr_pi = 0.0002
self.lr_v = 0.0005
self.pi = PolicyNet(action_size=2)
self.v = ValueNet()
self.optimizer_pi = optim.Adam(self.pi.parameters(), lr=self.lr_pi)
self.optimizer_v = optim.Adam(self.v.parameters(), lr=self.lr_v)
def get_action(self, state):
state = torch.tensor(state[np.newaxis, :])
probs = self.pi(state)[0]
m = Categorical(probs)
action = m.sample().item()
return action, probs[action]
def update(self, state, action_prob, reward, next_state, done):
state = torch.tensor(state[np.newaxis, :])
next_state = torch.tensor(next_state[np.newaxis, :])
# ① 训练 Critic:让 V(S_t) 逼近 TD 目标
target = reward + self.gamma * self.v(next_state) * (1 - done)
v = self.v(state)
loss_v = nn.MSELoss()(v, target)
# ② 训练 Actor:用 TD 误差当权重更新策略
delta = target - v # TD 误差
loss_pi = -torch.log(action_prob) * delta.item()
self.optimizer_v.zero_grad()
self.optimizer_pi.zero_grad()
loss_v.backward()
loss_pi.backward()
self.optimizer_v.step()
self.optimizer_pi.step()
💡 代码解析:update 干两件事——① 算 TD 目标 target,用 MSE 把 Critic 的 V ( S t ) V(S_t) V(St) 往它上拉;② 算 TD 误差 delta,当权重更新 Actor。注意 done 那一项:回合结束时 ( 1 − d o n e ) = 0 (1 - done) = 0 (1−done)=0,TD 目标就只剩即时奖励 R t R_t Rt,不再幻想"还有下一步"。
和前几篇最大的区别在训练循环——update 在每一步都调用,不用等回合结束:
for episode in range(2000):
state = env.reset()
done = False
total_reward = 0
while not done:
action, prob = agent.get_action(state)
next_state, reward, done, _ = env.step(action)
agent.update(state, prob, reward, next_state, done) # 每走一步就学
state = next_state
total_reward += reward
reward_history.append(total_reward)
🚩 运行结果:每步都在学,收敛比 REINFORCE 快不少。

小结:策略梯度法家族集齐
到这里,第 2 章的四种策略梯度法就集齐了。它们其实共享一个统一的数学骨架,区别只在权重 Φ t \Phi_t Φt:
∇ θ J ( θ ) = E [ ∑ t Φ t ⋅ ∇ θ log π θ ( A t ∣ S t ) ] \nabla_\theta J(\theta) = \mathbb{E}\left[\sum_t \Phi_t \cdot \nabla_\theta\log\pi_\theta(A_t|S_t)\right] ∇θJ(θ)=E[t∑Φt⋅∇θlogπθ(At∣St)]
| 算法 | 权重 Φ t \Phi_t Φt |
|---|---|
| 原始策略梯度 | G ( τ ) G(\tau) G(τ)(整条轨迹回报) |
| REINFORCE | G t G_t Gt(只看动作之后的) |
| 带基线 REINFORCE | G t − V ( S t ) G_t - V(S_t) Gt−V(St)(减去预期) |
| Actor-Critic | R t + γ V ( S t + 1 ) − V ( S t ) R_t + \gamma V(S_{t+1}) - V(S_t) Rt+γV(St+1)−V(St)(TD 误差) |
从上到下:噪声越来越少、方差越来越小、反馈越来越即时。Actor-Critic 已经能"走一步学一步",听起来很完美。
但别高兴太早——它还有个新毛病:步子太大容易训崩。一次更新走太远,策略直接翻车。下一篇的 PPO 就来给更新"装刹车",让训练既快又稳。它是 RLHF 的核心组件,也是 ChatGPT 背后的关键算法之一。
📌 [ 笔者 ] 文艺倾年
📃 [ 更新 ] 2026.06.14
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!

更多推荐



所有评论(0)