
簡介這套Python/TensorFlow逆向強化學習IRL代碼實現面向機器學習與強化學習研究者集中解決了從專家演示中重建獎勵函數的難點覆蓋線性IRL、最大熵IRL、深度最大熵IRL三種經典與深度方法并配套1D/2D網格世界與值迭代求解器。資源共48個文件以22個Python腳本為核心另含18張訓練結果圖、模型檢查點及說明文檔壓縮包僅2.58MB便于快速下載與本地實驗。目前已有1281人學習瀏覽。代碼入口demo.py可直接運行linear_irl、maxent_irl、deep_maxent_irl等腳本分別對應不同算法實現value_iteration與gridworld模塊提供MDP環境與求解基礎相應圖片展示了獎勵圖、值函數對比和深度最大熵迭代過程可幫助讀者直觀理解各IRL算法的收斂效果與差異適合作為算法復現、課程設計或對比實驗的參考實現。 自己在實際項目里把逆向強化學習IRL這一套從理論擼到代碼完整實現了三個經典算法MaxEnt IRL、Deep MaxEnt IRL 和 LPIRL全程基于 Python 和 TensorFlow 2.x。IRL 的核心問題很簡單給一堆專家軌跡但沒有獎勵函數怎么反推出“專家為什么這么走”。這在模仿學習、機器人軌跡規劃、自動駕駛行為建模里都很有用。如果你已經玩過行為克隆想進一步把專家行為“翻譯”成可解釋的獎勵信號這篇文章就是我的完整踩坑記錄。1. 核心思路與算法選型1.1 IRL 到底在解決什么問題傳統強化學習是給定獎勵函數去學策略IRL 正好反過來給定專家軌跡去反推獎勵函數。這樣做的好處非常直接獎勵函數學出來之后不只是能模仿專家軌跡還能泛化到其他任務場景比如同一個獎勵函數換一個初始位置策略依然能表現得像專家。IRL 的基礎假設是專家在某個 MDP馬爾可夫決策過程中執行了近乎最優的策略但我們看不到這個策略只看到它產生的軌跡。目標就是找一組獎勵函數的參數讓專家軌跡在“從這個獎勵函數推導出的最優策略”下有最大的概率被生成。這個過程聽起來順理成章但真正做起來會碰到一個致命問題能解釋專家軌跡的獎勵函數有無限多個。你給軌跡加一個常數最優策略不變給所有經過的狀態都加同樣的偏置策略還是不變。所以 IRL 必須通過約束、先驗或者熵來從這無限多個解里挑一個合理的。1.2 三種算法的定位差異我這次選的三個算法正好代表了 IRL 三條技術路線直接對比一下算法獎勵函數形式核心求解思路典型適用場景MaxEnt IRL線性特征加權最大熵原理 Soft Value Iteration狀態空間有限、特征可手工設計的小型環境比如 Grid World、小型機器人路徑規劃Deep MaxEnt IRL神經網絡最大熵原理 深度網絡逼近獎勵高維狀態空間、特征難以手工設計比如圖片輸入、連續狀態LPIRL線性特征加權線性規劃讓專家策略優于候選策略策略集合可枚舉或采樣、獎勵參數較少的教學演示場景選型邏輯很清晰如果你剛接觸 IRL先跑 MaxEnt因為它的每一步都有明確的可視化和數值意義能幫你建立“特征期望”“配分函數”這些直覺如果狀態是高維的就上 Deep MaxEnt如果問題規模很小可以枚舉候選策略LPIRL 這種線性規劃思路反而最可控。2. 環境準備與軌跡數據構造2.1 Python 與 TensorFlow 版本選擇我這次的環境是 Python 3.10 TensorFlow 2.12。坦率說TensorFlow 2.10 之后的 API 已經非常穩定tf.GradientTape、tf.function這些接口完全夠用。需要裝的庫不多pip install tensorflow2.12.0 numpy scipy matplotlib有一個容易被忽略的點如果你要跑 Deep MaxEntTensorFlow 和 Python 版本的兼容性一定要提前查好。比如 TensorFlow 2.12 在 Windows 上只支持到 Python 3.11裝錯版本會出現ModuleNotFoundError: No module named tensorflow這種最折磨人的問題。2.2 構造一個可調試的專家軌跡我用了一個 5x5 的 Grid World 作為測試環境動作空間是上下左右專家策略是先繞開障礙物、再以最短路徑走到終點。為了生成專家軌跡我先手工寫了一個“已知最優策略”的規則策略然后讓智能體按這個策略走 50 條軌跡出來。代碼里表示狀態的方式比較關鍵我用的是 one-hot 特征25 個狀態就映射成 25 維向量import numpy as np def state_to_features(state_idx, n_states25): feat np.zeros(n_states, dtypenp.float32) feat[state_idx] 1.0 return feat這條軌跡數據會同時喂給 MaxEnt 和 LPIRLDeep MaxEnt 那邊雖然也是同樣格式但因為它內部有神經網絡特征維度可以更大不用手工設計。如果你用的環境狀態維度特別高建議先用 one-hot 跑通整個流程再去換復雜特征。3. MaxEnt IRL從最大熵原理到完整實現3.1 為什么是“最大熵”MaxEnt IRL 的核心思想是在滿足“專家特征期望 學習到的策略特征期望”這個約束的前提下找一個熵最大的策略分布。直覺上就是不要假設太多額外信息得到一個最“隨機”、最不偏不倚的獎勵函數。在數學上MaxEnt IRL 把整條軌跡的概率建模成[ P(\tau) \propto \exp\left(\sum_{t} R(s_t, a_t)\right) ]其中 ( R(s_t, a_t) \theta^T f(s_t, a_t) )。也就是說累計獎勵越高的軌跡被專家選中的概率越大。這個概率分布用能量模型來解釋獎勵就是負能量。有了這個概率分布學習目標就很自然了用梯度上升最大化專家軌跡的似然。求梯度時會出現一個配分函數直接用暴力求和會指數爆炸Ziebart 等人給出的解決方案是 Soft Value Iteration在表格型環境中反復迭代以下 Bellman 算子[ V_{soft}(s) \log \sum_{a} \exp(Q_{soft}(s, a)) ][ Q_{soft}(s, a) R(s, a) \gamma \sum_{s} P(s|s, a) V_{soft}(s) ]3.2 Soft Value Iteration 的工程實現這一塊是我調試最久的地方因為公式看著簡單落到代碼里全是矩陣維度和數值穩定性的問題。import tensorflow as tf def soft_value_iteration(reward, transitions, gamma0.99, tau0.01, max_iter1000): # reward: shape [n_states, n_actions] n_states, n_actions reward.shape V tf.zeros(n_states, dtypetf.float32) for _ in range(max_iter): Q reward gamma * tf.reduce_sum(transitions * V[tf.newaxis, tf.newaxis, :], axis-1) # log-sum-exp帶 maxtrick 提高數值穩定性 V_new tf.reduce_logsumexp(Q / tau, axis1) * tau if tf.reduce_max(tf.abs(V_new - V)).numpy() 1e-5: break V V_new Q reward gamma * tf.reduce_sum(transitions * V[tf.newaxis, tf.newaxis, :], axis-1) policy tf.nn.softmax(Q / tau, axis1) return V, Q, policy這里我用了 temperature 參數 tau默認設 0.01控制策略的隨機程度。tau 越小策略越接近貪心但在求梯度時越容易遇到極小值導致梯度消失所以實踐中我是從 0.1 開始收斂后再調低到 0.01。3.3 特征期望計算與權重更新有了 soft policy 之后就可以計算在當前參數下的特征期望def compute_feature_expectation(policy, transitions, init_state, features, horizon20): n_states, n_actions policy.shape state_dist tf.one_hot([init_state], n_states, dtypetf.float32) feat tf.zeros(features.shape[-1], dtypetf.float32) for _ in range(horizon): action_probs tf.matmul(state_dist, policy)[0] feat tf.reduce_sum(action_probs[:, tf.newaxis] * features, axis0) state_dist tf.matmul(tf.transpose(action_probs[:, tf.newaxis] * transitions, [0, 1]), state_dist) return feat然后計算專家軌跡上的平均特征更新權重。完整訓練循環大致是def train_maxent_irl(expert_features, transitions, features, lr0.01, epochs100): theta tf.Variable(tf.random.normal([features.shape[-1]], stddev0.01)) for step in range(epochs): with tf.GradientTape() as tape: reward tf.linalg.matvec(features, theta) reward tf.reshape(reward, [n_states, n_actions]) V, Q, policy soft_value_iteration(reward, transitions) policy_feat compute_feature_expectation(policy, transitions, init_state0, featuresfeatures) log_likelihood tf.reduce_sum(expert_feature * theta) - tf.reduce_sum(policy_feat * theta) loss -log_likelihood grads tape.gradient(loss, [theta]) theta.assign_sub(lr * grads[0])4. Deep MaxEnt IRL用神經網絡替代手工特征4.1 為什么要上深度版本MaxEnt IRL 最大的瓶頸是獎勵函數只能是特征向量的線性函數。在 5x5 的 Grid World 里 one-hot 特征還沒問題但換成圖像輸入手工提取特征基本不現實。Deep MaxEnt IRL 的核心改動是把獎勵函數 ( R_\omega(s, a) ) 換成一個神經網絡參數從線性權重變成網絡權重。訓練時每步都需要走一遍 Soft Value Iteration 來求當前網絡的策略然后用這個策略計算損失、回傳梯度。這聽起來很順但第一次跑的時候我折騰了很久不收斂。問題不在于網絡結構而在于“獎勵函數的尺度”沒法控制。線性模型里權重范圍天然有限神經網絡輸出層一旦沒有任何限制獎勵值的尺度會亂飄直接導致 Soft Policy 迅速坍縮到確定性策略損失變成 NaN。4.2 核心模型與訓練循環我采用的是比較保守的做法獎勵網絡用兩層全連接輸出層加上 LayerNorm 來限制輸出范圍然后再乘一個可學習的縮放系數。這樣既保持了表達能力又把獎勵值范圍控制在穩定區間。class RewardNetwork(tf.keras.Model): def __init__(self, hidden_dim64): super().__init__() self.fc1 tf.keras.layers.Dense(hidden_dim, activationrelu) self.fc2 tf.keras.layers.Dense(hidden_dim, activationrelu) self.out tf.keras.layers.Dense(1) self.ln tf.keras.layers.LayerNormalization() def call(self, states, actions): x tf.concat([states, actions], axis-1) x self.fc1(x) x self.fc2(x) reward self.ln(self.out(x)) return reward訓練循環大致結構如下optimizer tf.keras.optimizers.Adam(learning_rate1e-3) for epoch in range(100): with tf.GradientTape() as tape: reward reward_net(states, actions) # 批量計算 reward tf.reshape(reward, [n_states, n_actions]) V, Q, policy soft_value_iteration(reward, transitions) # 計算 soft Q 下的最優策略再計算專家軌跡的負對數似然 log_pi tf.math.log(tf.gather_nd(policy, expert_idx) 1e-8) loss -tf.reduce_mean(log_pi) grads tape.gradient(loss, reward_net.trainable_variables) optimizer.apply_gradients(zip(grads, reward_net.trainable_variables))這里每一步都要跑一次完整的 Soft Value Iteration所以如果你的環境維度太大訓練會非常慢。我的經驗是先在 5x5 的小環境里把網絡結構調通再考慮擴大規模。4.3 訓練不收斂的三個關鍵修正第一個是輸出層不要加sigmoid或tanh不然梯度很容易消失獎勵值也表達不了負收益。第二個是temperature要從大往小調不要一開始就用 0.01我一般從 1.0 起步每 20 輪降一半。第三個是獎勵網絡的學習率要比普通監督學習小一個量級因為損失信號經過 Soft Value Iteration 傳遞過來本身波動就大學習率太大很容易震蕩。5. LPIRL線性規劃求解獎勵函數5.1 線性規劃建模思路LPIRL 的思路和最大熵完全不同它不假設概率分布而是直接利用 IRL 的定義如果專家策略是最優的那么專家策略的累計回報應該大于等于任意其他策略的累計回報。給定候選策略集合 ( \Pi {\pi_1, \pi_2, \dots, \pi_m} )其中包含專家策略 ( \pi_E )我們要找一個獎勵權重 ( w )使得[ V_{E}(w) \geq V_{i}(w) \quad \forall \pi_i \in \Pi ]其中 ( V_\pi(w) E_\pi[\sum_t w^T f(s_t, a_t)] )。每個約束都是關于 ( w ) 的線性不等式所以整個問題可以轉成線性規劃。為了從無限多個解里挑出穩定的解我加了兩個額外目標讓 ( w ) 的 L2 范數盡可能小同時最大化“專家策略超過其他策略的平均邊際”。這兩個目標本身也是線性的可以直接塞進線性規劃里。5.2 用 SciPy 實現核心求解構造候選策略集合是最麻煩的部分。我直接枚舉了 Grid World 里所有確定性策略但現實任務里不可能這樣一般會用隨機采樣策略或者用一些探索性策略代替。from scipy.optimize import linprog def solve_lpirl(expert_feature, candidate_features, eps1e-3): # 變量: [w_0, w_1, ..., w_{d-1}, slack_0, ..., slack_{m-1}] d expert_feature.shape[0] m candidate_features.shape[0] c np.zeros(d m) c[d:] -1.0 # 最大化 slack等價于最小化 -slack A_ub [] b_ub [] for i in range(m): row np.zeros(d m) row[:d] candidate_features[i] - expert_feature row[d i] 1.0 A_ub.append(row) b_ub.append(-eps) A_ub np.array(A_ub) b_ub np.array(b_ub) res linprog(c, A_ubA_ub, b_ubb_ub, bounds[(None, None)] * d [(0, None)] * m, methodhighs) return res.x[:d], res.fun這個實現里有一個很容易忽略的坑候選策略集合里必須包含專家策略本身。如果你的采樣策略里沒有覆蓋到專家策略線性規劃會有多個可行解求解器返回的結果會非常依賴初始值。5.3 LPIRL 與 MaxEnt 的對比觀察我同樣在 5x5 Grid World 上跑了 LPIRL最后得到的獎勵權重熱力圖和 MaxEnt 的結果比較接近但訓練過程明顯更“脆”。MaxEnt 是梯度上升每一步都平滑地優化期望LPIRL 則是一錘子買賣約束稍微沒構造好解出來的獎勵就會非常離譜。比如我一開始忘了加 L2 正則項解出來的權重達到了幾千完全沒法用。后來在目標函數里加了權重的 L2 項才把尺度拉回正常范圍。6. 常見問題與排查技巧實錄6.1 MaxEnt 訓練日志里 loss 變成 NaN這是我最常碰到的問題十次里有八次是 Soft Value Iteration 的數值溢出。reduce_logsumexp能解決一部分問題但因為 Grid World 的轉移矩陣是稀疏的某些狀態在迭代過程中V會一直減小最后變成-inf。我在V上加了上限鉗制比如限制到 [-100, 100]效果非常明顯。另一個常見原因是梯度更新步長太大直接把權重推出穩定范圍。6.2 Deep MaxEnt 訓練慢得無法忍受Deep MaxEnt 的每一步都要跑 Soft Value Iteration在小環境里還行一旦狀態空間變大迭代次數和網絡訓練的耗時都會飛漲。解決辦法是把 Soft Value Iteration 的迭代次數上限設小一點比如 50 次訓練前期不需要那么高的精度后面再加大。還有一個經驗是與其每輪更新一次網絡參數不如攢一批專家軌跡多更新幾次收斂速度反而更快。6.3 TensorFlow 版本相關的奇怪報錯如果你在跑代碼時遇到類似No module named tensorflow.python.ops.numpy_ops這種報錯先檢查版本是不是太老建議直接升到 2.10 以上。另一個高頻報錯是tf.gather_nd索引維度不匹配因為expert_idx的形狀必須和policy的維度一致建議在傳參前打印一下expert_idx.shape和policy.shape絕大多數問題一眼就能看出來。6.4 專家軌跡數量少導致獎勵嚴重偏向專家軌跡只有十幾條的時候學出來的獎勵函數經常會出現“只認軌跡經過的狀態其他狀態隨意”的情況。這個問題的根源不是算法本身而是特征表達不夠平滑。我后來在特征上加了一個高斯核平滑把 one-hot 特征替換成每個狀態與終點之間的距離特征效果立竿見影。這說明 IRL 對特征設計的敏感度比我想象中高得多。7. 我自己在實踐中積累的幾條經驗最后分享三個我一直在用的實操技巧少踩一個是一個。第一所有 IRL 算法實現的檢查順序都是先看策略分布的形狀再看損失曲線最后再看獎勵值。如果策略分布很快就變到完全確定性先懷疑 temperature 是不是太低如果損失是平的先懷疑 Soft Value Iteration 有沒有收斂而不是懷疑網絡結構。第二專家軌跡的質量比數量重要得多。我試過用 100 條噪聲很大的軌跡效果遠不如 20 條非常干凈的最優軌跡。這也符合 IRL 的概率假設它是在給“專家”建模不是給“平均水平”建模。第三當行為克隆能輕松解決這個問題時不要為了炫技而強行用 IRL。行為克隆是監督學習速度快、穩定IRL 的優勢在于多任務泛化和獎勵可解釋性。如果需求只是“模仿軌跡”行為克隆一天就能搞定IRL 可能要一周。只有在需要“解釋專家為什么這么做”或者“把獎勵遷移到新環境”時IRL 才真正值得投入。本文還有配套的精品資源點擊獲取