演员评论家 Actor-Critic 算法
Actor-Critic 算法是强化学习中一种结合了策略梯度(Policy Gradient)和值函数估计(Value Function Estimation)的方法。它通过引入 Critic 来评估状态价值,从而降低策略更新过程中的方差,加速收敛。
核心概念理解
角色设定
在 Actor-Critic 框架中,两个网络分工明确:
- Actor(演员):负责决策。根据当前状态 $s$ 输出动作 $a$ 的概率分布,即策略 $\ heta(a|s)$。
- Critic(评论家):负责评估。估算当前状态的价值 $V(s)$ 或状态 - 动作对的价值 $Q(s, a)$,用于判断 Actor 的动作好坏。
协作机制
- Actor 观察环境并执行动作。
- Critic 接收反馈,计算时序差分误差(TD Error),评价该动作的优劣。
- Actor 利用 Critic 的评价信号更新策略参数,向更优方向移动。
- Critic 同时根据实际奖励更新价值估计,提高评估准确性。
这种机制类似于教练指导运动员:运动员(Actor)尝试动作,教练(Critic)实时反馈效果,双方共同提升表现。
背景与动机
强化学习的演进
强化学习方法主要分为三类:
- 值函数方法(如 Q-Learning):估算价值表,适合离散空间,但面临维度灾难。
- 策略方法(如 REINFORCE):直接优化策略,适合连续空间,但梯度方差大,收敛慢。
- 结合方法(Actor-Critic):取长补短,既保留策略方法的灵活性,又利用值函数降低方差。
策略梯度的局限性
纯策略梯度方法的目标函数梯度为: $$ \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)$ 衡量动作相对于平均水平的优劣。直接使用环境奖励作为基线会导致高方差问题,需要大量采样才能稳定更新。引入 Critic 后,用 TD 误差替代优势函数,显著降低了方差。
数学推导
优化目标
最大化累积折扣奖励的期望: $$ J(\theta) = \mathbb{E}{\pi\theta} \left[ \sum_{t=0}^\infty \gamma^t r_t \right] $$
Critic 更新(值函数估计)
Critic 通过最小化均方误差来学习状态值函数 $V(s)$: $$ L(w) = \frac{1}{2} \mathbb{E} \left[ \left( r + \gamma V^\pi(s') - V^\pi(s) \right)^2 \right] $$ 参数 $w$ 的更新方向由 TD 误差 $\delta$ 决定: $$ \delta = r + \gamma V^\pi(s') - V^\pi(s) $$ $$ w \leftarrow w + \beta \cdot \delta \cdot \nabla_w V^\pi(s) $$
Actor 更新(策略优化)
Actor 利用 Critic 计算的 TD 误差作为优势函数的近似,更新策略参数 $\theta$: $$ \theta \leftarrow \theta + \alpha \cdot \nabla_\theta \log \pi_\theta(a|s) \cdot \delta $$ 这里 $\delta > 0$ 表示动作优于预期,应增加概率;反之则减少。
PyTorch 实现
下面是一个基于 PyTorch 的完整实现示例。代码结构清晰,分为策略网络、价值网络和主训练循环。
网络定义
import torch
import torch.nn as nn
from torch.nn import functional as F
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 = F.relu(self.fc1(x))
return F.softmax(self.fc2(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 = F.relu(self.fc1(x))
return self.fc2(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)
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):
# 将 numpy 状态转换为 tensor
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):
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 = 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-v1 环境进行验证。
import gym
import matplotlib.pyplot as plt
env_name = 'CartPole-v1'
num_episodes = 100
gamma = 0.9
actor_lr = 1e-3
critic_lr = 1e-2
n_hiddens = 16
env = gym.make(env_name)
n_states = env.observation_space.shape[0]
n_actions = env.action_space.n
agent = ActorCritic(n_states, n_hiddens, n_actions, actor_lr, critic_lr, gamma)
return_list = []
for i in range(num_episodes):
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()
关键点总结
- TD 误差是关键:Critic 的评估质量直接影响 Actor 的更新方向。如果 Critic 不准,Actor 可能学偏。
- 熵正则化:在实际应用中,建议在 Actor 损失中加入熵项,鼓励探索,避免过早陷入局部最优。
- 超参数敏感:Actor 和 Critic 的学习率通常不同,Critic 往往需要更快的收敛速度。
- 扩展算法:A3C 引入了多线程并行,PPO 限制了策略更新幅度,都是基于 Actor-Critic 思想的改进。
总结
Actor-Critic 算法通过结合策略梯度与值函数估计,有效解决了纯策略方法方差大的问题。文章详细推导了数学原理,并提供了基于 PyTorch 的完整实现及 CartPole 环境训练示例。掌握这一基础架构,有助于进一步理解 PPO、SAC 等现代强化学习算法。


