
简介面向机器学习与强化学习研究者这份资源提供在 Python/TensorFlow 中实现逆向强化学习IRL的完整代码库覆盖线性 IRL、最大熵 IRL 与深度最大熵 IRL 三种经典方法并配套 1D/2D 网格世界 MDP 环境与价值迭代求解器。压缩包共 48 个文件以 22 个 Python 脚本为主涵盖算法实现、demo 运行与测试另有 2 个 README 文档和 18 张结果对比图便于对照实验效果。整体体积仅 2.58MB结构清晰适合具备一定强化学习基础、希望复现和对比 IRL 算法的学习者。已有 1281 人浏览学习。资源中还附带 cartpole 强化学习示例、模型 checkpoint 与工具脚本可直接运行 demo.py 体验线性 IRL 在网格世界上的效果也可基于现有代码扩展非线性奖励函数与更高维状态空间是论文复现和课程实验的实用参考。1. 项目动机与整体思路为什么在TensorFlow里做IRL1.1 IRL解决什么问题这个项目标题看起来学术味很重但实际场景特别直白你手里有一批专家演示轨迹但不知道专家图什么——没有显式奖励函数。传统强化学习RL是给奖励学策略而逆向强化学习IRL正好反过来从专家的行为轨迹中反推奖励函数。模仿学习做到进阶形态就会遇到它不仅让智能体模仿专家的动作还要让它搞明白目标是什么。我在做这个项目时选了三种算法做对比经典MaxEnt、Deep MaxEnt、LPIRL。它们分别对应IRL的三条技术路线最大熵概率模型、深度网络拟合、线性规划求解。在Python TensorFlow环境下完整实现一遍既能验证理论又能直接迁移到后续的路径规划和行为建模任务里。项目名里那个模仿说得也很准确——IRL本质上就是一种模仿学习范式只不过模仿的不是动作而是意图。1.2 三种算法的选型逻辑为什么不只选一个算法因为IRL本身没有唯一答案。专家轨迹能提供的约束是有限的能解释同一批轨迹的奖励函数往往成百上千所以不同算法会收敛到不同偏好的解。MaxEnt在所有可行解里挑熵最大的那个也就是对未观测行为不做任何多余假设的解Deep MaxEnt用深度网络做奖励函数逼近器目标是把高维状态空间也纳入处理范围LPIRL则是把问题直接丢给线性规划求解器在约束域里找一个可行奖励。我设计的实验环境是经典的网格世界Gridworld5x5大小智能体从左上角出发走到右下角中间放两个障碍物。选网格世界有两个原因状态空间小三种算法在CPU上都能在几分钟内跑完奖励函数可以画成热度图一眼就能看出算法学没学对。下面三个算法的原理推导和代码实现都围绕这个场景展开。2. 核心原理三种IRL算法到底在算什么2.1 MaxEnt IRL让行为分布最自然MaxEnt IRL是Ziebart等人提出的经典方案它的核心思想是在满足特征匹配约束的所有分布里选择熵最大的那一个。特征匹配的意思是模型状态下访问频率的期望要与专家演示中统计到的状态访问频率一致。为什么强调熵最大因为如果不加这个偏好我们完全可以构造出一个只在专家轨迹上奖励为1、其他地方奖励为0的过拟合奖励虽然能解释专家行为但毫无泛化能力。在最大熵框架下策略会变成softmax形式π(a|s) ∝ exp(Q(s,a) - V(s))。这里面V(s)是soft value需要通过软价值迭代来算递推公式是V(s) log Σ_a exp(Q(s,a))。MaxEnt IRL的训练过程就是用当前奖励参数做软价值迭代得到模型状态访问频率然后计算它和专家状态访问频率的差把这个差作为梯度往上更新奖励参数。本质上是在做特征期望匹配每一步都在让模型的行为分布更接近专家的行为分布。2.2 Deep MaxEnt用神经网络替代手工特征经典MaxEnt需要手工设计特征比如网格世界里可以用状态坐标是否靠近障碍物作为特征。但在机器人操作、自动驾驶这些场景里手工特征根本设计不过来原始输入是图像或者高维传感器数据。Deep MaxEnt的思路很简单把奖励函数换成一个神经网络网络的输入是状态输出是奖励值其余训练逻辑和MaxEnt保持一致。听起来简单落地时有个大坑软价值迭代本身是一个循环计算过程如果用numpy来实现它会阻断TensorFlow的梯度传播反向传播根本走不到网络参数上。我最后采用的方案是直接用TensorFlow实现整个软价值迭代让每一步都留在计算图里。转移关系预先存成一个索引矩阵用tf.gather去取下一状态的V值这样整条链路是可微的。这也是网上很多教程没讲清楚的地方很多人在这一步直接把numpy和TensorFlow混着用结果训练出来的奖励网络完全不对。2.3 LPIRL把问题丢给线性规划求解器LPIRL是Ng和Russell在2000年提出的最早IRL方法之一思路比MaxEnt朴素得多。它直接利用MDP最优策略的充要条件如果专家策略是最优的那么在每个状态下专家动作的动作价值Q值不能低于其他任何动作。给每个状态和每个非专家动作都能写出一条不等式这些不等式对奖励函数施加了约束于是IRL就变成了一个线性规划问题。网格世界是确定性转移Q值可以展开成奖励的线性组合所以这些约束确实是关于奖励的线性不等式。我用scipy.optimize.linprog来求解目标函数选了最大化最小边际——就是让每个状态上专家动作和非专家动作的Q值差距尽量拉大。这样做能在可行解空间里选出一个相对稳定的奖励不至于因为可行域太大而解出一个全是零的平凡解。3. 环境准备与数据构造3.1 工具版本与依赖我的开发环境是Python 3.9 TensorFlow 2.10全部在CPU上跑。需要的依赖不多numpy做矩阵运算scipy提供线性规划求解器matplotlib画奖励热度图tensorflow只用于Deep MaxEnt那部分。有一点要提前说明这个项目对TensorFlow的版本不算敏感2.6以上基本都能跑通。但如果你装了GPU版TensorFlow训练Deep MaxEnt时要注意CPU和GPU的切换小规模网格世界用CPU反而更快因为GPU的启动开销比计算开销还大。我实际对比过5x5网格世界在CPU上三个算法总计跑完不到五分钟完全没有上GPU的必要。3.2 实验场景与专家轨迹生成网格世界环境定义不复杂我这里直接给出核心代码import numpy as np class Gridworld: def __init__(self, size5, obstacles[(1, 1), (3, 2)], start(0, 0), goal(4, 4), gamma0.9): self.size size self.obstacles set(obstacles) self.start start self.goal goal self.gamma gamma # 动作: 0上, 1右, 2下, 3左 self.actions [0, 1, 2, 3] self.n_states size * size self.n_actions len(self.actions) self._build_transition() def _build_transition(self): # s_next[s, a] 执行动作a后的下一状态索引 self.s_next np.zeros((self.n_states, self.n_actions), dtypeint) for s in range(self.n_states): row, col divmod(s, self.size) for a, (dr, dc) in enumerate([(-1, 0), (0, 1), (1, 0), (0, -1)]): nr, nc row dr, col dc if (0 nr self.size and 0 nc self.size and (nr, nc) not in self.obstacles): self.s_next[s, a] nr * self.size nc else: self.s_next[s, a] s # 撞墙/障碍物则留在原地专家轨迹的生成方式是先用标准值迭代求一个最优策略然后按这个策略做softmax采样生成200条轨迹每条轨迹从起点出发到终点或超过20步就截断。这里用softmax采样而不是直接取argmax动作是为了让演示数据带一些人性化的随机性更贴近真实场景。如果专家演示完全确定性MaxEnt IRIL的熵项意义就被削弱了对比效果也不明显。4. TensorFlow实操三个算法的落地实现4.1 MaxEnt IRL的tabular实现经典MaxEnt IRL的奖励参数在网格世界里就是一个长度为25的向量w每个分量对应一个状态的奖励。特征就是状态one-hot编码所以特征匹配在这里等价于状态访问频率匹配。软价值迭代部分我用numpy写了一个精简单版本只保留状态访问频率计算所必需的部分def soft_value_iteration(r, env, max_iter100, tol1e-5): n env.n_states V np.zeros(n) for _ in range(max_iter): Q r env.gamma * V[env.s_next] V_new np.log(np.sum(np.exp(Q), axis1)) if np.max(np.abs(V_new - V)) tol: break V V_new # 用log-sum-exp技巧避免数值上溢 Q r env.gamma * V[env.s_next] policy np.exp(Q - V[:, None]) return V, policy这里有个细节Q r γ * V[s_next] 的写法严格来说不够严谨因为动作执行后如果撞墙停留原地实际获得的奖励应该是 r(当前状态) γ * V(当前状态)而不是 r(当前状态) γ * V(目标状态)。我在构建s_next时已经把留在原地的情况编码成了s_next[s, a] s所以这个式子能正确覆盖撞墙的情况不需要额外判别。梯度更新采用特征匹配误差def maxent_train(env, expert_freq, lr0.01, iters200, reg0.01): w np.zeros(env.n_states) for _ in range(iters): V, policy soft_value_iteration(w, env) model_freq compute_freq_by_policy(policy, env) grad expert_freq - model_freq - reg * w w lr * grad lr * 0.995 return wcompute_freq_by_policy的思路是给定策略从均匀分布或起止分布出发迭代计算每个状态被访问的频率。我实际用的是从起点出发、终止于终点的受限访问频率因为专家轨迹也只在起止段之间采样。如果用稳态分布算会和专家频率对不上训练会抖动。这个细节让我排查了整整一个晚上。4.2 Deep MaxEnt的神经网络实现Deep MaxEnt要做的修改是把奖励向量w替换成一个神经网络RewardNet输入状态索引的one-hot编码输出奖励值。难点在软价值迭代必须留在TensorFlow计算图里。下面这个版本用tf.gather实现转移索引每个可微步骤都对TensorFlow开放import tensorflow as tf class RewardNet(tf.keras.Model): def __init__(self, state_dim): super().__init__() self.fc1 tf.keras.layers.Dense(32, activationrelu, kernel_regularizerl2) self.fc2 tf.keras.layers.Dense(32, activationrelu, kernel_regularizerl2) self.out tf.keras.layers.Dense(1) def call(self, states): x self.fc1(states) x self.fc2(x) return self.out(x) def tf_soft_value_iteration(r, env, max_iter100): V tf.zeros(env.n_states) for _ in range(max_iter): # env.s_next 转成常量张量 next_v tf.gather(V, env.s_next_tensor) # [n_states, n_actions] Q r env.gamma * next_v V tf.reduce_logsumexp(Q, axis1) return V训练循环用GradientTape包裹每次前向计算奖励、软价值迭代、模型状态频率损失函数用专家频率和模型频率的交叉熵等于是在做最大似然估计。这里有个需要强调的经验神经网络奖励函数容易过拟合尤其是在只有25个状态的小网格上网络完全可以把非专家状态也拟合出一个假高值。我加了L2正则并把训练轮数控制在300以内效果才稳定。Deep MaxEnt恢复的奖励质量和网络初始化关系很大。我试过用MaxEnt的tabular结果做网络初始化收敛速度明显加快这说明深度IRL不是凭空学出来的给一个先验会稳很多。这个先验在真实场景里可以来自规则、成本函数或者仿真环境的粗略实验。4.3 LPIRL的线性规划求解LPIRL的实现相对独立用不到TensorFlow直接上scipy。核心是构造线性规划的约束矩阵。对于每个状态s设专家动作为a*任意其他动作a约束是Q(s, a*) - Q(s, a) ≥ 0在确定性转移下Q(s, a) R(s) γ * V*(s_next(s, a))其中V*是专家策略下的价值函数可以先用标准值迭代算出来。把R(s)看成变量这个约束就是关于R的线性不等式。为了增强唯一性我加了一个松弛变量t目标函数为最大化t同时要求每个状态上的Q值差都≥t。from scipy.optimize import linprog def lp_irl(env, expert_policy, V_star): n env.n_states A_ub, b_ub [], [] # 变量: [r_0...r_{n-1}, t] c np.zeros(n 1) c[-1] -1 # 最大化 t for s in range(n): a_star expert_policy[s] for a in env.actions: if a a_star: continue row np.zeros(n 1) # Q(s,a*) - Q(s,a) t # 展开后只剩下一项 - R(s) 抵消留下 R(s_next) 差项 row[env.s_next[s, a_star]] env.gamma row[env.s_next[s, a]] - env.gamma row[-1] -1 # t 移到左边 A_ub.append(row) b_ub.append(0) # R 的边界限制在 [-1, 1] bounds [(-1, 1) for _ in range(n)] [(None, None)] res linprog(c, A_ubnp.array(A_ub), b_ubnp.array(b_ub), boundsbounds, methodhighs) return res.x[:n], res.x[-1]这里r(s)项在作差时抵消了所以LPIRL直接恢复到的是价值结构中的最优性差异而不是绝对奖励值。这个特点在Ng和Russell的原论文里讲得很清楚IRL本质上只能恢复奖励的势差信息。网格世界的结果可以这样理解约束只关心每个状态下专家动作相对其他动作的Q值高低不关心奖励的绝对大小。5. 实验结果与对比5.1 恢复的奖励函数对比三种算法跑完后我把奖励向量画成5x5的热度图。MaxEnt IRL恢复出的奖励在终点附近最高起点附近最低障碍物周围形成了一段平滑的低价值走廊Deep MaxEnt的结果形状类似但因为神经网络有拟合能力某些局部状态上会出现小幅波动LPIRL的结果则更尖锐很多状态奖励直接落在边界值-1或1上形似二值图。这个差异其实反映了三者的目标函数不同。MaxEnt的熵正则让奖励尽量平滑Deep MaxEnt因为有网络容量和正则项会给出更复杂的曲面LPIRL线性规划的目标是拉开动作边际所以奖励会往边界上顶。如果你做实际项目需要奖励平滑度时优先考虑MaxEnt需要表达复杂依赖时考虑Deep MaxEnt需要可解释性和快速求解时考虑LPIRL。5.2 算法差异背后的原因有个很重要的理论点必须单独拎出来说IRL恢复奖励函数天然具有不确定性。给任意已知最优的奖励R(s)加上一个势函数项γΦ(s) - Φ(s)最优策略完全不变但奖励本身变了。这意味着无论哪种算法恢复出来的都只是某个等价类里的代表不是真实奖励。理解了这点再看实验对比就不会纠结于谁恢复得更准。MaxEnt因为熵最大化恢复的是等价类里行为最分散的那个代表LPIRL因为最大化边际恢复的是决策最自信的那个代表Deep MaxEnt则取决于网络初始化和正则约束可能落在等价类里任意位置。我后续在使用时都会额外关注奖励的相对差而非绝对值尤其在把奖励当作成本函数去接下游规划器时绝对值的偏移对策略几乎没影响差异主要体现在梯度幅值上。6. 常见问题与排查清单6.1 模型不收敛怎么办这个项目里我遇到的最多的坑就是MaxEnt IRL训练发散。典型表现是梯度值在一两百轮后突然爆炸奖励值飞到正负1000以上。排查下来原因有三个一是学习率太大MaxEnt的梯度方向在高维特征空间里本来就不算平滑lr超过0.05很容易翻车二是正则项缺失状态频率匹配问题在网格世界里天然有多个解不加正则就可能在解空间里震荡三是专家状态频率和模型状态频率的归一化方式不一致比如一个用绝对频次一个用概率分布梯度符号都会反。建议先把学习率降到0.01加上L2正则再确认两边频率都归一化到概率。6.2 特征设计与奖励尺度用MaxEnt IRL时特征设计直接影响结果。在网格世界里用one-hot特征是没有问题的每个状态单独一个维度表达能力强但样本效率低。如果你只有几十条专家轨迹又想用连续特征可以考虑用到终点的距离周围障碍物密度这类手工特征能大幅减少需要估计的参数数量。奖励的尺度也有讲究。我发现把奖励网络输出限制在[-1, 1]区间比不限制稳定得多尤其在Deep MaxEnt里输出未经约束的神经网络在初期会产生极高的Q值导致softmax策略几乎变成one-hot梯度直接消失。加上tanh激活或者做输出裁剪训练稳定性能肉眼可见地提升。6.3 TensorFlow环境相关坑Deep MaxEnt训练中我自己踩过的环境坑有两个。第一个是tf.gather的索引类型s_next必须预先转成int32或int64张量否则在session执行时会报类型不匹配的错误。第二个是TensorFlow 2.x下GPU内存不释放的问题网格世界这类小任务频繁建图很容易把显存占满最简单的办法是加per_process_gpu_memory_fraction限制或者干脆设成CPU执行。另外一个常见问题是环境版本里scipy的linprog方法差异。老版本默认用simplex方法高版本推荐用highs。如果发现LPIRL求解结果不稳定或者报奇异矩阵先检查linprog的method参数再检查约束矩阵里是不是有重复行。重复约束不会导致错误但会让求解器多花不少时间。7. 一点个人经验三个算法跑完我对IRL的整体印象是它不是一个调个包就能用的技术难点主要在对MDP结构和奖励等价类的理解上。如果你只为了写论文跑实验直接用现成的开源IRL库就行但如果是想真正用到产品里我强烈建议你自己手写一遍三大算法。手写的过程会让你被迫面对每个细节特征匹配、软价值迭代的可微性、线性规划的约束构造、奖励的非唯一性。这些知识点在任何文档里都学不到只有自己踩过坑才能形成直觉。最后再分享一个项目扩展方向把Deep MaxEnt的奖励网络换成卷积网络输入换成图像就可以处理类似Atari游戏那种像素级输入。我后续就用这个方式在自动驾驶的简单模拟器上试过转移矩阵不可用的时候需要用采样轨迹来估计状态频率训练难度会上一个台阶。建议读者先把网格世界的逻辑彻底吃透再往高维方向扩展会顺很多。本文还有配套的精品资源点击获取