深度强化学习Actor-Critic算法:从原理到工程实践全解析
1. 项目概述从“演员”与“评论家”的博弈说起如果你对深度强化学习Deep Reinforcement Learning, DRL感兴趣并且已经啃过DQNDeep Q-Network这块硬骨头那你可能会发现一个现象基于值函数Value-based的方法比如DQN在处理连续动作空间比如机器人关节的精确角度、自动驾驶的方向盘转角时会显得力不从心。这时候策略梯度Policy Gradient方法就登场了它直接学习一个策略函数输出动作的概率分布。而Actor-CriticAC系列算法正是策略梯度家族中承前启后、影响深远的核心框架。它巧妙地将“演员”Actor和“评论家”Critic两个神经网络结合起来一个负责决策输出动作一个负责评估评判决策好坏共同学习协同进化。这个系列从最基础的AC算法发展到更高效的A2CAdvantage Actor-Critic再到并行计算的里程碑A3CAsynchronous Advantage Actor-Critic其演进过程本身就是一部强化学习工程化与性能优化的简史。理解它们你不仅能掌握处理连续控制问题的利器更能深刻体会到如何通过算法设计来稳定训练、提升样本效率、并充分利用计算资源。无论是做机械臂控制、游戏AI还是复杂的资源调度AC系列都是你必须装进工具箱的“瑞士军刀”。接下来我将以一个从业者的视角带你深入这个系列的核心不仅讲清楚原理更会分享在实际调参和工程实现中那些容易踩坑的细节。2. 核心思想与算法演进脉络要理解AC系列我们必须先拆解它的两个核心组件Actor演员和Critic评论家。想象一下演员在排练一部戏Critic就是坐在台下的导演。演员每做一个动作采取一个行动导演就会根据剧本环境反馈的奖励给出即时评价“这个动作情绪不够”或“这里走得位置很好”。演员根据导演的评价通常是优势函数Advantage来调整自己的表演方式更新策略网络。导演自己也在学习努力使自己的评价标准价值函数更贴近剧本最终想表达的效果累计回报。2.1 基础AC算法策略梯度与价值函数的联姻基础的AC算法是策略梯度定理与值函数近似的直接结合。Actor策略网络 π(a|s; θ) 输入状态s输出动作a的概率分布离散动作或动作的参数如高斯分布的均值和方差用于连续动作。它的目标是最大化期望回报。其参数θ的更新遵循策略梯度定理∇θ J(θ) ≈ E [∇θ log π(a|s; θ) * Q(s, a)]这里Q(s, a)是状态-动作值函数代表了在状态s下执行动作a的长期价值。Critic价值网络 V(s; w) 或 Q(s, a; w) 它的任务是学习这个Q(s, a)或状态值函数V(s)为Actor提供更准确的“评分”。通常我们学习V(s)会更稳定因为它只依赖于状态。此时我们用Q(s, a)的估计值如r γV(s‘)来替代上式中的Q(s, a)。核心互动流程Agent在环境中交互采样轨迹数据(s, a, r, s‘)。Critic网络根据当前状态s和下一个状态s‘计算出TD误差Temporal-Difference Errorδ r γV(s‘; w) - V(s; w)。这个δ直观理解就是“实际获得的即时奖励加上下一个状态的预估价值”与“当前状态预估价值”的差值它衡量了当前动作的“意外之喜”或“意外之失”。Actor网络使用这个TD误差作为更新信号∇θ J(θ) ≈ E [∇θ log π(a|s; θ) * δ]。如果δ为正说明动作a带来了比预期更好的结果那么就增加在状态s下选择动作a的概率反之则减少。Critic网络通过最小化TD误差的平方来更新自己L(w) δ²。注意 这里用δ直接替代Q(s,a)是一种近似称为“带baseline的策略梯度”而V(s)就是这个baseline。它能有效降低方差是AC算法稳定的关键。2.2 A2C引入优势函数降低方差基础AC直接用TD误差δ作为策略梯度的权重。但δ其实是对优势函数A(s, a)的一个估计。优势函数的定义是A(s, a) Q(s, a) - V(s)。它剥离了状态本身固有的价值纯粹衡量在状态s下选择动作a比平均选择动作好多少。为什么需要优势函数假设在一个游戏中某个状态s本身价值就很高V(s)很大无论做什么动作都能得到不错的回报。如果只用Q(s,a)或δ可能会给所有动作都赋予很大的正更新信号导致策略更新方向不明确方差大。而使用A(s,a)我们关注的是动作之间的相对好坏这能显著降低梯度估计的方差使训练更稳定、更快。在A2C中我们通常用δ来估计A(s,a)。因此Actor的更新公式就变成了∇θ J(θ) ≈ E [∇θ log π(a|s; θ) * A(s, a)] ≈ E [∇θ log π(a|s; θ) * δ]虽然公式看起来和基础AC一样但思想层面强调了“优势”的概念并且在实际实现中A2C通常会结合多步TD误差n-step return来估计优势函数取得更好的效果。A2C是“同步的”它通常指代一个使用多个并行环境但同步更新的Advantage Actor-Critic实现这些环境共享同一套网络参数收集到的经验被汇总后用于一次统一的梯度更新。2.3 A3C异步并行突破数据关联与效率瓶颈A3C是AC系列中的一个里程碑式工作。它的核心创新在于**“异步”**。它解决了什么问题数据关联性 在单个环境中顺序采样相邻的经验数据(s, a, r, s‘)高度相关用它们来更新神经网络会导致训练不稳定。DQN用经验回放池Replay Buffer通过随机采样打破关联但策略梯度方法由于策略一直在变旧经验可能不再适用直接使用经验回放会引入偏差。训练速度 在CPU上神经网络的前向传播和反向传播是主要瓶颈而与环境交互特别是模拟器可能很快。串行训练导致GPU/CPU大量时间闲置。A3C的解决方案 创建多个“工人”Worker线程每个线程都拥有一个独立的环境副本和一份网络参数的本地副本。这些线程完全异步地运行每个Worker独立地与自己的环境交互收集一段轨迹例如最多n步或直到终止。基于这段本地轨迹Worker计算策略梯度和价值损失。然后Worker将这些计算出的梯度而不是原始数据提交到全局网络。全局网络使用一个共享的优化器如带动量的SGD来应用这些梯度更新全局参数。随后Worker用更新后的全局参数覆盖自己的本地网络参数继续下一轮交互。这种异步架构带来了巨大优势打破数据关联 多个线程在不同环境状态下并行探索它们产生的梯度基于不同的经验在全局网络更新时自然实现了类似“随机采样”的效果。提升效率 CPU密集型的环境模拟和GPU/CNN计算可以重叠进行充分利用了计算资源。在当时的实验中A3C用单台多核CPU机器就能达到甚至超过用GPU和大型经验回放池训练的DQN的速度和性能。无需经验回放池 简化了系统设计避免了离线经验与在线策略不匹配的问题。实操心得 A3C的“异步”是一把双刃剑。由于不同Worker的本地参数版本可能落后于全局参数好几步这实际上引入了一种带延迟的随机梯度下降有时能起到正则化效果帮助跳出局部最优。但这也意味着算法对学习率等超参数更加敏感。在实践中A2C同步版往往更稳定、更容易复现而A3C在调优后可能获得更好的最终性能。3. 核心细节解析与实操要点理解了宏观框架我们深入到实现层面看看有哪些细节决定了算法的成败。3.1 网络结构设计共享特征层还是独立网络Actor和Critic是两个不同的函数我们需要用神经网络来近似它们。常见的结构有两种独立网络Separate NetworksActor网络输入状态输出动作概率分布Softmax或连续动作参数。Critic网络输入状态输出一个标量值V(s)。优点结构清晰两个网络的学习任务互不干扰更灵活例如可以为Critic使用更深或更宽的网络。缺点参数更多计算量较大且状态的特征提取部分被重复计算。共享特征层Shared Feature Base网络前端是共享的卷积层或全连接层负责从原始输入如图像、状态向量中提取高级特征。在共享特征之后网络分成两个“头”Head一个Actor头一个Critic头。优点参数更少计算效率高。共享的特征提取有助于Actor和Critic对状态形成一致的理解通常能加速训练。缺点Actor和Critic的梯度在共享层汇合可能存在冲突需要仔细调整两个损失函数的权重。如何选择对于图像输入如Atari游戏共享特征层通常是几层CNN几乎是标准做法因为特征提取计算代价高共享能极大提升效率。对于纯向量的状态输入两种都可以。我个人的经验是先从共享网络开始因为它更简单、更快。如果发现性能瓶颈或训练不稳定再尝试使用独立网络进行调试。3.2 策略输出与探索离散与连续动作空间离散动作空间Actor网络最后一层是Softmax输出每个动作的概率。探索通过按概率分布采样来实现。也可以通过在Softmax输出上添加熵正则项Entropy Bonus来鼓励探索防止策略过早收敛到次优确定性策略。连续动作空间这是AC系列算法的优势所在。通常假设动作服从一个对角高斯分布Diagonal Gaussian Distribution。Actor网络输出两个向量均值μ和标准差σ或log标准差。σ通常通过一个独立的可学习参数或一个小网络产生并经过Softplus等函数处理以保证为正。动作通过a ~ N(μ, σ²)采样得到。探索通过分布本身的随机性σ实现。同样可以通过熵正则来防止σ过早缩小到0即策略变成确定性策略停止探索。重要技巧动作缩放与Tanh 实际环境如MuJoCo机器人仿真的动作空间通常有界限如[-1, 1]。我们不会让高斯分布直接输出界内值而是采样一个无界的动作a_unscaled ~ N(μ, σ²)。通过双曲正切函数a_tanh tanh(a_unscaled)将其压缩到(-1, 1)。根据环境要求线性缩放a_tanh到最终的动作范围。关键点 计算动作的对数概率时必须考虑tanh变换带来的雅可比行列式Jacobian公式为log_prob(a_final) log_prob(a_unscaled) - Σ log(1 - tanh²(a_unscaled) ε)。几乎所有深度学习框架的分布库如PyTorch的TanhNormal或TransformedDistribution都自动处理了这个修正但自己实现时千万不能忽略否则梯度是错误的。3.3 优势估计从TD(λ)到GAE前面提到用单步TD误差δ估计优势函数。但这只是其中一种方法方差可能仍然较大。更常用的方法是广义优势估计Generalized Advantage Estimator, GAE。GAE是TD(λ)在优势函数估计上的一个优雅推广。它引入了一个衰减参数λ(0 ≤ λ ≤ 1)通过指数加权平均多步TD误差来估计优势A_t^GAE(γ, λ) Σ_{l0}^{∞} (γλ)^l δ_{tl}其中δ_t r_t γV(s_{t1}) - V(s_t)。参数λ的意义λ → 1 GAE接近蒙特卡洛MC估计使用了直到回合结束的全部信息偏差小但方差高。λ → 0 GAE退化为单步TD误差偏差大但方差低。λ是一个折中 通常在0.9到0.98之间选择在偏差和方差之间取得良好平衡。在A2C/A3C的实现中我们在一段轨迹例如n步结束后用这段轨迹的数据和轨迹终点的V(s_{tn})来递归地计算每一步的GAE优势值。使用GAE几乎是现代策略梯度算法的标配它能显著提升样本效率和训练稳定性。3.4 损失函数与熵正则化AC算法的总损失函数通常是Actor损失、Critic损失和熵正则项的加权和。Critic Loss (价值损失) 目标是让Critic的预测更准确。通常使用均方误差MSE或Huber损失。对于n步回报目标是使V(s_t)逼近n步回报估计值。L_v 0.5 * (V_target - V(s_t))²MSE 其中V_target可以是Σ_{i0}^{n-1} γ^i r_{ti} γ^n V(s_{tn})。Actor Loss (策略损失) 目标是最大化期望优势。我们通过梯度上升来更新在损失函数中通常体现为最小化负的期望。L_p -log π(a_t|s_t) * A_t其中A_t是估计的优势函数如GAE 注意这里取负号是因为优化器通常做最小化。Entropy Bonus (熵正则项) 策略的熵H(π(·|s)) -Σ π(a|s) log π(a|s)衡量了策略的随机性。熵越大探索性越强。为了防止策略过早收敛到某个确定性动作而停止探索我们在损失函数中增加熵项因为优化器最小化损失加熵项相当于鼓励熵增大。L_e -β * H(π(·|s_t))β是熵系数一个超参数总损失L_total L_p c1 * L_v c2 * L_e其中c1是价值损失系数c2是熵系数。c1通常设为0.5或1.0c2则是一个需要仔细调整的超参数初始值可能在0.01左右并随着训练衰减。注意事项 熵系数c2的调整至关重要。初期需要较大的熵鼓励探索后期应减小以让策略收敛。一种常见做法是让c2线性衰减到0或一个很小的值。另外L_p和L_v的量级可能不同观察两者的数值范围必要时调整c1以平衡两者对总梯度的贡献。4. 实操过程与核心环节实现下面我将以在经典连续控制环境Pendulum-v1倒立摆上实现一个共享网络的A2C算法为例拆解关键代码和步骤。我们使用PyTorch框架。4.1 环境与网络定义import gym import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F import numpy as np from torch.distributions import Normal # 定义Actor-Critic共享网络 class ActorCriticNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim256): super().__init__() # 共享特征层 self.shared_fc nn.Sequential( nn.Linear(state_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), ) # Actor头输出高斯分布的均值和log标准差 self.actor_mu nn.Linear(hidden_dim, action_dim) self.actor_log_std nn.Parameter(torch.zeros(1, action_dim)) # 可学习的log_std参数 # Critic头输出状态价值V(s) self.critic nn.Linear(hidden_dim, 1) def forward(self, state): features self.shared_fc(state) mu self.actor_mu(features) # log_std被限制在一个合理范围内防止数值不稳定 log_std self.actor_log_std.clamp(-20, 2) std log_std.exp() value self.critic(features) return mu, std, value def act(self, state, deterministicFalse): 用于环境交互时采样动作 with torch.no_grad(): mu, std, _ self.forward(state) dist Normal(mu, std) if deterministic: action mu # 测试时使用确定性策略 else: action dist.rsample() # 使用rsample以支持重参数化 # 应用tanh变换并计算正确的log概率 action_tanh torch.tanh(action) log_prob dist.log_prob(action) # 修正log概率log_prob - 2*(np.log(2) - action - F.softplus(-2*action)) # 更稳定的计算方式 log_prob - torch.log(1 - action_tanh.pow(2) 1e-6) log_prob log_prob.sum(dim-1) return action_tanh, log_prob def evaluate(self, state, action): 用于计算给定状态-动作对的log概率、熵和价值 mu, std, value self.forward(state) dist Normal(mu, std) # 注意输入的action是tanh后的需要反算回原始分布空间计算log_prob # 为了简化这里假设输入的action是未经过tanh的原始采样值在训练循环中我们会保持一致 # 更严谨的做法是传入原始采样值或者在这里进行反tanh变换。 action_original torch.atanh(torch.clamp(action, -0.999, 0.999)) # 近似反算 log_prob dist.log_prob(action_original).sum(dim-1) dist_entropy dist.entropy().sum(dim-1) return log_prob, dist_entropy, value4.2 A2C训练循环核心这里展示一个简化的同步A2C训练步骤使用多个环境并行收集数据。def compute_gae(next_value, rewards, masks, values, gamma0.99, lambda_0.95): 计算GAE优势函数和回报目标值 values values [next_value] gae 0 returns [] advantages [] # 从后向前计算 for step in reversed(range(len(rewards))): delta rewards[step] gamma * values[step 1] * masks[step] - values[step] gae delta gamma * lambda_ * masks[step] * gae advantages.insert(0, gae) # 在列表头部插入 returns.insert(0, gae values[step]) # R_t A_t V(s_t) return returns, advantages def a2c_update(agent, optimizer, states, actions, log_probs_old, returns, advantages, clip_param0.2): 执行一次PPO风格的裁剪更新A2C通常不用裁剪这里展示更稳定的PPO-Clip更新 # 转换为张量 states torch.stack(states) actions torch.stack(actions) returns torch.stack(returns).detach() advantages torch.stack(advantages).detach() # 标准化优势函数这是一个非常重要的技巧 advantages (advantages - advantages.mean()) / (advantages.std() 1e-8) # 评估当前策略 log_probs, entropy, values agent.evaluate(states, actions) # 计算策略损失PPO-Clip ratios torch.exp(log_probs - log_probs_old.detach()) surr1 ratios * advantages surr2 torch.clamp(ratios, 1 - clip_param, 1 clip_param) * advantages policy_loss -torch.min(surr1, surr2).mean() # 计算价值损失 value_loss 0.5 * F.mse_loss(values.squeeze(-1), returns) # 总损失 entropy_coef 0.01 value_coef 0.5 loss policy_loss value_coef * value_loss - entropy_coef * entropy.mean() # 反向传播与优化 optimizer.zero_grad() loss.backward() # 梯度裁剪防止爆炸 torch.nn.utils.clip_grad_norm_(agent.parameters(), max_norm0.5) optimizer.step() return loss.item(), policy_loss.item(), value_loss.item()4.3 主训练流程# 初始化 env_id Pendulum-v1 state_dim 3 # Pendulum的状态维度 action_dim 1 # Pendulum的动作维度 num_envs 8 # 并行环境数量 n_steps 128 # 每个环境每次交互的步数 agent ActorCriticNetwork(state_dim, action_dim) optimizer optim.Adam(agent.parameters(), lr3e-4, eps1e-5) # 创建多个环境 envs [gym.make(env_id) for _ in range(num_envs)] states [env.reset()[0] for env in envs] # Gymnasium API返回 (obs, info) max_episodes 10000 print_interval 20 for episode in range(max_episodes): # 用于存储轨迹的列表 batch_states, batch_actions, batch_log_probs [], [], [] batch_rewards, batch_masks, batch_values [], [], [] # 并行收集n_steps数据 for step in range(n_steps): state_tensor torch.FloatTensor(np.array(states)).unsqueeze(1) if state_dim1 else torch.FloatTensor(np.array(states)) with torch.no_grad(): actions, log_probs agent.act(state_tensor) _, _, values agent.evaluate(state_tensor, actions) # 在各个环境中执行动作 next_states, rewards, dones, truncs, infos [], [], [], [], [] for i, env in enumerate(envs): action_np actions[i].cpu().numpy() next_state, reward, terminated, truncated, info env.step(action_np) next_states.append(next_state) # Pendulum的奖励范围是[-16, 0]可以归一化到[-1,0]附近 batch_rewards.append(reward / 10.0) done terminated or truncated batch_masks.append(0.0 if done else 1.0) # 折扣掩码终止时为0 dones.append(done) # 存储数据 batch_states.append(state_tensor) batch_actions.append(actions) batch_log_probs.append(log_probs) batch_values.append(values.squeeze(-1)) # 更新状态 states next_states # 如果有环境终止则重置 for i, done in enumerate(dones): if done: states[i] envs[i].reset()[0] # 收集完一个批次后计算最后状态的价值 last_state_tensor torch.FloatTensor(np.array(states)) with torch.no_grad(): _, _, next_value agent.evaluate(last_state_tensor, torch.zeros_like(last_state_tensor)) next_value next_value.squeeze(-1) # 计算GAE和回报 returns, advantages compute_gae(next_value, batch_rewards, batch_masks, batch_values) # 将列表展平准备更新 flat_states torch.cat(batch_states).view(-1, state_dim) flat_actions torch.cat(batch_actions).view(-1, action_dim) flat_log_probs_old torch.cat(batch_log_probs).view(-1) flat_returns torch.cat(returns).view(-1) flat_advantages torch.cat(advantages).view(-1) # 执行多次策略更新例如PPO的多个epoch num_epochs 4 for _ in range(num_epochs): loss, p_loss, v_loss a2c_update(agent, optimizer, [flat_states], [flat_actions], flat_log_probs_old, [flat_returns], [flat_advantages]) # 日志输出 if episode % print_interval 0: avg_reward np.mean([sum(r) for r in np.array(batch_rewards).reshape(n_steps, num_envs).T]) print(fEpisode {episode}, Loss: {loss:.4f}, Policy Loss: {p_loss:.4f}, fValue Loss: {v_loss:.4f}, Avg Reward: {avg_reward:.2f})5. 常见问题与排查技巧实录在实际实现和训练AC系列算法时你会遇到各种各样的问题。下面是我踩过的一些坑和对应的排查思路。5.1 训练不稳定回报曲线剧烈震荡或崩溃这是最常见的问题。可能原因1学习率过高。排查 这是首要怀疑对象。尝试将学习率降低一个数量级例如从3e-4降到3e-5。技巧 使用学习率调度器如线性衰减或余弦退火在训练后期降低学习率有助于稳定收敛。可能原因2优势函数未标准化。排查 你是否在每一批更新数据中对优势函数A_t执行了减均值、除标准差的标准化操作如果没有请务必加上。这能稳定梯度幅度是稳定训练的关键一步。代码检查advantages (advantages - advantages.mean()) / (advantages.std() 1e-8)可能原因3梯度爆炸。排查 监控网络权重的梯度范数。在PyTorch中可以在loss.backward()和optimizer.step()之间插入梯度裁剪。技巧torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm0.5)将梯度范数裁剪到0.5。可能原因4价值函数训练滞后或超前。排查 观察价值损失L_v和策略损失L_p的量级。如果价值损失长期远大于策略损失Critic可能没学好给Actor提供了垃圾信号反之如果Critic学得太快优势估计可能不准确。调整损失系数c1价值损失权重。经验值c1通常设为0.5或1.0。可以尝试在0.2到2.0之间调整。5.2 智能体完全不探索策略很快收敛到次优解可能原因1熵系数衰减过快或初始值太小。排查 检查策略熵值。在训练初期熵应该保持在一个相对较高的水平。如果熵值迅速下降到接近0说明探索不足。解决 增大熵系数c2的初始值如从0.01调到0.1并减缓其衰减速度或者在前一定比例的更新中保持熵系数不变。可能原因2连续动作空间的标准差参数初始化不当或学习受阻。排查 观察Actor网络输出的log_std值。如果它被初始得太小或梯度太小导致无法有效更新动作分布就会很窄。解决 确保log_std是可学习的参数并给予合适的初始化如0或-0.5。也可以考虑为log_std设置一个独立于共享网络的小网络或者使用状态依赖的标准差。5.3 回报不增长智能体“摆烂”可能原因1奖励尺度问题。排查 环境给出的原始奖励可能过大或过小导致梯度异常。例如Pendulum的奖励在[-16,0]而某些游戏的得分可能成千上万。解决奖励缩放Reward Scaling。将每批数据的奖励进行标准化减均值除标准差或者简单地除以一个常数如Pendulum除以10。这是一个非常强大且常用的技巧。可能原因2价值函数初始化或输出范围问题。排查 Critic网络最后一层初始化不当可能导致初始价值预测偏离实际回报太远。解决 将Critic输出层的权重初始化为很小的值如nn.init.orthogonal_(layer.weight, gain0.01)偏置初始化为0。对于已知回报范围的环境也可以对Critic输出使用缩放。可能原因3GAE的λ或折扣因子γ设置不当。排查 λ过小接近0可能视野太短看不到长期收益γ过小则智能体过于短视。经验值 γ通常设在0.99长周期任务到0.999非常长的任务。λ通常设在0.9到0.98之间。可以尝试微调这两个参数。5.4 复现A3C时性能不如论文或极不稳定可能原因异步带来的延迟和策略不一致。排查 A3C的不同Worker可能使用着相差好几步的旧策略参数与环境交互这本身是一种噪声源。解决使用梯度裁剪 这是必须的。降低学习率 异步算法通常需要比同步算法更低的学习率。尝试A2C 如果你只是追求稳定复现和好的性能同步的A2C即上述代码中多个环境并行但同步更新往往是更简单、更可靠的选择。许多后来的研究如PPO也默认使用同步并行环境。A3C的异步思想更适用于分布式计算资源受限的特定场景。5.5 调试工具与技巧可视化是关键绘制平滑后的回报曲线如每100步的平均回报。绘制策略熵曲线监控探索程度。绘制价值损失和策略损失曲线观察训练动态。绘制梯度范数检查是否爆炸或消失。单元测试测试你的log_prob计算是否正确。对一个简单的高斯分布手动计算几个样本的概率与你的函数输出对比。测试GAE计算。用一个小的人工轨迹手动计算TD误差和GAE与代码输出对比。简化问题先在极简单的环境如CartPole-v1上验证算法基本逻辑是否正确。使用确定性环境种子确保实验可复现。AC系列算法是深度强化学习的基石之一。从基础的AC到A2C、A3C再到后来更强大的PPO、SAC等其核心思想——用一个Critic来指导Actor的更新——始终贯穿其中。理解其中的每一个细节亲手实现一遍并耐心地调试超参数你才能真正掌握这门让智能体从零开始学习复杂技能的艺术。记住在强化学习中没有“银弹”超参耐心、细致的观察和基于理解的调试才是通往成功的关键。