
1. 从“数据”到“连续动作”到底在解决什么问题连续动作空间的强化学习一直是让不少人头疼的方向。离散动作还能用Q表格、DQN那一套枚举但一旦动作变成机械臂关节角度、无人机推力、自动驾驶的油门刹车转向动作维度变成连续实数传统值函数方法立刻就不够用了。这个系列走到第四篇核心要解决的就是怎么用基于模型的方法把一批离线数据变成能输出连续动作的策略并且用Python工具箱把它跑通。我最初接触这个方向的时候踩过一个很典型的坑直接拿离散动作的DQN改一改把输出层换成连续值结果训练完全不收敛。后来才明白连续动作空间里策略梯度和基于模型的方法才是正路。而基于模型的强化学习MBRL最大的吸引力在于样本效率——真实环境交互一次可能很贵机器人磨损、自动驾驶危险、工业设备损耗但如果能学一个环境动力学模型就可以在“想象”中大量采样把数据利用率拉满。这篇内容适合谁如果你已经写过基础的强化学习代码知道什么是状态、动作、奖励但一到连续控制就卡壳或者你手头有一批离线轨迹数据想把它变成可用的控制器再或者你只是想找一个能直接跑的Python工具箱把MBRL的流程走一遍——那这篇就是给你准备的。我会从整体设计思路讲起然后拆核心细节、实操步骤、参数选择最后把常见坑和排查方法整理成速查表。全程用从业者视角不绕弯子。2. 整体设计与思路拆解2.1 为什么选“基于模型”而不是纯无模型先说清楚一个选择逻辑。无模型方法比如PPO、SAC在连续动作上确实能work但它们对样本的胃口极大。我实测过一个六自由度机械臂的到达任务SAC大概需要几十万到上百万步交互才能稳定而基于模型的方法在几万步量级就能看到像样的策略。差距在哪无模型方法把“环境规律”隐式地编码在策略网络里每一步都要靠真实交互去纠偏基于模型的方法则显式地学一个动力学模型然后让策略在模型里反复试错。这里有个关键权衡模型误差会累积。你学的动力学模型不可能完美策略在模型里表现好放到真实环境可能崩。所以MBRL的工程重点一半在模型精度一半在怎么控制“模型利用”的度。常见做法是限制策略在模型里的 rollout 长度或者用模型集成ensemble来估计不确定性不确定性高的地方就少信模型、多信真实数据。我个人的经验是如果你的真实环境交互成本高、数据量有限MBRL的性价比明显更高如果环境便宜、可以随便采样那无模型方法省心。这个判断决定了你整个工具箱的架构方向。2.2 工具箱的整体架构分层这个Python工具箱我把它分成四层从下到上依次是数据层负责离线轨迹的加载、清洗、归一化。连续动作对数据尺度非常敏感关节角度可能是[-π, π]推力可能是[0, 1000]不归一化直接喂网络梯度会炸。模型层动力学模型输入是状态动作输出是下一状态和奖励。我一般用概率神经网络集成输出均值和方差方差用来做不确定性惩罚。策略层连续动作策略网络输出高斯分布的均值和标准差或者直接用确定性策略加噪声。训练层把模型rollout和真实数据混合做策略优化同时定期用真实数据更新模型。这个分层的好处是每一层可以独立替换。比如你动力学模型想换成高斯过程只动模型层策略想换成扩散策略只动策略层。工程上解耦调试起来省心。2.3 连续动作输出的核心设计考量连续动作和离散动作最大的区别在于输出形式。离散动作是softmax出一组概率连续动作通常有两种高斯策略网络输出均值μ和标准差σ动作从N(μ, σ²)采样。优点是天然带探索缺点是σ需要约束用softplus或exp否则容易塌缩或爆炸。确定性策略外部噪声网络直接输出动作训练时加OU噪声或高斯噪声。优点是推理时干净缺点是探索靠外部噪声调参麻烦。我在这个工具箱里默认用高斯策略因为它在MBRL里和模型的不确定性结合得更自然。标准差我用softplus加一个下界比如1e-3防止训练早期σ直接归零导致完全不探索。这个细节后面实操部分会展开。3. 核心细节解析与实操要点3.1 动力学模型集成网络与不确定性估计动力学模型是整个MBRL的心脏。我用的结构是概率集成网络训练N个通常5到7个独立的神经网络每个网络输出下一状态的均值和方差。预测时把所有网络的均值平均作为预测把网络之间的分歧作为不确定性。为什么用集成而不是单个贝叶斯网络因为集成实现简单、并行友好而且实测不确定性估计质量够用。单个贝叶斯网络比如MC Dropout在连续控制任务上往往低估不确定性导致策略钻空子。模型训练的损失函数是负对数似然# 概率集成网络的单网损失 def nll_loss(mu, log_var, target): # mu, log_var: 网络输出 # target: 真实下一状态 precision torch.exp(-log_var) return torch.mean(0.5 * precision * (target - mu)**2 0.5 * log_var)这里有个实操要点log_var要裁剪。我一般把log_var限制在[-10, 2]之间防止方差过大导致损失被忽略或者方差过小导致数值不稳定。这个范围是试出来的太小模型不敢表达不确定性太大训练发散。3.2 数据预处理归一化与轨迹切分连续动作的数据预处理我踩过最深的坑就是归一化不一致。训练模型时用一套均值和方差训练策略时又用另一套结果策略在模型里表现好一放到真实环境就废。正确做法是把归一化参数存下来模型和策略共用同一套。具体步骤把所有状态拼成矩阵算每个维度的均值和标准差。动作同理但注意动作的归一化范围要和策略输出范围对齐。奖励一般不做归一化但可以做一个缩放比如除以最大奖励让价值网络好训。轨迹切分也很关键。离线数据往往是一条条长轨迹直接整条喂给模型梯度会受长序列影响。我一般切成固定长度比如20到50步的片段既能捕捉短期动态又不会让模型负担太重。切分时注意不要跨轨迹边界否则模型会学到不存在的转移。3.3 策略优化在模型里rollout的正确姿势策略优化的核心循环是从真实数据里采一批初始状态在模型里rollout H步用这H步的奖励和做策略梯度。这里H的选择很讲究H太小比如1到3策略看不到长期后果学不到需要多步规划的任务。H太大比如50以上模型误差累积策略会利用模型错误真实表现崩。我的经验值是H取10到20具体看任务的时间尺度。如果是快速反应任务比如平衡H可以小如果是规划型任务比如导航H要大一些但要配合不确定性惩罚。不确定性惩罚怎么加在rollout时每一步都用集成网络的分歧算一个惩罚项加到奖励里# rollout时的不确定性惩罚 def penalized_reward(reward, ensemble_disagreement, beta1.0): return reward - beta * ensemble_disagreementbeta是惩罚系数我一般从0.1开始调太大策略会变得过于保守太小又压不住模型利用。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把环境搭起来。这个工具箱依赖PyTorch、NumPy、Gym或Gymnasium。我推荐用conda建一个干净环境避免和系统Python打架。conda create -n mbrl python3.9 conda activate mbrl pip install torch numpy gymnasium matplotlib如果你要用MuJoCo环境做连续控制测试还需要额外装mujoco和gymnasium的mujoco扩展。注意版本匹配PyTorch和CUDA的对应关系去官网查别装错。提示Python 3.9是比较稳的选择3.10以上有些老库会有兼容问题。如果你只是跑这个工具箱3.9足够。4.2 数据加载与归一化实现假设你有一批离线轨迹存成npz或者pickle。加载后先做统计import numpy as np def compute_normalizer(states, actions): state_mean states.mean(axis0) state_std states.std(axis0) 1e-6 action_mean actions.mean(axis0) action_std actions.std(axis0) 1e-6 return { state_mean: state_mean, state_std: state_std, action_mean: action_mean, action_std: action_std } def normalize(x, mean, std): return (x - mean) / std def denormalize(x, mean, std): return x * std mean这里1e-6是防止除零。归一化参数一定要存盘推理时用同一套。我见过有人训练时归一化、推理时忘了结果动作输出完全不对排查了半天。4.3 动力学模型训练完整流程模型训练我一般跑200到500个epoch每个epoch随机采一批片段。关键参数参数推荐值说明集成数量5-7太少不确定性不准太多训练慢隐藏层2层×256连续控制够用再大容易过拟合学习率3e-4Adam默认实测稳batch size256片段数×片段长度训练轮数300看验证损失早停训练循环大致这样for epoch in range(num_epochs): for batch in dataloader: states, actions, next_states batch total_loss 0 for model in ensemble: mu, log_var model(states, actions) log_var torch.clamp(log_var, -10, 2) loss nll_loss(mu, log_var, next_states) total_loss loss optimizer.zero_grad() total_loss.backward() optimizer.step()验证集要单独留看模型在没见过的数据上的预测误差。如果验证损失一直降不下去说明模型容量不够或者数据有问题。4.4 策略训练与模型交互策略训练是外层循环。每一轮从真实数据采初始状态。在模型里rollout H步记录奖励和不确定性。算策略梯度更新策略网络。每隔几轮用真实环境采一批新数据更新模型。这里有个工程细节模型更新和策略更新的频率。我一般策略更新5到10次模型更新1次。因为模型更新太频繁策略刚适应旧模型模型一变又得重来训练不稳定。策略网络输出高斯分布采样动作def sample_action(policy, state): mu, log_std policy(state) std torch.exp(log_std) dist torch.distributions.Normal(mu, std) action dist.rsample() # 重参数化采样 return action, dist.log_prob(action).sum(dim-1)用rsample而不是sample是为了让梯度能回传。log_std同样要裁剪我一般限制在[-5, 0]对应std在[0.0067, 1]之间。5. 常见问题与排查技巧实录5.1 训练不收敛的典型原因连续动作MBRL训练不收敛我总结下来主要是这几类归一化问题状态或动作尺度差异大网络学不动。排查方法打印归一化后的数据统计均值接近0、标准差接近1才对。学习率过大模型或策略损失震荡。先把学习率降到1e-4试试。不确定性惩罚系数不当beta太大策略不探索太小策略钻模型空子。观察rollout奖励和真实奖励的差距差距大就加大beta。rollout长度过长模型误差累积。把H从20降到10看看。5.2 模型预测误差大的排查模型预测不准先看是训练误差大还是验证误差大训练误差大模型容量不够或训练不够。加宽网络、加训练轮数。验证误差大过拟合。加正则化、减网络容量、加数据。两者都大数据本身有问题。检查数据里有没有异常值、缺失值轨迹切分有没有跨边界。我遇到过一次模型怎么训都误差大最后发现是数据采集时状态记录有延迟状态和动作对不齐。这种问题只能靠检查数据源头解决。5.3 策略在模型里好、真实环境崩这是MBRL最经典的坑。原因基本是模型利用策略找到了模型预测错误的地方在那里刷高奖励。解决办法加大不确定性惩罚beta。缩短rollout长度H。增加集成数量让不确定性估计更准。混合真实数据和模型数据训练策略别纯靠模型。我一般会监控一个指标策略在模型里的平均奖励 vs 在真实环境的平均奖励。差距超过30%就要警惕了。5.4 常见问题速查表现象可能原因排查方向损失NaN学习率过大、log_var未裁剪降学习率、加clamp策略不探索log_std塌缩、beta过大检查log_std下界、降beta模型误差大数据未归一化、容量不足检查归一化、加网络宽度真实环境崩模型利用加beta、减H、加集成训练慢集成太多、rollout太长减集成数、减H注意调参时一次只动一个别同时改学习率、beta和H否则出了问题不知道是谁的锅。6. 工具箱扩展与实战建议6.1 从单任务到多任务的扩展思路这个工具箱目前是单任务设计但实际项目里经常要处理多任务。扩展方向有两个条件策略把任务ID作为额外输入喂给策略和模型一个网络处理多个任务。优点是参数共享缺点是任务差异大时互相干扰。多模型多策略每个任务一套模型和策略互不干扰。优点是干净缺点是数据利用率低。我一般先用条件策略试如果任务间冲突明显再拆开。判断冲突的方法很简单看多任务训练时单任务性能比单独训练差多少差太多就拆。6.2 真实机器人部署的注意事项如果你要把训练好的策略部署到真实机器人有几个坑必须提前防推理频率策略网络推理要够快一般要求10Hz以上。如果太慢考虑模型量化或剪枝。安全边界动作输出一定要加限幅防止网络输出异常值把机器人搞坏。状态估计误差真实环境的状态估计有噪声训练时最好加噪声增强让策略对状态噪声鲁棒。我部署过一次机械臂策略训练时状态是完美的真实环境编码器有噪声策略抖动明显。后来在训练时给状态加了高斯噪声问题解决。6.3 后续可以深入的方向这个工具箱跑通之后可以往几个方向深挖一是把动力学模型换成更表达力强的结构比如Transformer处理长序列依赖二是引入离线强化学习的保守估计让策略在数据分布外更保守三是做模型和策略的联合优化而不是分开训练。每个方向都有不少论文和开源实现可以参考但核心流程和这个工具箱是一致的。我个人在实际操作中的体会是MBRL的难点不在算法本身而在工程细节归一化、不确定性、rollout长度、更新频率这些看起来不起眼的地方往往决定了训练能不能收敛。把这篇里的参数和排查表存下来遇到问题对着查能省不少时间。