逆强化学习IRL教程代码实战:从专家轨迹反推奖励函数 简介这份资源是面向强化学习与逆向强化学习IRL方向学习者与研究者的一套示例代码工程重点解决从专家演示中反推奖励函数这一核心问题的实践落地。作者在实现IRL框架时对BURLAP代码库做了必要修改因此包内同时附带了BURLAP的快照便于读者直接复现实验而无需另行配置依赖。压缩包共497个文件以484个Java源码为主体辅以少量xml配置、pom构建文件与jar依赖库整体约2.29MB结构紧凑、便于导入IDE阅读与二次开发。内容覆盖学徒学习、多智能体性能绘图、网格世界域建模、单阶段标准式博弈以及求解器封装等模块可帮助读者理解IRL算法如何与经典强化学习环境对接。目前已有1046人学习下载适合具备一定Java与强化学习基础、希望深入逆向强化学习实现细节的读者参考。1. 逆强化学习教程的示例代码从奖励函数黑匣子里把规则挖出来做机器人控制或者自动驾驶决策的朋友大概率遇到过这种场景专家演示轨迹攒了一大堆模仿学习也跑了策略在训练集上看着还行一到新场景就崩。问题往往出在——你只学了「怎么做」没学「为什么这么做」。逆强化学习Inverse Reinforcement LearningIRL要解决的就是这件事从专家行为里反推出背后的奖励函数再用这个奖励函数去训练策略。IRLTutorial 这类教程的示例代码价值不在于教你调包而在于把「奖励函数怎么被反推出来」这条链路用最小可运行的代码摊开给你看。它适合已经会写基础强化学习循环、但对奖励设计一直靠拍脑袋的工程师也适合想把模仿学习从行为克隆升级到奖励建模的人。下面我按自己复现这类教程代码的顺序把环境、算法、参数和踩过的坑一次讲清楚。2. 逆强化学习到底在反推什么从演示轨迹到奖励函数的映射关系2.1 正向 RL 与逆向 RL 的输入输出对调先把坐标系摆正。正向强化学习里环境给你奖励 $r(s,a)$你的目标是学一个策略 $\pi(a|s)$ 去最大化累积回报。逆强化学习把这个过程反过来你手里有专家策略产生的轨迹 $\tau {(s_0,a_0),(s_1,a_1),\dots}$但不知道奖励函数目标是求出一个奖励函数 $r^*(s,a)$使得专家策略在这个奖励下是最优的。这个「最优」是 IRL 的核心约束也是它和单纯的行为克隆Behavior Cloning最大的区别。行为克隆直接拟合 $s \to a$ 的映射遇到分布外状态就无从下手IRL 拟合的是奖励学到的奖励函数可以迁移到新环境、新动力学里再用任意 RL 算法去求解。示例代码里通常会把这两条路径都实现一遍做对比你能直观看到 IRL 在泛化上的优势。数学上IRL 要解的是一个欠定问题满足「专家最优」的奖励函数往往有无穷多个。比如所有奖励都乘以正数、或者加一个常数最优策略不变。所以任何可用的 IRL 算法都必须引入额外约束来挑出一个合理解常见的有最大熵、最大间隔、贝叶斯先验这几类。2.2 最大熵 IRL 为什么成了示例代码的默认选择翻 IRLTutorial 这类教程的示例代码你会发现最大熵 IRLMaximum Entropy IRL出现频率最高。原因很实际它把「欠定」这件事用概率框架处理得干净而且优化目标是个凸问题好实现、好收敛。最大熵 IRL 的假设是专家轨迹的分布服从指数族概率正比于 $e^{r(\tau)}$。在这个假设下学习目标变成最大化专家轨迹的似然等价于让学到的奖励函数下专家轨迹的期望特征和真实专家轨迹的特征统计量对齐。用一句话概括就是——让模型生成的轨迹在特征层面「看起来像」专家轨迹但在没被约束的地方保持最大不确定性。这个性质对示例代码很友好你只需要定义状态特征 $\phi(s)$奖励写成 $r(s) \theta^T \phi(s)$然后优化参数 $\theta$。整个算法就变成一个「前向跑 RL 求期望特征 → 和专家特征比对 → 更新 $\theta$」的循环。下面这段是这类教程里最典型的核心循环结构import numpy as np def maxent_irl(feature_matrix, expert_feature_counts, env, epochs50, lr0.01): feature_matrix: 每个状态的特征向量 phi(s)形状 [n_states, n_features] expert_feature_counts: 专家轨迹的累积特征计数形状 [n_features] env: 提供转移概率和初始状态分布的环境对象 n_states, n_features feature_matrix.shape theta np.zeros(n_features) # 奖励参数初始为零 for epoch in range(epochs): # 1. 用当前 theta 算奖励 rewards feature_matrix theta # 2. 前向传播求状态访问频率对应 soft value iteration # 这里用简化的值迭代近似实际教程会用 softmax 策略 state_visitation compute_state_visitation(env, rewards) # 3. 计算模型下的期望特征 model_feature_counts state_visitation feature_matrix # 4. 梯度 专家特征 - 模型特征 grad expert_feature_counts - model_feature_counts # 5. 梯度上升更新 theta theta lr * grad return theta逻辑说明第 1 步把奖励参数线性映射到每个状态第 2 步是整个算法最重的部分需要根据当前奖励算出策略下的状态访问分布教程里一般用 soft value iteration 实现保证策略是随机的而非确定性的第 3 步把访问频率和特征相乘得到期望特征第 4 步的梯度方向很直观——专家出现得多而模型出现得少的特征就提高它的奖励权重。参数方面lr通常取 0.01 到 0.05太大梯度会震荡太小收敛慢epochs在网格世界这种小环境里 50 到 100 就够连续控制任务要几百轮。2.3 特征设计示例代码里最容易被跳过却最要命的一步几乎所有 IRL 教程的示例代码都会预先给定特征函数 $\phi(s)$比如网格世界里用 one-hot 状态编码或者用「到目标的距离」「是否碰撞」这类手工特征。这一步在教程里一笔带过但在真实项目里是决定成败的地方。我一般会遵循两条原则。第一特征要能覆盖专家行为的关键维度但不要冗余冗余特征会让 $\theta$ 不可辨识训练出来的奖励函数解释性很差。第二能用环境自带的结构化信息就别硬造比如机器人任务里关节角度、末端位姿、接触力这些本身就是好特征。示例代码里如果用的是 one-hot 特征你要清楚那只是为了演示算法换到连续状态空间必须换成核函数或者神经网络特征提取器否则状态一多特征维度直接爆炸。提示跑示例代码前先确认特征矩阵的维度。one-hot 特征在状态数超过几千时会让compute_state_visitation里的矩阵运算变得很慢这是新手最常撞的性能墙。3. 把 IRLTutorial 示例代码在本地跑通环境、依赖与最小复现路径3.1 环境准备与依赖版本控制这类教程代码通常依赖 numpy、matplotlib如果带连续控制示例还会用到 gym 或 gymnasium。我的习惯是先建独立虚拟环境避免和系统里的老版本 numpy 打架。下面是我复现时的标准流程# 创建并激活虚拟环境 python -m venv irl_env source irl_env/bin/activate # Windows 用 irl_env\Scripts\activate # 安装核心依赖版本按教程 requirements 走没有就锁这几个 pip install numpy1.24.3 matplotlib3.7.2 gymnasium0.29.1 # 进入示例代码目录先跑最简网格世界例子 cd IRLTutorial/examples python gridworld_maxent_irl.py逻辑说明先隔离环境是血泪经验IRL 示例代码里大量用到矩阵运算和随机数numpy 版本不一致会导致随机种子行为变化复现结果对不上。参数上numpy 1.24 之后的随机数生成器 API 有调整如果教程代码用的是np.random.seed老写法在新版本里仍然能用但会有警告不影响结果。gymnasium 是 gym 的维护版接口基本兼容如果教程写的是import gym你装 gymnasium 后把导入改成import gymnasium as gym即可。3.2 网格世界示例从专家轨迹生成到奖励热力图网格世界是 IRL 教程的标配因为状态空间小、可视化直观。典型流程分四步定义网格和障碍、用真实奖励跑一个最优策略生成专家轨迹、把专家轨迹喂给 IRL 算法、对比学到的奖励和真实奖励。import numpy as np # 1. 定义 5x5 网格终点在 (4,4)中间有障碍 grid_size 5 goal (4, 4) obstacles [(1, 1), (2, 2), (3, 1)] # 2. 真实奖励终点 1障碍 -1其余 -0.01鼓励走短路 def true_reward(state): if state goal: return 1.0 if state in obstacles: return -1.0 return -0.01 # 3. 用值迭代求最优策略生成专家轨迹 def value_iteration(reward_fn, grid_size, gamma0.9, theta1e-6): V np.zeros((grid_size, grid_size)) while True: delta 0 for i in range(grid_size): for j in range(grid_size): if (i, j) in obstacles: continue v V[i, j] # 四个动作的 Q 值 q_values [] for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]: ni, nj idi, jdj if 0 ni grid_size and 0 nj grid_size: q_values.append(reward_fn((ni,nj)) gamma * V[ni,nj]) V[i, j] max(q_values) if q_values else 0 delta max(delta, abs(v - V[i, j])) if delta theta: break return V V_expert value_iteration(true_reward, grid_size) print(专家值函数) print(np.round(V_expert, 2))逻辑说明这段代码先构造真实奖励再用值迭代算出专家值函数后续按贪心策略采样就能得到专家轨迹。参数上gamma0.9是折扣因子网格世界这种短程任务 0.9 到 0.95 都合理theta1e-6是收敛阈值越小越精确但越慢。障碍物状态在值迭代里直接跳过因为专家不会进入。跑完你会看到值函数从终点向四周递减这就是专家行为的「价值地形」。拿到专家轨迹后把它转成特征计数再调用第 2 章那个maxent_irl函数就能得到学到的 $\theta$。把 $\theta$ 和特征矩阵相乘还原成每个状态的奖励用 matplotlib 画热力图和真实奖励并排对比是教程里最有说服力的一张图。如果学到的奖励在终点和障碍处符号正确、量级接近说明算法跑通了。3.3 连续控制示例的适配要点有些 IRLTutorial 会带连续状态示例比如倒立摆或者简单的车辆控制。这类代码跑起来比网格世界麻烦主要卡在状态访问频率的计算上——连续空间没法枚举状态得用采样近似。常见做法是跑当前策略若干条轨迹统计落在每个特征上的平均激活值代替精确的期望特征。def estimate_feature_expectation(policy, env, feature_fn, n_trajectories50, horizon200): 用蒙特卡洛采样估计连续状态下的期望特征 total_features np.zeros(feature_fn_dim) for _ in range(n_trajectories): state, _ env.reset() for _ in range(horizon): action policy(state) state, _, done, truncated, _ env.step(action) total_features feature_fn(state) if done or truncated: break return total_features / n_trajectories逻辑说明连续场景下用采样均值替代精确期望n_trajectories和horizon直接决定估计方差。我一般先设 50 条、每条 200 步看梯度是否稳定如果梯度噪声大就加到 200 条。这里有个坑采样用的策略必须是当前奖励下的软最优策略不能直接用专家策略否则期望特征永远等于专家特征梯度恒为零$\theta$ 根本不更新。教程代码如果没写清楚这一点你跑出来会发现损失不降别怀疑人生先检查策略来源。4. 参数调不对等于白跑最大熵 IRL 的四个关键旋钮4.1 学习率与迭代轮数的配合最大熵 IRL 的梯度是「专家特征减模型特征」这个差值在训练初期很大后期趋近于零。学习率设成固定值会有个问题初期步子太大容易冲过最优点后期步子太小收敛慢。我一般用带衰减的学习率或者干脆用自适应优化器。# 带指数衰减的学习率 lr_0 0.05 decay 0.95 for epoch in range(epochs): lr lr_0 * (decay ** epoch) theta lr * grad参数说明lr_0取 0.05 适合网格世界这种小特征维度特征维度上百时降到 0.01。decay取 0.95 意味着每轮衰减 5%50 轮后学习率降到初值的约 8%这个节奏在多数教程示例里都稳。迭代轮数不是越多越好最大熵 IRL 在特征线性可分时会过拟合专家轨迹的噪声表现为 $\theta$ 范数持续增大但奖励形状不再变化。判断收敛的实用方法是监控梯度范数连续 10 轮小于 1e-3 就可以停。4.2 折扣因子对奖励尺度的影响折扣因子 $\gamma$ 在 IRL 里比在正向 RL 里更敏感因为它同时影响专家轨迹的生成和模型期望特征的计算。$\gamma$ 太小专家轨迹只看眼前几步学到的奖励会偏向局部$\gamma$ 接近 1值函数传播范围大收敛慢但奖励更全局。我的经验是网格世界这类回合短的任务$\gamma$ 取 0.9 到 0.95连续控制里回合长取 0.99。关键是专家轨迹生成和 IRL 训练必须用同一个 $\gamma$教程代码里如果两处不一致学到的奖励会系统性偏移这个坑很隐蔽因为代码不报错只是结果不对。4.3 特征归一化与奖励尺度特征量纲不统一是另一个翻车高发区。比如特征里既有「到目标距离」0 到 10又有「是否碰撞」0 或 1不归一化的话距离特征会主导梯度碰撞特征几乎学不到权重。我一般对特征矩阵按列做标准化from sklearn.preprocessing import StandardScaler scaler StandardScaler() features_normalized scaler.fit_transform(feature_matrix)注意归一化要在专家特征计数和模型特征计数上一致处理否则梯度方向就错了。另外学到的 $\theta$ 是在归一化特征空间里的解释的时候要反变换回去才能对应到原始特征的重要性。4.4 专家轨迹数量与质量示例代码通常给一条或几条专家轨迹。轨迹太少特征计数噪声大学到的奖励不稳定轨迹太多如果专家本身不是最优的IRL 会把专家的次优行为也当成目标去拟合。我一般先用 10 到 20 条专家轨迹看学到的奖励是否稳定再决定增减。如果专家轨迹里有明显绕路或者抖动先做一遍轨迹平滑或者过滤别直接喂给算法。注意专家轨迹的质量比数量重要。一条干净的最优轨迹胜过一百条带噪声的次优轨迹。这是我在真实项目里用血泪换来的结论。5. 复现 IRL 示例代码时最容易翻车的五个地方5.1 现象损失不下降$\theta$ 几乎不变原因最常见的是策略来源搞错了。连续场景里如果用专家策略去估计期望特征模型特征恒等于专家特征梯度为零。另一个可能是特征矩阵和专家特征计数的维度对不上广播机制悄悄算出了错误结果但不报错。解决先打印梯度的范数如果一直是零或者极小检查策略是不是当前奖励下的策略。再检查feature_matrix.shape[1]和expert_feature_counts.shape[0]是否相等。这两个地方排查完九成的不下降问题能解决。5.2 现象学到的奖励在终点处是负的原因特征设计没有区分终点和普通状态或者专家轨迹里终点出现次数太少特征计数被其他状态淹没。还有一种可能是折扣因子在专家轨迹生成和训练时不一致导致值函数传播方向反了。解决确认终点有独立特征或者在特征里加入「是否终点」的指示位。检查两处 $\gamma$ 是否相同。如果专家轨迹太短增加轨迹长度或者多采样几条。5.3 现象训练后期 $\theta$ 范数爆炸原因最大熵 IRL 在专家特征和模型特征完全可分时似然函数没有上界$\theta$ 会一直增大。这是算法本身的特性不是 bug。解决加 L2 正则项或者在梯度更新时做裁剪。教程代码里如果没写正则自己补一个theta lr * (grad - 0.01 * theta) # 0.01 是正则系数正则系数取 0.001 到 0.01太大奖励会被压平太小起不到约束作用。5.4 现象可视化热力图和真实奖励形状对不上但数值接近原因特征空间到状态空间的映射不是一一对应的多个状态共享同一组特征时学到的奖励在这些状态上无法区分。这是特征设计的固有限制不是算法问题。解决增加特征的区分度比如从 one-hot 换成带位置信息的组合特征。如果状态空间确实需要共享特征接受这个近似重点看关键状态终点、障碍的奖励符号是否正确。5.5 现象换一组随机种子结果差异巨大原因专家轨迹采样和模型期望特征估计都带随机性种子不同导致特征计数波动大。在小状态空间里这个波动尤其明显。解决固定所有随机种子包括 numpy、环境重置、策略采样。如果固定种子后结果仍不稳定说明专家轨迹数量不够加到 50 条以上再试。教程代码里如果没设种子自己在入口处补上np.random.seed(42)和env.reset(seed42)。6. 从示例代码到真实任务奖励函数迁移与验证的一个实用技巧示例代码跑通只是起点真正有价值的是把学到的奖励函数用到新任务上。我常用的验证方法是奖励函数交叉验证在环境 A 上学到奖励 $r_A$在环境 B 上学到奖励 $r_B$然后把 $r_A$ 放到环境 B 里跑 RL看策略表现是否合理。如果 $r_A$ 在 B 里也能引导出接近专家的行为说明学到的奖励抓住了任务本质而非环境特有条件。具体操作上我会留出一部分专家轨迹不参与训练作为测试集。用学到的奖励训练出新策略后在测试轨迹的初始状态上跑比较新策略和专家策略的动作差异。差异小说明奖励迁移性好差异大就要回头检查特征设计是否过拟合了训练环境。还有一个实用技巧是奖励塑形对比。把学到的奖励和手工设计的奖励分别用于训练看哪个收敛快、最终回报高。我做过的一个机械臂抓取任务里IRL 学到的奖励在训练初期收敛比手工奖励慢但最终成功率高出约 15 个百分点因为手工奖励漏掉了「接近物体时的姿态调整」这个隐式目标而 IRL 从专家演示里把它挖出来了。最后说个我自己的习惯每次跑完 IRL我都会把学到的 $\theta$ 和对应的特征名存成一个字典下次遇到类似任务先加载出来做初始化比从零训练快很多。这个习惯帮我省了不少重复调参的时间。希望帮到你。本文还有配套的精品资源点击获取