Actor-Critic算法原理与实战:从基础到进阶优化
1. Actor-Critic算法概述
Actor-Critic算法是强化学习领域中最具代表性的算法架构之一,它巧妙地将策略梯度(Policy Gradient)和价值函数(Value Function)两种方法结合起来。我第一次接触这个算法是在解决一个工业机器人控制问题时——传统方法要么收敛速度慢,要么稳定性差,直到尝试了Actor-Critic架构才真正突破了性能瓶颈。
这个算法的核心思想就像导演(Critic)指导演员(Actor)拍戏:Actor负责根据当前状态做出动作决策,Critic则实时评估这些动作的价值并提供改进建议。这种分工协作的模式使得算法既能像策略梯度方法那样直接优化策略,又能利用价值函数的低方差特性来加速学习过程。在实际项目中,我发现这种架构特别适合处理连续动作空间和高维状态空间的问题,比如自动驾驶的转向控制或者机械臂的轨迹规划。
2. 算法核心原理拆解
2.1 双网络架构设计
Actor-Critic的核心在于其独特的双网络结构:
- Actor网络:输入状态s,输出动作概率分布π(a|s)
- Critic网络:输入状态s(或状态-动作对(s,a)),输出价值估计V(s)或Q(s,a)
我在TensorFlow中实现的典型网络结构如下:
# Actor网络示例 class Actor(tf.keras.Model): def __init__(self, action_dim): super().__init__() self.dense1 = layers.Dense(64, activation='relu') self.dense2 = layers.Dense(64, activation='relu') self.output_layer = layers.Dense(action_dim, activation='softmax') def call(self, state): x = self.dense1(state) x = self.dense2(x) return self.output_layer(x) # Critic网络示例 class Critic(tf.keras.Model): def __init__(self): super().__init__() self.dense1 = layers.Dense(64, activation='relu') self.dense2 = layers.Dense(64, activation='relu') self.output_layer = layers.Dense(1) def call(self, state): x = self.dense1(state) x = self.dense2(x) return self.output_layer(x)关键经验:Actor的最后一层激活函数选择取决于动作空间类型——离散动作用softmax,连续动作用tanh或sigmoid。Critic的输出层不要加激活函数,因为价值估计的范围不受限。
2.2 优势函数计算
算法性能的关键在于优势函数A(s,a)的计算,它表示当前动作相对于平均水平的优势。最常用的计算方法是:
A(s,a) = Q(s,a) - V(s) ≈ r + γV(s') - V(s)
其中γ是折扣因子。在实际编码时,我通常会使用广义优势估计(GAE)来平衡偏差和方差:
def compute_gae(rewards, values, gamma=0.99, lam=0.95): deltas = rewards[:-1] + gamma * values[1:] - values[:-1] gae = np.zeros_like(deltas) running_add = 0 for t in reversed(range(len(deltas))): running_add = deltas[t] + gamma * lam * running_add gae[t] = running_add return gae3. 经典算法实现与优化
3.1 A2C算法实现
Advantage Actor-Critic (A2C)是最基础的同步版本实现。以下是核心训练循环的关键步骤:
- 数据收集:运行当前策略πθ与环境交互N步
- 优势计算:使用Critic网络计算每个状态的价值估计
- 策略更新:计算策略梯度 ∇θ logπθ(a|s)A(s,a)
- 价值更新:最小化Critic的TD误差 (r+γV(s')-V(s))²
# 伪代码示例 for episode in range(EPISODES): states, actions, rewards = run_episode(env, actor) values = critic(states) advantages = compute_gae(rewards, values) # 更新Actor with tf.GradientTape() as tape: probs = actor(states) selected_probs = tf.gather(probs, actions, batch_dims=1) actor_loss = -tf.reduce_mean(tf.math.log(selected_probs) * advantages) actor_grads = tape.gradient(actor_loss, actor.trainable_variables) actor_optimizer.apply_gradients(zip(actor_grads, actor.trainable_variables)) # 更新Critic with tf.GradientTape() as tape: current_values = critic(states) critic_loss = tf.reduce_mean((rewards + GAMMA * critic(states_next) - current_values)**2) critic_grads = tape.gradient(critic_loss, critic.trainable_variables) critic_optimizer.apply_gradients(zip(critic_grads, critic.trainable_variables))3.2 A3C算法优化
Asynchronous Advantage Actor-Critic (A3C)通过多个worker并行收集经验来提升效率。我在实际部署中发现这些优化点特别重要:
- 网络同步策略:全局网络每K步同步到各个worker
- 熵正则化:在策略损失中加入熵项避免过早收敛
entropy = tf.reduce_sum(probs * tf.math.log(probs), axis=1) actor_loss = -tf.reduce_mean(log_probs * advantages) - 0.01 * entropy - 梯度裁剪:防止梯度爆炸
grads, _ = tf.clip_by_global_norm(grads, 0.5)
4. 实战技巧与调参经验
4.1 超参数设置黄金法则
经过数十个项目的调参实践,我总结出这些经验值:
| 参数 | 推荐值 | 作用说明 |
|---|---|---|
| 学习率(Actor) | 3e-4 ~ 1e-3 | 策略更新步长 |
| 学习率(Critic) | 1e-3 ~ 3e-3 | 价值函数收敛速度 |
| 折扣因子γ | 0.9 ~ 0.99 | 未来奖励的重要性 |
| GAE参数λ | 0.9 ~ 0.95 | 优势估计的偏差-方差权衡 |
| 批量大小 | 64 ~ 512 | 每次更新的样本数 |
| 熵系数 | 0.01 ~ 0.1 | 策略探索强度 |
血泪教训:Critic的学习率通常应该比Actor大2-3倍,因为价值函数通常比策略更容易学习。我曾在一个机械臂控制项目中发现,当Critic学习不足时,Actor会基于错误的价值估计做出决策,导致完全无法收敛。
4.2 网络架构设计技巧
共享底层特征:对于视觉输入等复杂状态,可以让Actor和Critic共享前面的卷积层
class SharedNetwork(tf.keras.Model): def __init__(self): super().__init__() self.conv1 = layers.Conv2D(32, 3, activation='relu') self.flatten = layers.Flatten() self.common_fc = layers.Dense(256, activation='relu') def call(self, inputs): x = self.conv1(inputs) x = self.flatten(x) return self.common_fc(x)输出层初始化:Actor的最后一层bias初始化为0.5(sigmoid)或0(tanh),Critic最后一层初始化为0
归一化技巧:对优势函数进行批量归一化可以显著提升稳定性
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
5. 常见问题与解决方案
5.1 训练不稳定问题
症状:回报曲线剧烈波动或突然崩溃
- 检查清单:
- 确认Critic的损失是否在持续下降
- 检查优势值是否出现NaN(通常意味着梯度爆炸)
- 观察策略熵值是否过早趋近于0
解决方案:
- 降低Actor学习率
- 增加熵系数
- 使用梯度裁剪(特别是对于RNN版本)
- 改用PPO等带约束的算法变种
5.2 探索不足问题
典型案例:智能体卡在局部最优策略
- 诊断方法:
健康值通常在动作空间大小的1/2左右(如5个动作则熵≈1.5)# 计算策略熵 probs = actor(states) entropy = -tf.reduce_sum(probs * tf.math.log(probs), axis=1).numpy().mean() print(f"Average policy entropy: {entropy:.3f}")
改进措施:
- 动态调整熵系数:开始时较大,随着训练逐渐减小
- 使用噪声探索:在动作输出中加入OU噪声
- 采用课程学习:从简化环境开始逐步增加难度
6. 进阶变种与扩展应用
6.1 分布式Actor-Critic
现代实现通常采用IMPALA架构,其核心改进包括:
- 使用V-trace修正off-policy偏差
- 分离推理worker和训练worker
- 优先经验回放(PER)机制
# V-trace计算示例 def vtrace(target_values, behaviour_values, rewards, discounts, rho=1.0): v_target = np.zeros_like(target_values) delta = rewards + discounts * target_values[1:] - target_values[:-1] v_target[-1] = target_values[-1] for t in reversed(range(len(delta))): v_target[t] = target_values[t] + delta[t] * min(rho, 1.0) return v_target6.2 混合动作空间处理
对于同时包含离散和连续动作的场景(如RTS游戏),可以采用分层架构:
- 顶层Actor选择离散动作类型
- 子Actor处理对应类型的连续参数
- Critic评估整体决策价值
class HybridActor(tf.keras.Model): def __init__(self, discrete_dim, continuous_dims): super().__init__() self.common = layers.Dense(64, activation='relu') self.discrete_head = layers.Dense(discrete_dim, activation='softmax') self.continuous_heads = [layers.Dense(dim, activation='tanh') for dim in continuous_dims] def call(self, state): x = self.common(state) discrete_probs = self.discrete_head(x) continuous_actions = [head(x) for head in self.continuous_heads] return discrete_probs, continuous_actions在实际机器人控制项目中,这种架构成功地将离散操作(如抓手开合)和连续控制(如关节角度)统一到了一个策略网络中。