演员评论家 Actor-Critic 算法
Actor-Critic 算法是强化学习中一种结合了策略梯度(Policy Gradient)和值函数估计(Value Function Estimation)的方法。它通过'演员'(Actor)负责决策动作,'评论家'(Critic)评估状态价值,两者协作以降低方差并加速收敛。
核心概念与角色设定
想象一个学习爬山的机器人,目标是找到山顶(最高奖励):
- Actor(行动者):像冒险家,根据当前策略决定下一步怎么走(左或右)。它不总是聪明,可能会选错方向。
- Critic(评论者):像导师,观察冒险家的表现,评价这一步的好坏。它会告诉 Actor:'这一步走得好,接近山顶了'或者'走错了'。
协作机制:
- Actor 观察环境,根据策略选择一个动作。
- Critic 根据动作和环境反馈计算'价值',表示该动作的优劣。
- Actor 利用 Critic 的评价调整策略,使未来选择更优。
这种分工使得 Actor 专注于优化策略,而 Critic 专注于准确评估,两者互补。
背景与动机
为什么需要 Actor-Critic?
强化学习主要有三类方法:值函数法(如 Q-Learning)、策略法、以及结合两者的 Actor-Critic。
纯策略梯度方法存在明显局限:
- 高方差:直接使用环境反馈计算梯度会导致更新不稳定。
- 低效率:奖励信号稀疏且传递慢,采样成本高。
引入 Critic 后,通过估算值函数来计算优势函数(Advantage Function),可以显著降低策略梯度的方差,同时保留策略方法的灵活性。
数学推导基础
1. 优化目标
最大化累积折扣奖励的期望: $$J(\theta) = \mathbb{E}{\pi\theta} \left[ \sum_{t=0}^\infty \gamma^t r_t \right]$$
2. 策略梯度定理
为了优化策略参数 $\theta$,计算目标函数的梯度: $$\nabla_\theta J(\theta) = \mathbb{E}{\pi\theta} \left[ \nabla_\theta \log \pi_\theta(a|s) \cdot A^\pi(s, a) \right]$$ 其中 $A^\pi(s, a)$ 是优势函数,衡量动作的相对好坏。
3. Critic 的作用
Critic 通过最小化均方误差来学习状态值函数 $V^\pi(s)$: $$L(w) = \frac{1}{2} \mathbb{E} \left[ (r + \gamma V^\pi(s') - V^\pi(s))^2 \right]$$ 这里 $\delta = r + \gamma V^\pi(s') - V^\pi(s)$ 即为时间差分(TD)误差,用于指导 Actor 更新。
算法流程
完整的 Actor-Critic 迭代过程如下:
- 初始化 Actor 和 Critic 网络参数。
- 在状态 $s$ 下,Actor 根据策略 $\pi_\theta(a|s)$ 采样动作 $a$。
- 执行动作,获得奖励 $r$ 和下一状态 $s'$。
- Critic 计算 TD 误差 $\delta$。
- Critic 利用 $\delta$ 更新值函数参数,减小预测误差。
- Actor 利用 $\delta$ 更新策略参数,向更优方向移动。
- 重复直至收敛。
PyTorch 实现
下面是一个基于 PyTorch 的完整实现示例。我们定义两个网络:PolicyNet 作为 Actor,ValueNet 作为 Critic。
网络结构
import torch
from torch import nn
from torch.nn import functional as F
import numpy as np
class PolicyNet(nn.Module):
"""Actor: 策略网络,输出动作概率分布"""
def __init__(self, n_states, n_hiddens, n_actions):
super(PolicyNet, self).__init__()
self.fc1 = nn.Linear(n_states, n_hiddens)
self.fc2 = nn.Linear(n_hiddens, n_actions)
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return F.softmax(x, dim=1)
class ValueNet(nn.Module):
"""Critic: 值函数网络,输出状态价值"""
def __init__(self, n_states, n_hiddens):
super(ValueNet, self).__init__()
self.fc1 = nn.Linear(n_states, n_hiddens)
self.fc2 = nn.Linear(n_hiddens, 1)
def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x
训练逻辑
class ActorCritic:
def __init__(self, n_states, n_hiddens, n_actions, actor_lr, critic_lr, gamma):
self.gamma = gamma
self.actor = PolicyNet(n_states, n_hiddens, n_actions)
self.critic = ValueNet(n_states, n_hiddens)
# 注意:Actor 和 Critic 使用不同的学习率通常效果更好
self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)
self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)
def take_action(self, state):
"""根据当前策略采样动作"""
state = torch.tensor(state[np.newaxis, :], dtype=torch.float)
probs = self.actor(state)
action_dist = torch.distributions.Categorical(probs)
action = action_dist.sample().item()
return action
def update(self, transition_dict):
"""批量更新 Actor 和 Critic"""
states = torch.tensor(transition_dict['states'], dtype=torch.float)
actions = torch.tensor(transition_dict['actions']).view(-1, 1)
rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1)
next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float)
dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1)
# Critic 更新:最小化 TD 误差
td_value = self.critic(states)
td_target = rewards + self.gamma * self.critic(next_states) * (1 - dones)
td_delta = td_target - td_value
critic_loss = torch.mean(F.mse_loss(td_value, td_target.detach()))
# Actor 更新:最大化优势函数
log_probs = torch.log(self.actor(states).gather(1, actions))
actor_loss = torch.mean(-log_probs * td_delta.detach())
# 反向传播与参数更新
self.actor_optimizer.zero_grad()
self.critic_optimizer.zero_grad()
actor_loss.backward()
critic_loss.backward()
self.actor_optimizer.step()
self.critic_optimizer.step()
环境测试(CartPole)
使用 OpenAI Gym 的 CartPole-v1 环境进行验证:
import gym
import matplotlib.pyplot as plt
env_name = 'CartPole-v1'
env = gym.make(env_name)
n_states = env.observation_space.shape[0]
n_actions = env.action_space.n
# 实例化 Agent
agent = ActorCritic(
n_states=n_states,
n_hiddens=16,
n_actions=n_actions,
actor_lr=1e-3,
critic_lr=1e-2,
gamma=0.9
)
return_list = []
for i in range(100): # 100 个回合
state = env.reset()[0]
done = False
episode_return = 0
transition_dict = {'states': [], 'actions': [], 'next_states': [], 'rewards': [], 'dones': []}
while not done:
action = agent.take_action(state)
next_state, reward, done, _, _ = env.step(action)
transition_dict['states'].append(state)
transition_dict['actions'].append(action)
transition_dict['next_states'].append(next_state)
transition_dict['rewards'].append(reward)
transition_dict['dones'].append(done)
state = next_state
episode_return += reward
return_list.append(episode_return)
agent.update(transition_dict)
print(f'Iter {i}, Avg Return: {np.mean(return_list[-10:])}')
plt.plot(return_list)
plt.title('Training Returns')
plt.show()
关键点总结
- 稳定性:Critic 的误差直接影响 Actor 的梯度更新,因此 Critic 的学习率通常设置得比 Actor 稍大或相当。
- 探索性:如果训练陷入局部最优,可以考虑对 Actor 加入熵正则化项鼓励探索。
- 扩展性:此框架是 A3C、PPO 等现代算法的基础,理解其核心有助于掌握更复杂的强化学习模型。
环境配置建议
- Python 3.8+
- PyTorch 1.10+
- Gym 0.21+
确保依赖库版本兼容,避免运行时出现张量维度不匹配的问题。


