首页 / 资讯中心 / 文章详情

模仿学习算法包实战:BC、DAgger与GAIL训练调参与排错

模仿学习算法包实战:BC、DAgger与GAIL训练调参与排错 ★ FEATURED ARTICLE
简介面向深度强化学习与模仿学习方向的学习者这份代码包围绕从专家轨迹中学习策略这一主题覆盖 PPO、AIRL、BC、DRIL、FAIRL、GAIL、GMMIL、PUGAIL、RED 等算法并提供仅状态模仿、吸收状态指示器与 R1 梯度正则化等实验选项适合算法复现、课程项目与基线对比。压缩包共 27 个文件、约 999KB其中 6 个 Python 脚本承担训练、评估与环境交互逻辑17 张 PNG 汇总各算法训练与测试回报曲线2 个 Markdown 文档说明使用方法另附专家轨迹数据文件目录紧凑、模块边界清楚。已有 1068 人学习读者可从中获得可运行的模仿学习实验框架、专家轨迹加载与策略优化示例、不同算法的结果对照图以及排查训练不稳定与回报波动的参考思路并据此理解生成对抗式模仿与行为克隆的差异。1. 奖励函数写不出来时imitation-learning 模仿学习算法包能接住哪类任务控制类项目最常见的卡点不在模型大小而在奖励函数机械臂要的是平滑轨迹写个距离惩罚训练出来全是抖动策略梯度在稀疏奖励下几乎不更新调两周不如人遥操作十分钟。imitation-learning 这套 Python 实现的模仿学习算法包走的是另一条路——把专家演示当监督信号先用行为克隆BC把策略立起来再用 DAgger 补上分布偏移最后用 GAIL 在环境里做对抗式精调。它面向的是已经有脚本专家或遥操作数据、但奖励写不出来或写不准的 deep-reinforcement-learning 场景判断自己适不适合标准很朴素你能把一条条轨迹存成(obs, action, next_obs, done)的数组并且能让策略真正跑进环境里这套东西就能用。2. 三种范式的代码骨架BC 打底、DAgger 补偏移、GAIL 做对抗精调模仿学习算法包最容易让人误判的地方是把三个算法当成三选一的替代品。实际工程里它们是阶梯BC 决定你有没有一个能跑的策略DAgger 决定这个策略在离线和在线之间能不能接住GAIL 决定最终性能能不能逼近专家。这一章按数据流顺序把三块骨架拆开代码用最常见的组织方式写仓库里的类名函数名可能不一样但张量形状和损失形式是对得上的。2.1 轨迹容器与数据协议先把专家数据对齐成 (obs, action, next_obs, done)新手翻车十有八九不在算法在数据协议。专家数据是离线采的策略数据是在线采的如果两边字段顺序、dtype、done的表示方式不一致后面所有 loss 曲线都没有参考价值。我一般先写一个两种数据源共用的环形缓冲把协议钉死。import numpy as np class TrajectoryBuffer: 环形缓冲专家轨迹和策略 rollout 共用同一套字段协议 def __init__(self, obs_dim, act_dim, capacity200_000): self.capacity capacity self.obs np.zeros((capacity, obs_dim), dtypenp.float32) self.act np.zeros((capacity, act_dim), dtypenp.float32) self.rew np.zeros((capacity, 1), dtypenp.float32) self.next_obs np.zeros((capacity, obs_dim), dtypenp.float32) self.done np.zeros((capacity, 1), dtypenp.float32) # 用 0/1不用 bool self.ptr, self.size 0, 0 def add(self, obs, act, rew, next_obs, done): i self.ptr self.obs[i], self.act[i] obs, act self.rew[i], self.next_obs[i], self.done[i] rew, next_obs, float(done) self.ptr (self.ptr 1) % self.capacity self.size min(self.size 1, self.capacity) def sample(self, batch_size, rngNone): rng rng or np.random idx rng.randint(0, self.size, sizebatch_size) # 有放回采样够用且实现简单 return (self.obs[idx], self.act[idx], self.rew[idx], self.next_obs[idx], self.done[idx]) def save(self, path): # 键名固定成这五个评估脚本和可视化脚本都按这套键读 np.savez_compressed(path, obsself.obs[:self.size], actself.act[:self.size], rewself.rew[:self.size], next_obsself.next_obs[:self.size], doneself.done[:self.size])几个参数值得说清。dtypenp.float32不是随手写的PyTorch 默认张量就是 float32用 float64 存一遍再转一遍几十万条样本会白白多占一倍内存而且转换发生在DataLoader的 worker 里很容易变成隐性瓶颈。done存成 0/1 浮点而不是布尔是为了和rew一起拼成(N, 5)的数组做批量处理时类型不打架。capacity给 20 万条是经验值连续动作任务单条轨迹通常几百到几千步20 万条大致相当于几百条轨迹覆盖 GAIL 一轮训练足够再大就只是拖慢采样。图像观测要额外注意一点如果环境返回的是(H, W, C)的 uint8 帧在add之前就要定好是展平还是保留三维。展平省事但卷积策略网络就用不上了保留三维则要在obs_dim上写(C, H, W)并做通道前置。这个决定一旦写进 npz后面改起来要重采数据属于典型的后悔药没处买。2.2 行为克隆三层 MLP 加 MSE 就够但输出头决定成败BC 本质是监督学习obs → action的回归或分类。网络不需要深两层 256 的 tanh MLP 在绝大多数连续控制任务上已经够用真正决定成败的是输出头跟动作空间的匹配方式。import torch import torch.nn as nn class PolicyNet(nn.Module): def __init__(self, obs_dim, act_dim, hidden256): super().__init__() self.body nn.Sequential( nn.Linear(obs_dim, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh(), ) self.head nn.Linear(hidden, act_dim) # 确定性策略直接回归动作均值 def forward(self, obs): return self.head(self.body(obs)) def bc_loss(policy, obs, act, discreteFalse): out policy(obs) if discrete: # 离散动作交叉熵act 是 long 型类别索引 return nn.functional.cross_entropy(out, act.long()) # 连续动作MSE 回归act 需与 out 同量纲 return nn.functional.mse_loss(out, act)nn.Tanh()而不是 ReLU是因为连续控制的观测和动作经常带负值tanh 的对称性让初始阶段的梯度分布更均匀如果观测是图像把body换成几层 stride2 的卷积再接展平即可输出头不动。连续动作用 MSE 意味着你在拟合条件均值这在高斯策略假设下等价于只学均值不学方差好处是稳定坏处是遇到多峰专家数据同一个状态专家有时左拐有时右拐会学出一个谁都不像的中间动作。常见做法是改成高斯头多输出一维log_std并用-log_prob当损失代价是训练更容易发散方差要 clamp 到[-5, 2]这种范围。动作量纲是另一个高频坑。环境action_space是[-1, 1]的话专家数据如果来自力矩控制比如[-20, 20]直接回归出来的策略一进环境就会被 clip 成常量现象是 BC loss 掉得很快但 rollout 一动不动。落数据前统一按(a - low) / (high - low) * 2 - 1归一化到[-1, 1]评估时再反归一化这一步写在采集脚本里比写在训练脚本里安全。2.3 DAgger 的在线聚合把「策略会走到哪」交给专家重新打标BC 的理论误差随轨迹长度平方增长直觉解释就是策略在专家没去过的状态上没学过一旦偏离就再也回不来。DAgger 的思路很直接——让当前策略自己去跑跑到哪些状态就请专家对这些状态重新打标签把新样本并进数据集重训。代码骨架不长但循环里三个顺序不能乱。def dagger_loop(env, expert, policy, dataset, iters10, beta00.5, steps_per_iter2000): for it in range(iters): # 1) 用当前策略在环境里采样收集它真实访问到的状态 states [] obs, _ env.reset() for _ in range(steps_per_iter): beta beta0 ** it # 专家介入概率指数衰减 if np.random.rand() beta: act expert.act(obs) # 专家接管把轨迹拉回可行域 else: with torch.no_grad(): act policy(torch.as_tensor(obs, dtypetorch.float32)).numpy() states.append(obs) obs, _, term, trunc, _ env.step(act) if term or trunc: obs, _ env.reset() # 2) 请专家对这些“策略自己走出来的状态”打标签 states np.asarray(states, dtypenp.float32) labels expert.label(states) # 支持批量查询的实现比逐帧快一个量级 # 3) 聚合后重训注意是在全量数据集上重训而不是只训新样本 dataset.extend(states, labels) train_bc(policy, dataset, epochs5)beta0控制专家介入强度0.5意味着第一轮一半动作由专家给之后每轮减半。有人直接取beta max(0, 1 - it / iters)线性衰减差别不大但指数衰减在前期更依赖专家适合专家本身很贵的场景。steps_per_iter2000是每轮新采的步数太小聚合效果不明显太大则单轮训练时间压不住2000 到 5000 之间比较常见。这里有个实现细节容易被忽略expert.label(states)必须支持批量查询。如果专家是规则函数逐帧调用两百次和一次批量调用在 Python 里的开销差几十倍如果专家是人那 DAgger 就不适合跑十轮得改成两三轮加大量增广。另外train_bc每轮只训 5 个 epoch 是有意的——DAgger 靠的是数据分布持续扩张不是单轮把 loss 压到最低训太多轮会让策略过早过拟合到旧状态分布上。2.4 GAIL 的判别器与奖励整形奖励从哪来GAIL 解决的是 BC 误差累积的另一种思路不学动作学一个「像专家」的判别器用判别器输出反向构造奖励再拿这个奖励跑标准策略梯度。判别器输入是(obs, action)拼接输出一个 logit专家样本标 1、策略样本标 0。class Discriminator(nn.Module): def __init__(self, obs_dim, act_dim, hidden128): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim act_dim, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh(), nn.Linear(hidden, 1), ) def forward(self, obs, act): return self.net(torch.cat([obs, act], dim-1)).squeeze(-1) # 输出 logit def disc_loss(disc, expert_obs, expert_act, policy_obs, policy_act): # 标准二分类交叉熵专家 1策略 0 pos disc(expert_obs, expert_act) neg disc(policy_obs, policy_act) return (nn.functional.binary_cross_entropy_with_logits(pos, torch.ones_like(pos)) nn.functional.binary_cross_entropy_with_logits(neg, torch.zeros_like(neg))) def gail_reward(disc, obs, act, clip10.0): # r -log(1 - D(s,a))D 是判别器给出的“像专家”概率 prob torch.sigmoid(disc(obs, act)) r -torch.log(1.0 - prob 1e-8) return torch.clamp(r, -clip, clip)判别器网络刻意比策略网络小128 vs 256且只有两层是为了不让它太快把策略和专家完全分开。奖励取-log(1 - D)而不是直接用D是因为前者在D接近 0 时给的正奖励更平滑策略在早期不至于所有样本奖励都是 0.001 这个量级梯度全被吞掉。clip10.0是给奖励设上限防止个别样本把优势函数撑爆。判别器的训练和策略的训练是交替的每一轮先用当前策略采一批 rollout更新判别器若干次再用更新后的判别器算奖励、更新策略。这个「若干次」就是下一章要单独说的判别器步频是 GAIL 里最敏感的超参之一。3. 从零跑通一次训练依赖、专家数据、配置和日志的四段命令链拿到一个算法包第一件事不是读完全部源码是把它跑出第一条非零奖励曲线。这一步跑通了后面所有调参才有意义跑不通读源码也读不出所以然。下面这条链路按「装环境 → 采数据 → 训 BC → 训 GAIL → 评估」的顺序排脚本名以仓库里的实际文件为准我按常见命名写对不上就按功能找对应入口。3.1 依赖装完之后先验环境接口对不对# 1) 建独立解释器环境避免和系统里的 torch 版本打架 python -m venv .venv source .venv/bin/activate # Windows 下换成 .venv\Scripts\activate # 2) 装依赖requirements.txt 里通常锁 numpy / torch / 环境接口库 / pyyaml / tensorboard pip install -r requirements.txt # 3) 自检把算法注册表和已安装的环境列出来确认 BC/DAgger/GAIL 三个入口都在 python -m pkg.registry --list第三步别省。算法包的入口常挂在注册表或getattr动态导入上包名写错、模块没被__init__.py导出报错会出现在训练中段而不是启动时排查成本翻倍。如果注册表能列出三个算法名且环境接口库能make出你的环境依赖这一关就算过了。提示环境接口库的版本差异主要体现在reset和step的返回值上。老接口reset()只返回obsstep()返回四元组新接口返回(obs, info)和五元组。包里的 rollout 代码只认其中一种版本不对会报「too many values to unpack」这类看似莫名其妙的错。改法很简单在采集脚本里加一层适配或者在环境外面包一个 wrapper不要在算法内部到处打补丁。3.2 采集专家轨迹脚本参数与落盘格式# 采集专家轨迹。--env 换成自己的环境 id--episodes 决定演示条数 python scripts/collect_demos.py \ --env YourEnv-v0 \ --episodes 60 \ --policy expert_rule_based \ --noise 0.02 \ --out data/expert_60ep.npz--episodes 60不是拍脑袋的数下一章会讲怎么估第一次跑通先给 20 到 30 条确认文件能存下来、字段对得上再补量。--noise 0.02是给专家动作加的小幅高斯噪声目的是让专家数据覆盖一点状态邻域BC 学出来的策略不会在专家轨迹两侧立刻崩掉。噪声太大等于污染标签0.01 到 0.05 之间按动作量纲调动作在[-1, 1]的话 0.02 合适。落盘之后先做个形状检查比直接开训省事得多import numpy as np d np.load(data/expert_60ep.npz) print({k: (d[k].shape, d[k].dtype) for k in d.files}) # 期望看到 obs (N, obs_dim) float32, act (N, act_dim) float32, done 里 1 的个数等于轨迹条数done为 1 的样本数应该正好等于--episodes多一个少一个都说明采集循环里对terminated和truncated的处理有问题。超时截断算不算done在不同算法里处理不一样BC 不在意GAIL 在意——超时被当成终止会让价值函数学到「到时间就没价值」正确做法是用truncated做 bootstrap 而不是当终止。3.3 配置文件一次训练里真正要动的十几个键algo: gail seed: 0 env_id: YourEnv-v0 demo_path: data/expert_60ep.npz policy: hidden: [256, 256] activation: tanh train: total_steps: 1_000_000 batch_size: 256 lr_policy: 3.0e-4 lr_disc: 1.0e-3 disc_updates: 3 # 每轮策略更新前判别器更新几次 gamma: 0.99 gae_lambda: 0.95 ent_coef: 0.01 gail: reward_clip: 10.0 disc_hidden: 128total_steps: 1_000_000是环境交互步数而不是梯度步数这个口径一定要跟日志对上否则你会以为训了很久其实只采了几万步。ent_coef: 0.01是熵正则GAIL 里不建议设太大——奖励本身是从判别器来的熵太高会让策略主动往判别器没见过的区域跑判别器给的奖励全是噪声训练曲线表现为先涨后崩。3.4 训练与评估日志里该盯哪几条曲线# 训 BC 先确认数据管线没问题 python scripts/train_bc.py --config configs/bc.yaml --demo data/expert_60ep.npz # 训 GAIL从 BC 权重热启动 python scripts/train_gail.py --config configs/gail.yaml --init runs/bc/policy.pt # 评估20 条 episode同时看均值和最差分位数 python scripts/evaluate.py --ckpt runs/gail/policy.pt --episodes 20 --percentile 25日志里至少四条曲线要看episode_return、disc_accuracy、policy_entropy、value_loss。disc_accuracy稳定在 0.5 到 0.75 之间是健康区间说明判别器还分不太清一旦冲到 0.95 以上并横住策略这边基本拿不到有效梯度奖励曲线会变成一条缓慢下滑的直线。policy_entropy应该缓慢下降而不是断崖式掉到 0掉太快说明策略过早确定化GAIL 阶段就废了。评估只看均值是自欺欺人。模仿学习的失败是尾部事件——20 条里 18 条接近专家、2 条完全崩均值可能还不错但上线就是那 2 条在出问题。所以评估脚本里同时打 25 分位数--percentile 25就是干这个的。4. 超参不是玄学专家轨迹量、判别器步频与折扣因子的联动关系模仿学习算法的超参比普通监督学习少但相互耦合更强。改一个数往往要连带改另一个不然就是从「不收敛」跳到「过拟合」再跳回来。这一章把三个最影响结果的量单独拎出来给的都是范围和判断依据不是绝对最优值。4.1 专家轨迹数量与状态维度先算清覆盖度再谈算法BC 的误差上界大致是O(T²ε)T是轨迹长度ε是单步监督误差。这个式子说明两件事轨迹越长同样的单步误差放大得越厉害想降低误差要么减小ε更好的网络/更多数据要么打断误差累积DAgger。实操里的估算方式是按状态空间覆盖率不是按条数。经验区间任务类型状态维度建议专家轨迹条数备注低维连续控制10 以内20 到 50每条几百步够 BC 出可用策略中维带关节角、速度10 到 5050 到 200建议配 DAgger纯 BC 容易偏图像输入高维200 以上必须做数据增广否则过拟合背景多模态专家人遥操作任意200 以上高斯头或多峰建模MSE 会学出平均动作表里的数字是起点不是终点。判断数据够不够看 BC 的验证集 loss 和 rollout 表现是否背离验证 loss 很低但 rollout 一开始就偏说明数据覆盖不够加数据或者上 DAgger验证 loss 本身就高说明网络容量或者输入特征有问题加数据没用。4.2 判别器步频与学习率GAIL 最容易失衡的地方disc_updates和lr_disc是一对联动参数。判别器更新太频繁或学习率太高会迅速把策略和专家分开奖励饱和策略梯度消失更新太少判别器跟不上策略变化奖励信号滞后训练表现为奖励曲线剧烈震荡。我一般这样起手lr_disc 1e-3、lr_policy 3e-4判别器学习率比策略高 3 倍左右disc_updates 1先跑 20 万步看disc_accuracy。如果它一直在 0.5 附近上不去说明判别器太弱把disc_updates提到 3 或者把disc_hidden从 128 加到 256如果它三步之内冲到 0.9 以上反过来做lr_disc降到 3e-4或者给判别器加梯度惩罚。另一个常被忽略的旋钮是reward_clip。判别器输出经过-log(1-D)之后理论上无界D → 1时奖励会冲到几十甚至上百优势估计直接被个别样本主导。reward_clip: 10.0相当于给奖励设了个硬上限代价是损失一部分区分度但换来的稳定性在调试阶段更值钱。4.3 折扣因子与 GAE把信用分配对上专家轨迹长度gamma 0.99意味着有效视野大约 100 步0.995大约 200 步0.999大约 1000 步。这个数要和专家轨迹的实际长度匹配如果专家轨迹平均 300 步收敛到目标gamma给 0.99 会让价值函数看不到后半段策略学到的是「快速动一下」而不是「完整完成任务」。gae_lambda控制偏差和方差的折中0.95 是通用起点。GAIL 的奖励来自判别器噪声比环境原生奖励大这种情况下把lambda降到 0.9 甚至 0.85 能明显减小优势估计的方差代价是偏差变大、收敛变慢。判断依据是value_loss的波动幅度如果它在两个数量级之间来回跳先降lambda再考虑降学习率。注意gamma改大之后total_steps也要跟着加。视野从 100 步拉到 300 步同样学到「完整任务」需要的数据量大致按比例增加只改gamma不改步数最常见的现象是训练到一半曲线开始缓慢下滑看起来像过拟合其实是步数不够。4.4 参数速查表参数作用起步值什么时候改lr_policy策略网络步长3e-4曲线震荡就减半20 万步不动就翻倍lr_disc判别器步长1e-3判别器准确率过高就降过低就升disc_updates每轮判别器更新次数1 到 3先动这个再动学习率影响更平滑gamma折扣因子0.99按专家轨迹长度的 1/3 到 1/2 反推gae_lambda优势估计折中0.95value_loss抖动大就降到 0.9ent_coef熵正则0.01策略过早确定化就加大探索过头就减小reward_clip判别器奖励上限10.0奖励方差爆炸时降到 5.0batch_size批量大小256显存够就加到 512梯度更稳5. 五类翻车现场排查现象、成因和能直接改的那一行算法包的 issue 区里重复率最高的不是「怎么实现」而是「我训出来不对」。下面五条按出现频率排每条都按现象、成因、解决三层写能直接对应到代码里改哪一行。5.1 BC 训练 loss 很低rollout 三步就飘现象训练集 MSE 降到 1e-3 量级进环境跑前三步还行第五步动作开始抖第十步直接撞墙。成因典型的分布偏移。专家数据只覆盖专家走过的状态策略在专家状态上学得很准一旦偏出去几厘米进入没见过的状态输出就是网络外推的随机值误差按T²放大。解决上 DAgger哪怕只跑两三轮也有效暂时不想引入在线专家的话退一步做状态增广——对专家观测加小幅高斯噪声、对图像做随机裁剪和亮度扰动让策略在专家轨迹邻域内有定义。噪声幅度按观测标准差的 5% 到 10% 给给大了等于教策略在错误状态上输出正确动作反而更糟。5.2 GAIL 判别器准确率三步冲到 0.98奖励曲线变平现象训练前几千步disc_accuracy迅速逼近 1.0gail_reward均值掉到接近 0 并横住策略性能不再变化。成因判别器容量或学习率相对策略太强几乎瞬间就把两类样本线性分开。奖励函数变成「策略样本恒为 -10专家样本恒为 10」策略拿到的梯度全是常数等于没有奖励。解决先把disc_updates从 3 降到 1再把lr_disc降到 3e-4还不够就给判别器加梯度惩罚WGAN-GP 那一套或者把disc_hidden从 256 砍到 128。判别器网络比策略网络小一到两档是 GAIL 里比较稳的配置习惯。5.3 观测归一化统计量随训练漂移训练中期突然崩现象前 30 万步曲线正常上涨之后突然掉下去再也不回来重启同 seed 也不一定复现。成因观测归一化用了滑动统计量而策略探索到的状态分布随训练不断扩张mean/std跟着漂导致同一个观测在不同时刻被归一化成不同值。价值网络学的是旧分布输入一变就全乱。解决归一化统计量只用专家数据算一次然后冻结写进 checkpoint 一起保存。评估和训练必须用同一份mean/std这一点在加载模型时最容易漏——只加载了权重没加载统计量现象是「训练好好的模型评估时表现极差」。5.4 专家动作和环境动作空间量纲不一致现象BC loss 正常下降但 rollout 里动作像是被截断永远贴着[-1, 1]的边界走机器人只朝一个方向缓慢移动。成因专家数据来自力矩控制动作范围是[-20, 20]环境action_space是[-1, 1]回归出来的策略输出被环境 clip等于把 95% 的信息丢了。解决采集阶段就把专家动作按(a - low) / (high - low) * 2 - 1归一化落盘的 npz 里存归一化后的值同时在文件里存一份action_low/action_high。评估脚本反归一化后再env.step。这条建议写进采集脚本的注释里不然下一个人接手还要再踩一次。5.5 固定 seed 但两次训练结果差很多现象seed: 0写死在配置里同样的数据同样的超参两次跑出来的最终回报差 30% 以上。成因三处非确定性没关掉——环境内部的随机数生成器没设种子、PyTorch 的 cuDNN 自动调优、DataLoader 多 worker 的采样顺序。少关一处复现性就没了。解决环境reset(seedcfg.seed)、torch.backends.cudnn.deterministic True、torch.backends.cudnn.benchmark False、DataLoader(..., worker_init_fn...)里给每个 worker 派生种子。调试阶段干脆num_workers0慢一点但结果可比。这套开关关掉之后性能可能降几个点属于为了可复现付的必要成本。6. 再往前一步BC 预训练接 GAIL 微调加一个误差分箱的验证习惯从零训 GAIL 的探索成本很高尤其是动作维度大于 10 的时候判别器早期的奖励几乎全是噪声。更省事的做法是先用 BC 把策略热启动到一个「能跑但不精细」的水平再切 GAIL 微调。切换时有两个细节要改策略学习率降到原来的三分之一到五分之一BC 权重已经很接近解大步长会把它们打散判别器从零开始训、学习率保持原值因为判别器面对的是一个已经比较像专家的策略需要足够强的区分能力才能提供有效梯度。验证环节我习惯加一个误差分箱比只看总回报有用得多。思路是拿一批专家观测喂给策略算每个样本的动作误差再按专家动作模长或者状态某个关键维度分箱看误差集中在哪一段。import numpy as np import torch torch.no_grad() def divergence_by_bin(policy, expert_obs, expert_act, n_bins5): 按专家动作模长分箱统计策略与专家的动作 MSE定位误差集中区 obs_t torch.as_tensor(expert_obs, dtypetorch.float32) pred policy(obs_t).numpy() err ((pred - expert_act) ** 2).mean(axis1) # 每个样本一个标量误差 key np.linalg.norm(expert_act, axis1) # 用动作模长当分箱依据 edges np.quantile(key, np.linspace(0, 1, n_bins 1)) idx np.clip(np.digitize(key, edges[1:-1]), 0, n_bins - 1) return {i: round(float(err[idx i].mean()), 5) for i in range(n_bins) if (idx i).any()}n_bins5按分位数切保证每箱样本量接近避免某一箱只有几个样本让均值失真。输出里如果误差集中在最高的那一箱说明策略在「大幅度动作」上学得差多半是专家数据里大幅动作样本少补采时要有意识地多采这类片段如果集中在最低那一箱通常是噪声问题——小幅动作的信噪比低专家自己抖一下就被当成标签学走了这时候该做的是对专家动作做一点平滑再训。配合评估脚本的 25 分位数一起看基本能定位八成问题分箱误差均匀但最差分位数很差是分布偏移分箱误差本身就偏某一箱是数据覆盖问题两者都正常但环境回报上不去那要回头看奖励整形而不是继续调策略。从那以后我每次跑 GAIL 之前都会强制先跑一遍 BC 的分箱误差确认失败状态集中在哪一段再决定是补数据还是直接上对抗训练——省下的不是训练时间是那种「训了两天不知道错在哪」的消耗。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站