简介:这份资源是面向强化学习与逆向强化学习(IRL)方向学习者与研究人员的一套示例代码教程,重点解决从专家演示中反推奖励函数这一核心问题,适合具备Java编程基础、希望深入理解IRL算法实现的中高级读者。压缩包共497个文件,以484个Java源码为主体,辅以少量xml配置、pom构建文件与jar依赖库,整体约2.29MB,结构紧凑、便于直接导入IDE运行调试。内容涵盖学徒学习(ApprenticeshipLearning)、网格世界域建模、多智能体性能绘图以及SCPSolver等求解器集成,并附带了作者为搭建IRL框架而修改过的BURLAP代码库快照,读者可借此完整复现从环境建模到奖励推断的实验流程,理解IRL与BURLAP框架的衔接方式。目前已有1046人学习下载,适合作为课程设计、科研入门或算法复现的参考素材。
1. 逆强化学习教程代码拆解:从演示轨迹反推奖励函数的完整路径
逆强化学习(IRL)解决的是一个很实际的问题:你手头有一堆专家演示轨迹,但不知道背后的奖励函数长什么样,想让机器自己把这个奖励函数猜出来。这份 IRLTutorial 示例代码就是干这个的——它把 IRL 的核心流程拆成可运行的模块,从轨迹采样、特征构造,到奖励函数迭代求解,再到策略评估,每一步都有对应的实现。适合已经了解强化学习基础、想动手跑通 IRL 流程的开发者,也适合需要把 IRL 用到机器人示教、自动驾驶决策复现等场景的从业者。代码结构不复杂,但里面有几个参数和收敛判断的细节,不踩一遍不容易注意到。
2. 环境搭建与核心模块拆解:先跑通再改参数
2.1 依赖安装与目录结构确认
拿到代码包后第一件事不是急着跑主程序,而是先确认依赖版本。IRL 类项目对数值计算库的版本比较敏感,尤其是 numpy 和 scipy,版本差一个大版本,矩阵求解的结果可能就对不上。
# 建议用虚拟环境隔离,避免和系统 Python 冲突 python -m venv irl_env source irl_env/bin/activate # Windows 下用 irl_env\Scripts\activate # 安装核心依赖,版本按代码包里的 requirements 来 pip install -r requirements.txt # 如果 requirements 没锁版本,我一般会手动固定这几个 pip install numpy==1.24.3 scipy==1.10.1 matplotlib==3.7.1装完之后先看目录结构。常见做法是envs/放环境定义,irl/放算法核心,utils/放轨迹处理和特征提取,main.py或run.py是入口。先别改任何代码,直接跑一遍默认配置,确认能出结果。
# 先跑默认配置,观察输出是否正常 python main.py --env gridworld --algo maxent --iterations 50这一步的目的是建立基线。如果默认配置都跑不通,后面调参就是瞎调。跑通之后记录下默认的收敛曲线和最终奖励函数的值,作为后续对比的参照。
2.2 轨迹采样与特征构造逻辑
IRL 的输入是专家轨迹,代码里通常有一个generate_expert_trajectories或类似的函数。这里的关键是特征函数的设计——它决定了奖励函数能表达多复杂的偏好。
# 典型的特征构造:把状态映射成特征向量 def state_features(state, env): """ 将环境状态转换为特征向量。 常见做法是 one-hot 编码 + 手工特征拼接。 """ # 基础 one-hot:每个状态一个维度 one_hot = np.zeros(env.n_states) one_hot[state] = 1.0 # 手工特征:比如到目标的距离、是否靠近障碍物 dist_to_goal = env.distance_to_goal(state) near_obstacle = env.is_near_obstacle(state) # 拼接后返回,维度 = n_states + 2 return np.concatenate([one_hot, [dist_to_goal, near_obstacle]])这段代码的逻辑是:奖励函数被参数化为R(s) = w^T * phi(s),其中phi(s)就是特征向量,w是要求解的权重。特征设计得好不好,直接决定 IRL 能不能恢复出有意义的奖励。如果只用 one-hot,那奖励函数就是每个状态一个独立值,表达能力强但容易过拟合;加入手工特征相当于引入先验,泛化会好一些,但特征选得不对就会引入偏差。
参数方面,n_states是状态总数,distance_to_goal和is_near_obstacle是环境提供的辅助函数。改特征的时候注意维度要对齐,否则矩阵运算会报错。
2.3 最大熵 IRL 的迭代求解流程
代码里最核心的部分是最大熵 IRL 的求解循环。它的思路是:在当前奖励函数下计算最优策略,然后用这个策略产生的状态访问频率去匹配专家轨迹的状态访问频率,不匹配就调整奖励权重。
# 最大熵 IRL 主循环(简化版) def maxent_irl(expert_trajectories, env, feature_fn, lr=0.01, n_iters=100): """ 最大熵逆强化学习求解。 lr: 学习率,控制每次权重更新的步长 n_iters: 迭代次数,太少不收敛,太多浪费时间 """ # 初始化权重 weights = np.zeros(feature_fn(0, env).shape[0]) # 计算专家轨迹的特征期望 expert_feature_exp = compute_feature_expectation(expert_trajectories, feature_fn, env) for i in range(n_iters): # 1. 用当前权重构造奖励函数 reward_fn = lambda s: np.dot(weights, feature_fn(s, env)) # 2. 用当前奖励求最优策略(值迭代或策略迭代) policy = value_iteration(env, reward_fn) # 3. 用策略采样轨迹,计算模型的特征期望 model_trajectories = sample_trajectories(policy, env, n_traj=len(expert_trajectories)) model_feature_exp = compute_feature_expectation(model_trajectories, feature_fn, env) # 4. 梯度更新:专家期望 - 模型期望 grad = expert_feature_exp - model_feature_exp weights += lr * grad # 5. 打印收敛信息 if i % 10 == 0: print(f"Iter {i}, grad norm: {np.linalg.norm(grad):.4f}") return weights这段代码有几个关键点。第一,compute_feature_expectation计算的是轨迹上特征的平均值,专家轨迹和模型轨迹要用同一套特征函数。第二,梯度方向是专家期望 - 模型期望,直观理解就是:如果某个特征在专家轨迹里出现得比模型轨迹多,就提高它的权重。第三,学习率lr不能太大,否则权重会震荡;也不能太小,否则迭代 100 次还没收敛。我一般会先跑 50 次看梯度范数的变化,如果还在明显下降就加到 200 次。
value_iteration是内层循环,每次迭代都要跑一遍,所以整体计算量不小。如果环境状态多,建议用向量化实现,别用 Python 循环逐状态更新。
3. 奖励函数恢复效果验证:怎么判断跑出来的结果是对的
3.1 用策略性能做间接验证
IRL 没有 ground truth 奖励函数可以对比(如果有的话就不用逆强化学习了),所以验证只能间接来。最常用的方法是:把学到的奖励函数拿去重新做正向强化学习,看得到的策略在真实环境里表现如何。
# 验证流程:用学到的奖励训练策略,在真实奖励下评估 def validate_reward(learned_weights, env, feature_fn, true_reward_fn, n_eval=100): """ learned_weights: IRL 学到的权重 true_reward_fn: 真实奖励函数(仅用于评估,不参与训练) n_eval: 评估回合数 """ # 用学到的奖励构造奖励函数 learned_reward_fn = lambda s: np.dot(learned_weights, feature_fn(s, env)) # 用学到的奖励训练策略 learned_policy = value_iteration(env, learned_reward_fn) # 在真实奖励下评估策略 total_return = 0.0 for _ in range(n_eval): traj = sample_trajectories(learned_policy, env, n_traj=1)[0] total_return += sum(true_reward_fn(s) for s in traj) avg_return = total_return / n_eval print(f"Average return under true reward: {avg_return:.2f}") return avg_return这个验证的逻辑是:如果学到的奖励函数和真实奖励函数足够接近,那么基于学到的奖励训练出来的策略,在真实奖励下也应该表现不错。如果策略性能很差,说明奖励函数恢复得不对,需要回头检查特征设计或迭代过程。
参数n_eval一般设 100 以上,太少的话方差大,看不出差异。true_reward_fn只在评估时用,训练过程中不能泄露给 IRL 算法,否则就是作弊了。
3.2 特征权重的可视化与对比
除了策略性能,还可以直接看学到的权重向量。如果特征设计得有意义,权重的符号和相对大小应该能反映专家的行为偏好。
# 可视化权重,和真实权重对比(如果已知) import matplotlib.pyplot as plt def plot_weights(learned_weights, true_weights=None, feature_names=None): """ 画出学到的权重,如果有真实权重就并排对比。 """ n = len(learned_weights) x = np.arange(n) plt.figure(figsize=(10, 4)) plt.bar(x - 0.2, learned_weights, width=0.4, label='Learned') if true_weights is not None: plt.bar(x + 0.2, true_weights, width=0.4, label='True') if feature_names: plt.xticks(x, feature_names, rotation=45) plt.legend() plt.title('Reward Weight Comparison') plt.tight_layout() plt.savefig('weight_comparison.png') plt.show()如果某些特征的权重符号反了,比如真实奖励里“靠近目标”是正权重,学出来是负的,那说明迭代没收敛或者特征尺度差异太大。常见做法是对特征做归一化,让每个维度的取值范围接近,这样梯度更新时不会偏向某些维度。
3.3 收敛性检查与迭代次数选择
最大熵 IRL 的收敛判断没有统一标准,代码里一般用梯度范数或者权重变化量来卡。
# 在迭代循环里加入收敛检查 prev_weights = weights.copy() weights += lr * grad weight_change = np.linalg.norm(weights - prev_weights) if weight_change < 1e-4: print(f"Converged at iter {i}") break阈值1e-4是我常用的经验值,太小会跑很久,太大可能还没收敛就停了。另外可以同时监控梯度范数,如果梯度范数在某个值附近震荡不再下降,也说明基本收敛了。
迭代次数不是越多越好。跑太多轮之后,如果特征表达能力有限,权重可能会过拟合到专家轨迹的噪声上,反而让策略性能下降。我一般会跑 200 轮,每 20 轮存一次权重,最后用验证集挑最好的那组。
4. 避坑与常见问题排查:那些跑不通和跑不对的情况
4.1 梯度爆炸导致权重变成 NaN
现象:迭代几次后权重突然变成 NaN,后续所有计算都失效。
原因:学习率太大,或者特征值范围差异太大,导致梯度范数爆炸。
解决:先把学习率降到 0.001 试试。如果还不行,对特征做标准化,让每个维度的均值为 0、方差为 1。代码里可以在compute_feature_expectation之前加一步归一化。
4.2 学到的奖励函数所有状态都差不多
现象:跑完 IRL 后,奖励函数在每个状态上的值几乎一样,策略没有区分度。
原因:特征设计太单一,比如只用 one-hot,而专家轨迹覆盖的状态又很少,导致很多状态的特征期望都是零,权重更新没有信号。
解决:增加手工特征,比如状态到关键点的距离、动作代价等。另外检查专家轨迹的数量,如果只有一两条,特征期望的估计方差会很大,建议至少 20 条以上。
4.3 策略评估时回报为负且一直不提升
现象:用学到的奖励训练策略,在真实奖励下评估,回报一直是负数,且随迭代没有改善。
原因:奖励函数的符号反了。最大熵 IRL 的梯度方向是专家期望 - 模型期望,如果代码里写成了模型期望 - 专家期望,学到的奖励就会鼓励模型去模仿错误的行为。
解决:检查梯度更新那行代码,确认是expert_feature_exp - model_feature_exp。另外可以打印前几轮的梯度值,如果专家期望和模型期望的差值一直是负的,说明方向反了。
4.4 环境状态数多时运行极慢
现象:状态数超过 1000 后,每次迭代要跑好几分钟,跑完 100 轮要几个小时。
原因:value_iteration用了 Python 循环逐状态更新,没有向量化。
解决:把值迭代改成矩阵运算。状态转移矩阵提前算好,每次迭代用np.max对动作维度做聚合。如果环境是确定性的,还可以用策略迭代代替值迭代,收敛更快。
4.5 专家轨迹和模型轨迹的特征期望量纲不一致
现象:梯度值很大,权重更新剧烈震荡。
原因:专家轨迹和模型轨迹的采样数量不同,导致特征期望的尺度不一样。
解决:确保compute_feature_expectation里除以的是轨迹数量,而不是总步数。另外模型轨迹的采样数量要和专家轨迹一致,别一个采 10 条一个采 100 条。
5. 进阶技巧:把 IRL 用到自己的环境里
5.1 自定义环境的接入方式
代码包里的环境是示例用的网格世界,要接到自己的环境里,需要实现几个接口:状态转移函数、奖励函数(仅用于生成专家轨迹)、状态特征函数。常见做法是继承一个基类,然后覆盖这几个方法。
class CustomEnv(BaseEnv): def __init__(self): self.n_states = 500 self.n_actions = 4 self.transition_matrix = self._build_transition() def _build_transition(self): # 返回形状为 (n_states, n_actions, n_states) 的转移概率矩阵 pass def state_features(self, state): # 返回该状态的特征向量 pass def true_reward(self, state): # 仅用于生成专家轨迹,IRL 训练时不使用 pass接入之后先用随机策略生成一批轨迹,确认环境本身没有 bug,再用专家策略生成专家轨迹。专家策略可以是手工设计的规则,也可以是用真实奖励训练出来的最优策略。
5.2 特征设计的几个实用原则
特征设计是 IRL 里最需要经验的部分。我一般会遵循几个原则:第一,特征要能区分不同状态的价值,如果两个状态的特征向量完全一样,IRL 就没法给它们不同的奖励。第二,特征维度不要太高,一般控制在状态数的 1/10 到 1/5 之间,太高容易过拟合。第三,尽量用有物理意义的特征,比如距离、速度、能量消耗,这样学出来的权重可解释性更好。
如果特征太多,可以用 L1 正则化做稀疏化,让不重要的特征权重趋近于零。代码里可以在梯度更新后加一步软阈值操作。
5.3 从最大熵到其他 IRL 变体
最大熵 IRL 是最常用的基线,但它假设专家轨迹是最优的。如果专家演示里有次优行为,学到的奖励会有偏差。这种情况下可以试试其他变体,比如基于对抗训练的 IRL,或者用相对熵做正则化的版本。代码包里如果只实现了最大熵,可以自己加一个algo参数,把不同算法的求解逻辑分开。
我自己的习惯是:先用最大熵跑一遍看结果,如果策略性能不达标,再换其他算法对比。换算法的时候注意保持特征函数和轨迹数据不变,只改求解部分,这样才能公平比较。
从那以后我每次跑 IRL 都会先固定随机种子,把专家轨迹和模型轨迹的采样数量对齐,再开始调学习率。这个顺序能省掉很多玄学问题。希望帮到你。
本文还有配套的精品资源,点击获取