1. 从策略梯度到Actor-Critic框架的演进
在强化学习领域,策略梯度方法为我们提供了一种直接优化策略函数的途径。但当我们深入实践时会发现,原始的REINFORCE算法存在两个显著问题:一是需要完整的轨迹数据才能进行参数更新,二是梯度估计的方差过高导致训练不稳定。这正是Actor-Critic框架诞生的背景。
1.1 策略梯度的核心缺陷
策略梯度定理告诉我们,策略性能的梯度可以表示为:
∇J(θ) = E[∇logπ(a|s) * Q(s,a)]
这个公式虽然优雅,但在实际应用中存在明显局限。首先,Q(s,a)需要通过蒙特卡洛采样来估计,这意味着必须等到一个episode结束才能计算回报。其次,由于采样轨迹的随机性,Q值的估计方差往往很大,导致参数更新方向波动剧烈。
我在实际项目中曾尝试用原始策略梯度训练机械臂控制任务,发现两个典型现象:
- 同一组参数在不同训练回合表现差异极大
- 需要超过5000个完整episode才能看到稳定进步
这些现象正是上述理论缺陷的现实映射。
1.2 价值函数近似的引入
解决上述问题的关键突破在于引入价值函数近似。我们可以用参数化的价值函数来估计Q值,这样带来三个优势:
- 无需等待完整轨迹,实现单步更新
- 通过函数逼近减小方差
- 允许重用历史数据
具体实现时,我们构建两个神经网络:
- Actor网络:负责策略π(a|s)的输出
- Critic网络:负责评估Q(s,a)或V(s)
这种架构下,Critic就像一位严格的教练,不断评估Actor的表现并给出改进建议;而Actor则像运动员,根据反馈调整自己的动作策略。二者相互促进,共同提升。
2. QAC算法深度解析
2.1 QAC的算法架构
QAC(Q-value Actor-Critic)是最基础的AC算法,其核心组件包括:
Critic部分:
python复制class Critic(nn.Module):
def __init__(self, state_dim, hidden_size):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, 1)
)
def forward(self, state):
return self.net(state)
Actor部分:
python复制class Actor(nn.Module):
def __init__(self, state_dim, action_dim, hidden_size):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, action_dim),
nn.Softmax(dim=-1)
)
def forward(self, state):
return self.net(state)
2.2 训练过程中的关键细节
在实际实现QAC时,有几个容易出错的细节需要特别注意:
-
学习率设置:Critic的学习率通常应比Actor大3-5倍。这是因为价值估计是策略优化的基础,需要更快收敛。我常用的比例是:
python复制actor_lr = 1e-4 critic_lr = 5e-4 -
折扣因子选择:对于episode较长的任务(如围棋),γ应设得较大(0.99);对于即时决策任务(如机器人避障),γ可以较小(0.9)。
-
探索策略:虽然Actor本身具有随机性,但在训练初期仍需额外探索。我通常会在策略输出上添加随时间衰减的噪声:
python复制def get_action(self, state, epsilon=0.1): probs = self.actor(state) if random.random() < epsilon: return random.randint(0, self.action_dim - 1) return torch.multinomial(probs, 1).item()
2.3 典型问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 回报不增反降 | Critic过拟合 | 增加Critic网络dropout,减小学习率 |
| 策略收敛到局部最优 | 探索不足 | 提高噪声系数,或改用熵正则化 |
| 训练波动剧烈 | 学习率过大 | 采用自适应优化器如Adam |
| 早期训练崩溃 | 梯度爆炸 | 添加梯度裁剪(grad_clip=1.0) |
3. A2C算法:带基线的进阶版本
3.1 基线技术的数学原理
A2C(Advantage Actor-Critic)的核心创新是引入优势函数:
A(s,a) = Q(s,a) - V(s)
这个改进背后的数学原理十分深刻。考虑策略梯度的方差:
Var[∇J] = E[(Q∇logπ)^2] - (E[Q∇logπ])^2
引入基线b(s)后,方差变为:
Var[∇J] = E[((Q-b)∇logπ)^2] - (E[(Q-b)∇logπ])^2
通过选择合适的b(s),可以显著减小第一项的值。理论证明,当b(s)=V(s)时,方差最小化效果最好。
3.2 实际实现技巧
在PyTorch中实现A2C时,有几个实用技巧:
-
共享网络架构:Actor和Critic可以共享底层特征提取层:
python复制class SharedBackbone(nn.Module): def __init__(self, state_dim, hidden_size): super().__init__() self.shared = nn.Sequential( nn.Linear(state_dim, hidden_size), nn.ReLU() ) self.actor_head = nn.Linear(hidden_size, action_dim) self.critic_head = nn.Linear(hidden_size, 1) -
并行环境采样:使用多环境并行可大幅提升数据效率:
python复制envs = [gym.make('CartPole-v1') for _ in range(4)] states = [env.reset() for env in envs] -
回报标准化:对优势函数进行标准化有助于稳定训练:
python复制advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
3.3 超参数调优经验
基于我在多个项目中的实践,推荐以下参数组合作为起点:
| 参数 | 推荐值 | 调整方向建议 |
|---|---|---|
| γ | 0.99 | 长周期任务增大,短周期减小 |
| λ(GAE) | 0.95 | 平衡偏差方差 |
| 熵系数 | 0.01 | 探索不足时增大 |
| 批量大小 | 64 | 根据显存调整 |
4. 离轨AC方法:重要性采样实践
4.1 重要性采样的本质理解
重要性采样本质上是一种加权修正技术,其核心公式:
E_p[f(x)] = E_q[f(x)p(x)/q(x)]
在强化学习语境下,p是当前策略,q是行为策略。权重项p/q就像"数据可信度"的修正因子。
我在机器人控制项目中曾遇到这样的情况:旧策略采集的数据中,80%的动作为"向左转",而新策略更倾向于"直行"。此时如果不加修正直接使用旧数据,会导致策略严重偏向左转。
4.2 实现中的数值稳定技巧
直接计算重要性权重容易导致数值不稳定,实践中建议:
-
对数域计算:
python复制
log_ratio = new_log_probs - old_log_probs ratio = torch.exp(log_ratio) -
权重裁剪:
python复制clipped_ratio = torch.clamp(ratio, 0.8, 1.2) -
混合策略更新:
python复制def update(self, batch): # 计算新旧策略概率比 new_probs = self.actor(batch.states) ratios = new_probs / batch.old_probs # 计算裁剪后的surrogate loss surr1 = ratios * batch.advantages surr2 = torch.clamp(ratios, 1-eps, 1+eps) * batch.advantages actor_loss = -torch.min(surr1, surr2).mean()
4.3 离轨学习的适用场景
离轨AC方法特别适合以下场景:
- 环境交互成本高(如真实机器人)
- 需要复用历史数据
- 多智能体协同学习
但在以下情况可能表现不佳:
- 行为策略与目标策略差异过大
- 高维连续动作空间
- 稀疏奖励环境
5. 工程实践中的进阶技巧
5.1 分布式训练架构
对于复杂任务,我推荐采用Ape-X风格的架构:
code复制 |-> Learner <-|
Replay Buffer - -> Actors
|-> Learner <-|
关键组件实现:
python复制class PrioritizedReplay:
def __init__(self, capacity, alpha=0.6):
self.alpha = alpha
self.capacity = capacity
self.buffer = []
self.priorities = np.zeros(capacity)
def add(self, experience, priority):
if len(self.buffer) >= self.capacity:
idx = np.argmin(self.priorities)
self.buffer[idx] = experience
self.priorities[idx] = priority
else:
self.buffer.append(experience)
self.priorities[len(self.buffer)-1] = priority
5.2 混合探索策略
结合以下探索技术往往能取得更好效果:
- 初始随机探索:前1k步完全随机动作
- 噪声注入:在策略输出添加OU噪声
- 熵正则化:在损失函数中添加熵项
python复制def compute_loss(self, batch):
# 策略损失
new_probs = self.actor(batch.states)
ratios = new_probs / batch.old_probs
surr1 = ratios * batch.advantages
surr2 = torch.clamp(ratios, 1-self.eps, 1+self.eps) * batch.advantages
policy_loss = -torch.min(surr1, surr2).mean()
# 熵正则项
entropy = -(new_probs * torch.log(new_probs + 1e-10)).sum(-1).mean()
# Critic损失
value_loss = F.mse_loss(self.critic(batch.states), batch.returns)
return policy_loss - self.entropy_coef*entropy + self.value_coef*value_loss
5.3 训练监控与调试
建立完善的监控体系至关重要,我通常会跟踪以下指标:
- 策略指标:平均回报、回合长度、动作熵
- 价值指标:TD误差、价值估计范围
- 训练指标:梯度范数、参数更新幅度
使用TensorBoard进行可视化:
python复制writer.add_scalar('Loss/Policy', policy_loss.item(), global_step)
writer.add_scalar('Loss/Value', value_loss.item(), global_step)
writer.add_histogram('Actions', actions, global_step)
在连续控制任务中,我发现当动作熵下降到初始值的30%时,通常意味着策略开始收敛,此时可以适当减小探索强度。