资讯动态

DPPO分布式强化学习:从PPO到并行架构的工程实践

发布时间:2026/9/17 19:02:14 来源:尧图企业网站定制
DPPO 到底是什么以及为什么值得花时间搞懂它强化学习Reinforcement LearningRL这几年在游戏 AI、机器人控制、推荐系统这些领域火得不行各种算法也是层出不穷。如果你翻过 OpenAI 的论文或者看过一些成熟的 RL 开源项目大概率会撞见 PPOProximal Policy Optimization这个名字。它几乎成了深度学习框架里策略梯度方法的默认选项。但如果只是单机跑 PPO当你把任务从 CartPole 这种玩具环境换到真正的机械臂仿真、多智能体协作或者大规模推荐场景时训练速度立刻会成为瓶颈。这时候Distributed PPODPPO就派上用场了。简单说DPPO 就是把 PPO 的训练过程从单机单卡扩展到多机多卡、多进程并行的一种工程化方案。它不改变 PPO 的核心数学原理而是把“采样”和“更新”这两个环节拆开让它们各司其职、并行运转。我最初看 DPPO 相关论文的时候被架构图绕得头晕后来自己动手实现了一遍才真正搞明白它到底在解决什么问题各个模块之间又是怎么协作的。这篇笔记我准备从 PPO 本身讲起一步步拆到分布式架构再到伪代码逐段解读、超参调优和实战踩坑希望能让和我一样正在啃 DPPO 的同学少走点弯路。1. 内容整体设计与思路拆解为什么 PPO 需要“分布式”这层外衣1.1 PPO 核心机制快速回顾重要性采样、clip 限制、GAE 优势估计要理解 DPPO必须先扎实掌握 PPO 的原理因为 DPPO 的分布式设计全都是围绕着 PPO 的这三个核心组件展开的。第一个组件是重要性采样Importance Sampling。PPO 是 on-policy 算法按理说每次更新都只能用当前策略采出来的数据。但这样样本利用率太低跑一步丢一步。PPO 的想法是允许我们用一个“旧策略”去采样然后在更新“新策略”的时候通过重要性权重对旧数据重新加权。这个权重就是新策略概率除以旧策略概率即 r_t(θ) π_θ(a_t|s_t) / π_θ_old(a_t|s_t)。有了这个权重旧数据就能被重复利用几轮而不是只更新一次就作废。第二个核心组件是 clip 目标函数。传统策略梯度方法对学习率步长非常敏感步子太大直接导致策略崩掉步子太小半天不动。TRPO 用 KL 散度做约束来限制每步更新的幅度但实现复杂、计算量大。PPO 的设计者换了个更简单的思路不显式约束 KL而是直接把重要性权重限制在一个范围内。目标函数写成L_CLIP(θ) E[ min( r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1ε) * A_t ) ]这个公式读起来有点绕但实际含义非常直觉当某个动作比旧策略好优势 A 为正我们最多让这个动作的概率增加 1ε 倍当它在变差优势为负我们最多让它减少 1-ε 倍。超过这个范围的梯度直接截断。这个“暴力”但有效的机制让 PPO 在实现对 TRPO 近似效果的同时计算和维护成本低了一个量级。clip 参数 ε 通常取 0.2这是论文和实践中都比较稳的默认值。第三个组件是 GAEGeneralized Advantage Estimation广义优势估计。优势函数 A_t 衡量的是“当前状态动作相比于平均值好多少”是策略梯度里最关键的量。GAE 是一种平衡偏差和方差的折中方案通过一个 λ 参数来平滑 n 步回报。它的伪代码如下def compute_gae(rewards, values, dones, gamma0.99, lam0.95): rewards: [T] 每步奖励 values: [T1] 每条轨迹最后要多算一个 bootstrapped 值 dones: [T] 是否为终止状态 gae 0 advantages np.zeros_like(rewards) for t in reversed(range(len(rewards))): if dones[t]: # 终止状态不需要算后续回报 delta rewards[t] - values[t] gae delta else: delta rewards[t] gamma * values[t1] - values[t] gae delta gamma * lam * gae advantages[t] gae return advantagesGAE 的 λ 取了 0只看一步 TD 误差和 1看完整 Monte Carlo 回报之间的一个值实践中 λ0.95 是个非常常用的选择。它让优势估计既不会像 MC 那样方差爆炸又不会像一步 TD 那样偏差太大。1.2 单机 PPO 的瓶颈在哪采样与更新的串行矛盾理解了 PPO 的核心之后就能明白单机跑的痛点了。在标准 PPO 中主循环流程是用当前策略 π_θ 和环境互动采集一批轨迹数据。用这批数据计算 GAE 和 PPO 损失。做 K 轮 mini-batch 梯度更新论文里 K 通常取 3 到 15。回到第 1 步用更新后的策略重新采样。这个流程最耗时的往往不是梯度更新而是第 1 步“与环境互动采样”。尤其是机器人控制任务环境仿真的渲染、物理引擎的步进计算都非常昂贵。单机上就算你用 GPU 加速神经网络推理环境的采样速度也只是在 CPU 上跑一个串行的循环根本无法把 GPU 的算力吃满。换句话说单机 PPO 是典型的“采得慢、训得快”——时间全浪费在了采样上。这类瓶颈在计算机系统里太常见了解决思路也很经典生产者和消费者解耦。既然训练快、采样慢那我就多开几个采样器生产者同时跑环境让它们源源不断地把数据喂给训练器消费者。这就是 DPPO 的核心架构动机它不是一种新的强化学习数学算法而是一种分布式系统设计模式在 RL 里的应用。1.3 DPPO 的设计哲学将采样Rollout与学习Learning解耦PPO 的论文《Proximal Policy Optimization Algorithms》实际上已经提到了分布式实现但真正把 DPPO 这个名字打响的是 OpenAI 的另一篇工作《Emergence of Locomotion Behaviours in Rich Environments》。在这篇论文中OpenAI 的训练系统就是分布式 PPO大量 worker 并行跑仿真环境收集经验集中式的 learner 负责梯度更新两者之间通过参数服务器机制保持同步。这种“采样-学习”分离的架构有几个直接好处一是采样吞吐量线性提升。多开一个 worker就多一条独立的环境轨迹流。理论上采样速度可以随 worker 数量近似线性扩展直到通信和 learner 更新成为新的瓶颈。二是训练与采样互不阻塞。learner 更新参数的时候worker 根本不用停下来等待。它只需要在每轮采样之前去参数服务器拉取一次最新参数即可。这样可以保证 worker 始终在“近似最新”的策略下采样配合 PPO 的 clip 机制吸收参数延迟带来的误差效果非常稳。三是硬件利用率更合理。在大规模训练时learner 可以用高性能 GPU 机器workers 可以用一堆便宜的 CPU 机器或 GPU 实例跑仿真。因为两者负载不同可以分别优化硬件选型综合成本反而更低。DPPO 在架构上不是唯一的选择但它在实现难度、稳定性和扩展性之间取得了很好的平衡。这也是为什么它成为很多工业级 RL 训练框架比如 Ray RLlib、Sample Factory、Impala 的一些变种的默认算法之一。2. 核心细节解析与实操要点DPPO 的架构模块和关键机制2.1 Learner-Worker 架构谁是主角谁是配角DPPO 的经典架构由图中的几个角色构成参数服务器、learner 和 workers。参数服务器是整个系统的“数据中枢”但不一定是一个独立进程。在小规模实现里它可以就是 learner 内部维护的一份参数字典。它的职责是存储全局策略参数提供“发布参数”和“获取参数”两个接口。在分布式实现中参数服务器会处理并发访问和版本控制避免多个 worker 看到不一致的参数快照。learner 是唯一执行梯度更新的角色。它从参数服务器获取当前参数从一个经验缓冲池里取一批轨迹数据计算 PPO 损失并做若干轮梯度下降然后把新参数推回参数服务器。注意learner 自身不与环境交互它的世界里只有数据和梯度。workers 是纯粹的数据生产者。每个 worker 持有一份策略参数的本地副本初始时从参数服务器拉取然后循环执行“采样轨迹 → 把轨迹发送给 learner → 拉取最新参数 → 继续采样”。worker 自身不做梯度计算这大大降低了它的计算负担也让它的实现变得非常轻量。这套架构里有一个容易被忽略的设计细节worker 从参数服务器拉取参数的时机。如果 worker 每采样一步就拉一次参数通信开销会占用大量带宽反而拖垮整体效率。所以通常的做法是worker 每次跑完一整条 trajectory或一个 rollout chunk例如 2048 步之后再同步一次参数。这样既保证了数据的时效性又把通信频率控制在一个合理的水平。2.2 同步更新 vs 异步更新稳定性和吞吐量的权衡DPPO 的实现里有一个重要分叉learner 和 workers 之间是同步Synchronous还是异步Asynchronous协作。同步模式下所有 worker 都必须完成一轮采样、把数据送到 learner 后learner 才做一次更新然后广播新参数给所有 worker再开始下一轮。这种模式逻辑非常简单数据是整齐的批batch训练曲线比较稳定调试方便。但缺点是整体速度被最慢的 worker 拖累——如果有 100 个 worker其中有一个因为机器负载高跑得慢所有人都在等它。这就是经典的木桶效应。异步模式下worker 只要完成一条轨迹就立刻发给 learnerlearner 收到一批数据就更新一次参数广播也是“随时可能发生”。这样系统的吞吐量更高因为不用等待慢 worker。但代价是训练过程可能出现策略滞后、数据分布不均匀的问题。极端情况下learner 刚基于老策略更新完参数、还没来得及广播又有几个 worker 基于更老的参数回传了数据这几批数据就过时了。实操里怎么选我的建议是如果 worker 数量在 16 个以内、环境运行时间比较均匀优先用同步稳定性和可复现性都更好。如果规模大到几十上百个 worker异步几乎是必然选择因为同步模式在规模上来之后反而会频繁触发超时和等待吞吐量不升反降。OpenAI 原始 DPPO 论文里采用的是“半同步”思路每个 worker 跑完一个固定长度 K 的 rollout 段learner 每收到一个完整的 rollout 段就更新一次但 worker 不需要互相等待先到先得。它本质上是异步的但通过固定 rollout 长度让数据粒度一致、更新频率可控效果很好。2.3 关键实现机制数据版本标记与策略时效性分布式系统里最让人头疼的问题就是“数据不一致”。在 DPPO 里这个问题以“策略时效性”的形式出现learner 收到一批数据时这批数据是用哪个版本的策略采的更新当前参数时数据对应的策略参数和当前参数差了多少如果不加控制最终会乱套。一个简单但有效的机制是给参数加版本号。class ParameterServer: def __init__(self): self.params {} self.version 0 self.lock threading.Lock() def publish_params(self, new_params): with self.lock: self.params new_params self.version 1 return self.version def get_params(self): with self.lock: return self.params, self.versionworker 发送给 learner 的每条轨迹除了数据本身还附带一个policy_version字段表示这条轨迹是用哪个版本策略采的。learner 在计算 PPO loss 时不需要把这个版本号用于数学计算但可以用它在日志里监控“数据平均过时程度”。如果发现 worker 采的轨迹平均落后 learner 参数几十个版本说明系统存在严重的策略滞后这时候需要调整同步频率或者减少 worker 规模。这个版本标记机制在单机单进程实现里看着像多此一举但一旦部署到真正的多机环境就变成排查问题的重要抓手。我之前有一次训练效果异常靠的就是版本号日志定位到了某个 worker 因为网络丢包一直在用老参数采样。3. DPPO 的完整训练流程伪代码逐段拆解和参数设计3.1 核心伪代码从 worker 采样到 learner 更新的完整链路把架构模型都理清之后到了最实操的部分。我写一份简化版的 DPPO 伪代码对照着逐段讲。# ------------------------- worker 侧 ------------------------- def worker_rollout(worker_id, env, policy_net, param_server, rollout_len): 每个 worker 的独立循环。 env: 每个 worker 各自的独立环境实例 param_server: 全局参数服务器 rollout_len: 每次采样的轨迹长度如 2048 # 1. 从参数服务器拉取最新参数 params, version param_server.get_params() policy_net.load_state_dict(params) trajectory { states: [], actions: [], rewards: [], dones: [], log_probs: [], values: [] } state env.reset() for step in range(rollout_len): state_tensor torch.FloatTensor(state).unsqueeze(0) with torch.no_grad(): dist, value policy_net(state_tensor) action dist.sample() log_prob dist.log_prob(action).squeeze(0) next_state, reward, done, _ env.step(action.cpu().numpy()[0]) # 记录轨迹 trajectory[states].append(state) trajectory[actions].append(action.cpu().numpy()[0]) trajectory[rewards].append(reward) trajectory[dones].append(done) trajectory[log_probs].append(log_prob.item()) trajectory[values].append(value.item()) state next_state if done: state env.reset() # 轨迹末端做 bootstrap用于计算 GAE with torch.no_grad(): _, last_value policy_net(torch.FloatTensor(state).unsqueeze(0)) trajectory[last_value] last_value.item() trajectory[policy_version] version # 发送轨迹给 learner param_server.send_trajectory(worker_id, trajectory) # 3.2 段的代码会在这里被调用继续循环采样 # ------------------------- learner 侧 ------------------------- def learner_update(param_server, policy_net, opt, batch_size256, ppo_epochs10): learner 的主循环持续从缓冲池中取数据做 PPO 更新。 while True: # 1. 收集一个 batch 的轨迹数据可以是多个 worker 的数据拼起来 trajectories param_server.get_trajectories(min_count1) # 2. 把多条 trajectory 拼接成一个大 batch batch concat_trajectories(trajectories) # 3. 计算 GAE 和 returns advantages compute_gae( batch[rewards], batch[values], batch[dones], gamma0.99, lam0.95 ) returns advantages batch[values] # 4. 标准化优势经验技巧能显著提升稳定性 advantages (advantages - advantages.mean()) / (advantages.std() 1e-8) # 5. 做 K 轮 mini-batch 更新 for _ in range(ppo_epochs): indices np.random.permutation(len(batch[states])) for start in range(0, len(indices), batch_size): idx indices[start:start batch_size] states torch.FloatTensor(batch[states][idx]) actions torch.FloatTensor(batch[actions][idx]) old_log_probs torch.FloatTensor(batch[log_probs][idx]) adv torch.FloatTensor(advantages[idx]) ret torch.FloatTensor(returns[idx]) dist, values policy_net(states) log_probs dist.log_prob(actions).sum(dim-1) # 重要性权重 ratio torch.exp(log_probs - old_log_probs) # PPO clip loss surr1 ratio * adv surr2 torch.clamp(ratio, 1.0 - 0.2, 1.0 0.2) * adv policy_loss -torch.min(surr1, surr2).mean() # value loss value_loss F.mse_loss(values.squeeze(-1), ret) # 熵正则项鼓励探索 entropy dist.entropy().mean() total_loss policy_loss 0.5 * value_loss - 0.01 * entropy opt.zero_grad() total_loss.backward() opt.step() # 6. 发布新参数到参数服务器 new_version param_server.publish_params(policy_net.state_dict()) print(f【learner】version{new_version}, fpolicy_loss{policy_loss.item():.4f}, fvalue_loss{value_loss.item():.4f})这段伪代码已经可以直接照着用 TensorFlow 或 PyTorch 实现。我加几点说明。3.2 逐段解读GAE 计算、目标函数拼装、熵正则项的作用worker 端的逻辑非常机械先同步参数然后用当前策略跑 rollout_len 步把状态、动作、奖励、done、log prob、value 都存下来。有两个细节容易踩坑。第一value 必须存。这里存的是policy_net(state)输出的 critic 网络的估计值用于后续 GAE 计算。如果忘了存 valueGAE 就没法算了。第二轨迹结束位置要多算一个last_value也就是下一个状态的 value 估计。这个值用于引导bootstrap当前轨迹最后一步的优势估计如果没有它轨迹末端的 GAE 就会缺失一项导致优势被系统性低估。learner 端的关键在 GAE 循环和 PPO 目标函数拼装。GAE 的循环是逆着时间方向从后往前算的。为什么因为 GAE 的定义是递归的当前步的 GAE 等于当前步的 TD 误差加上折扣后的下一步 GAE。所以必须从最后一步逐步向前推出所有步的 GAE。很多新手第一次写都会不小心正着遍历算出来的优势全是错的还很难排查。我建议先在纸上手推两步的 GAE确认对了再写代码。PPO 目标函数由三部分组成clip 过的策略损失、价值损失和熵正则项。策略损失就是前面讲的 min(surr1, surr2)价值损失就是 critic 输出的 value 和按照 GAE 算出的 return 之间的均方误差。熵正则项的系数通常选 0.01 或 0.001作用是把策略“撑开”防止策略过早收敛到一个确定性分布、丧失探索能力。优势标准化这一步很值得专门提一下。在单机 PPO 里标准做法就是先算 GAE然后对整个 batch 的 advantages 做一次归一化。这能显著缓解不同任务之间奖励尺度差异带来的训练不稳定问题。实际测试中如果跳过这一步很多环境上 PPO 的 reward 曲线会出现剧烈震荡。分布式环境下数据量大归一化计算成本可以忽略所以不要省这一步。3.3 超参数选择的实战指南batch size、rollout 长度、学习率怎么配DPPO 的超参数和单机 PPO 大体上一致但因为多了分布式维度多了一层约束。我整理了几个关键超参数的选择逻辑都是实测经验。rollout 长度rollout_len。这个参数决定每条 trajectory 包含多少步。太小的话GAE 的前瞻性不足优势估计偏一步 TD太大的话数据时效性下降。在 DPPO 场景下我一般取 2048 步作为默认值。对于任务步长较短的任务比如很多 grid world 控制在 100 步内结束可以适当减小到 512 或 1024。mini-batch size 和 ppo_epochs。论文里取值是 mini-batch size2048ppo_epochs15。但实际跑的时候这个组合在多数任务上会让训练前期更新过于激进。我的经验是 mini-batch 在 256 到 1024 之间、ppo_epochs 在 5 到 10 之间是更稳的配置。尤其分布式环境下数据总量大不需要靠增加 epochs 来榨干每个 batch 的利用价值反而应该多采样、少重复利用保持数据新鲜度。学习率。Actor 和 critic 共用同一个 Adam 优化器学习率一般取 3e-4。如果发现训练中期 reward 突然掉下去有可能是 lr 太大导致策略被 clip 之后又在边缘反复横跳试着把 lr 降到 1e-4 或者用学习率衰减。GAE 中的 λ 和折扣因子 γ。γ 决定智能体看多远0.99 适用于绝大多数任务。λ 建议 0.95不要随意改动。把 λ 调大接近 1会让优势估计偏向 Monte Carlo方差急剧上升调小则会让估计偏差变大。除非你明确知道自己在做什么否则保持 0.95。环境并行数。也就是 worker 数量它直接影响采样吞吐量。在实验室一台 8 核机器上开 8 到 16 个 worker 是比较合理的。每多开一个 worker环境的 Python 解释器、仿真进程都会吃 CPU 资源太多反而因为上下文切换导致总吞吐下降。下面的表格是我自己项目里常用的 DPPO 推荐参数基线新手可以先照这个跑通再逐步调整。参数推荐值说明γ折扣因子0.99长 horizon 任务可以 0.995λGAE 参数0.95兼顾偏差和方差clip ε0.2以论文默认值为准rollout_len2048轨迹长度可根据任务调整mini-batch size256~1024越大更新越稳定但速度慢ppo_epochs5~10每批数据重复利用轮数学习率3e-4Adam 默认可加衰减熵系数0.01任务复杂可调大value loss 系数0.5论文默认worker 数量8~16单机取决于 CPU 核心数4. DPPO 训练中的常见问题与排查技巧实录4.1 问题一reward 曲线几乎不动像条直线这个问题我碰到过不止一次在单机和分布式上都可能出现。常见的罪魁祸首有三个。一是策略网络初始化问题。如果 actor 网络初始化不当初始策略可能把一个动作的概率推到接近 1探索能力趋近于零智能体从头到尾都在重复同一个动作reward 自然不动。解决办法是给网络最后一层输出加一个较小的标准差或者直接用正交初始化orthogonal initializations配合较小的权重缩放。二是熵正则项丢失。有些人在实现时为了省事省略了熵项初期可能看不出问题但训练到一定阶段后策略快速退化成确定性策略进入到“死区”。一旦策略分布坍缩熵为零策略梯度也接近零模型彻底卡死。所以熵项最好不要省。三是数据流断裂。在分布式环境里奖励曲线长时间不动先别急着调参检查 worker 的采样数据有没有真正到达 learner。我当时遇到过一个情况worker 里某个环境实例初始化失败返回的全是零 reward 的空轨迹数据量还很大直接把 learner 的梯度计算带偏了。排查方法是在 learner 侧打日志输出每批数据的 reward mean 和 max看是否合理。4.2 问题二训练一段时间后 loss 突然 nan参数直接崩掉nan 问题在 DPPO 里最常见的来源是 GAE 出现了无穷值而后面的归一化把 nan 扩散到了整个 batch。追查路径是这样先看 reward 有没有可能出现异常大的值比如仿真环境偶尔解算出 1e10 级别的 reward再看 value network 有没有在某个状态上输出极端值导致 TD 误差爆炸最后看 log_prob 计算是不是有日志项为负数的隐式 bug。我建议在训练的前几百轮里对每个 batch 检查一下advantages的 max/min 值并且给 advantage 标准化之前加上一个数值保护的 clamp。另一种更省事的办法是梯度裁剪gradient clipping设为 0.5 到 1.0虽然不解决根因但至少能防止参数一步崩掉给排查留出时间。4.3 问题三worker 数量上去了但总吞吐量没有线性提升理论上 8 个 worker 的采样速度应该是 2 个 worker 的 4 倍实际却经常达不到。这时候瓶颈通常是这几处。先是通信。worker 每次发轨迹给 learner如果序列化方式用的是 Python 的 pickle 加上多进程 Pipe数据量一大就会非常慢。换成共享内存shared memory或 Redis 这种消息队列性能能提升好几倍。我在单机多进程的实现里试过最直观的优化是把轨迹中的 numpy 数组预先转成 BytesIO 再传避免反复序列化。再是 learner 的更新速度。如果 learner 更新耗时太长worker 采完数据都在排队等 learner 消化系统吞吐就会被 learner 的更新周期卡住。优化方向是把 PPO 的 epochs 调小一点或者把 batch 拆细一点减少单次更新的耗时。最后是环境的本身并行度。很多环境的 step 函数里有全局锁或者依赖同一个随机数种子导致多个 worker 共享了同一份状态实际等于在跑同一个环境。要确保每个 worker 创建独立的 env 实例并设置不同 seed。4.4 问题四异步模式下训练震荡严重reward 波动越来越大异步模式下数据分布不平稳是震荡的根源。某个 worker 的参数可能长时间没同步一直在用老策略采集数据而 learner 已经更新了几十版。PPO 的 clip 机制虽然能吸收一定程度上的策略偏移但偏移量太大时clip 的约束反倒会让梯度信号失真训练就开始震荡。解决思路有几种。一是在 worker 侧加一个“参数最大滞后版本数”的检查如果当前本地参数版本落后全局版本超过某个阈值比如 10就强制重新拉取参数再采样。二是调大 clip 系数到 0.3 左右给策略偏移留出更多空间。三是把 rolluot 长度调短让 worker 更频繁地同步参数缩小数据与当前策略之间的时差。我个人的经验是异步模式适合环境步进时间差异很大的场景比如有些 worker 跑的是复杂场景、有些是简单场景但这时候对监控的要求也更高。一定要记录版本滞后这个指标它比 reward 更能反映系统的健康状况。动手做一个最小 DPPO 项目的实操建议这篇笔记写到这里DPPO 的核心原理和实现细节基本都覆盖了。如果看到这里你有点跃跃欲试我给几条从零开始落地的建议。第一别一上来就搞多机。先把单机多进程版的 DPPO 跑通。用 Python 的 multiprocessing 模块一个进程跑 learner四到八个进程跑 worker进程间用 multiprocessing.Queue 或者 shared memory 通信。复杂度可控最多几十行代码但能让你把架构中的每个角色吃透。第二调试时用最简单环境。不要在 MuJoCo 或者 Isaac Gym 上直接调 DPPO先从 CartPole-v1、HalfCheetah 这种轻量环境开始一方面是训练快另一方面是环境本身没有太多干扰因素更容易验证算法实现是否正确。第三把日志系统做扎实。真实调 DPPO 和单机 PPO 最大的不同是单机代码可以用 print 断点调试分布式系统的调试几乎完全依赖日志。建议记录每个 worker 的轨迹长度、采样耗时、参数版本、发送数据大小、learner 每个 batch 的 reward mean/std、政策 loss、value loss、熵值。这些指标能帮你快速定位问题在哪一侧。根据我个人经验DPPO 最大的学习门槛不是数学而是从“单机单线程”的思考方式切换到“多进程分布式协作”的思考方式。一旦你真的动手把一个最小系统跑起来看着多个 worker 同时采数据、learner 稳定更新参数、reward 曲线一步步爬上去那种感觉比单纯读论文要扎实得多。希望这份笔记能帮你顺利跨过这个门槛。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价