網(wǎng)絡(luò)的強化學習實踐)
簡介面向深度學習與強化學習初學者這份PDF系統(tǒng)講解深度強化學習DQN的核心原理并以“迷宮尋路”為例演示如何用神經(jīng)網(wǎng)絡(luò)近似Q函數(shù)突破傳統(tǒng)Q-Learning在高維狀態(tài)動作空間下的存貯與計算瓶頸。內(nèi)容涵蓋Q-Learning基礎(chǔ)、DQN損失函數(shù)設(shè)計、Experience Replay經(jīng)驗回放、epsilon-greedy動作選擇等關(guān)鍵知識點并基于TensorFlow實現(xiàn)一個可運行的迷宮求解示例源碼直接附在文檔中便于讀者對照理解與二次實踐。資源為單個PDF文件體積約205KB輕量易讀適合快速學習入門也適合作為課程設(shè)計或科研項目中引入DQN的參考資料。目前已有1774人學習下載口碑與實用性得到一定驗證。如需快速上手深度強化學習或希望用一個具體迷宮案例打通“原理—網(wǎng)絡(luò)構(gòu)建—訓練流程—結(jié)果分析”這份文檔能提供清晰的路徑與可直接參考的TensorFlow代碼。1. DQN跑通迷宮之前先想清楚它憑什么接替Q-table如果你翻過強化學習入門資料大概率見過這張6×6的迷宮獎勵矩陣分別在狀態(tài)2、4、5設(shè)置正獎勵墻壁位置一律給負分agent要從任意房間出發(fā)找到去5號房間的路。傳統(tǒng)Q-Learning的做法是維護一張Q-table每個state-action對占一格迷宮小的時候沒問題一旦狀態(tài)變多、動作變多表格大小指數(shù)膨脹內(nèi)存根本裝不下。這正是DQN出現(xiàn)的理由用神經(jīng)網(wǎng)絡(luò)去近似Q函數(shù)輸入當前狀態(tài)輸出每個動作的Q值把查表問題變成回歸問題。這篇筆記圍繞一個可跑的DQN迷宮源碼展開講清楚網(wǎng)絡(luò)結(jié)構(gòu)、epsilon探索、經(jīng)驗回放、損失計算這幾個核心模塊再把我實際運行時踩過的坑和排查思路一并列出。適合剛看完Q-Learning、想弄明白DQN到底改了什么的人也適合拿到代碼后不知道怎么調(diào)參數(shù)的新手。2. 項目結(jié)構(gòu)與神經(jīng)網(wǎng)絡(luò)搭建6×6迷宮如何塞進3層網(wǎng)絡(luò)2.1 先看懂這份源碼的骨架整份源碼只包含一個DeepQNetwork類沒有拆成多個文件這在教學Demo里很常見。類內(nèi)部按職責劃分成幾個方法create_network()負責建網(wǎng)絡(luò)select_action()負責按策略選動作save_store()負責存記憶step()負責執(zhí)行動作并返回獎勵和下一個狀態(tài)experience_replay()負責從記憶庫中抽樣訓練train()是主循環(huán)pay()是訓練完之后的測試展示。類的頂部定義了一組關(guān)鍵參數(shù)這些參數(shù)值得逐個看清楚因為它們直接決定訓練行為參數(shù)值作用OBSERVE1000前1000步只探索不訓練累積足夠記憶BATCH20每次從記憶庫抽20條樣本訓練INITIAL_EPSILON0.1epsilon初始值探索概率上限FINAL_EPSILON0.0001epsilon衰減下限EXPLORE3000000epsilon從初始值衰減到最小值所需總步數(shù)learning_rate0.001梯度下降學習率gamma0.9未來獎勵折損率memory_size5000經(jīng)驗回放池容量上限state_num6狀態(tài)數(shù)action_num6動作數(shù)state_list和action_list都用np.identity()生成單位矩陣每一行代表一個狀態(tài)或動作的one-hot編碼。比如狀態(tài)1編碼成[[0,1,0,0,0,0]]動作3編碼成[[0,0,0,1,0,0]]。這種編碼方式是理解后面損失函數(shù)計算的關(guān)鍵因為網(wǎng)絡(luò)輸出的是一個6維向量只有通過one-hot點乘才能把當前執(zhí)行動作對應(yīng)的Q值單獨取出來。r矩陣是這份源碼的核心先驗知識它提前定義好了迷宮的地圖結(jié)構(gòu)r np.array([[-1, -1, -1, -1, 0, -1], [-1, -1, -1, 0, -1, 100.0], [-1, -1, -1, 0, -1, -1], [-1, 0, 0, -1, 0, -1], [0, -1, -1, 1, -1, 100], [-1, 0, -1, -1, 0, 100], ])這里r[state][action]表示在狀態(tài)state下執(zhí)行動作action得到的即時獎勵next_state直接等于action。也就是說這個迷宮的規(guī)則是執(zhí)行動作a就走到房間a獎勵由r矩陣決定。動作5是目標房間從狀態(tài)1、4、5出發(fā)到達動作5都能拿到100分。2.2 create_network三層網(wǎng)絡(luò)如何輸出Q值create_network()是理解DQN替代Q-table的關(guān)鍵入口。先看代碼def create_network(self): self.q_eval_input tf.placeholder(shape[None, self.state_num], dtypetf.float32) self.action_input tf.placeholder(shape[None, self.action_num], dtypetf.float32) self.q_target tf.placeholder(shape[None], dtypetf.float32) neuro_layer_1 3 w1 tf.Variable(tf.random_normal([self.state_num, neuro_layer_1])) b1 tf.Variable(tf.zeros([1, neuro_layer_1]) 0.1) l1 tf.nn.relu(tf.matmul(self.q_eval_input, w1) b1) w2 tf.Variable(tf.random_normal([neuro_layer_1, self.action_num])) b2 tf.Variable(tf.zeros([1, self.action_num]) 0.1) self.q_eval tf.matmul(l1, w2) b2 self.reward_action tf.reduce_sum(tf.multiply(self.q_eval, self.action_input), reduction_indices1) self.loss tf.reduce_mean(tf.square((self.q_target - self.reward_action))) self.train_op tf.train.GradientDescentOptimizer(self.learning_rate).minimize(self.loss) self.predict tf.argmax(self.q_eval, 1)邏輯說明網(wǎng)絡(luò)結(jié)構(gòu)是6→3→6輸入層6個神經(jīng)元對應(yīng)6個狀態(tài)的one-hot編碼隱藏層3個神經(jīng)元配ReLU激活輸出層6個神經(jīng)元對應(yīng)6個動作的Q值。q_eval_input是狀態(tài)輸入action_input是當前執(zhí)行動作的one-hot編碼q_target是計算出的目標Q值這三個都是placeholder訓練時通過feed_dict填充。tf.multiply(self.q_eval, self.action_input)做的是逐元素相乘不是矩陣乘法。假設(shè)網(wǎng)絡(luò)對狀態(tài)1輸出的Q值是[[0.81, 0.5, 0.24, 0.513, 0.9, 0.71]]agent執(zhí)行了動作3action_input是[[0,0,0,1,0,0]]逐元素相乘后得到[[0,0,0,0.513,0,0]]reduce_sum把非零元素累加最終reward_action就是0.513——當前狀態(tài)下執(zhí)行動作3的Q值。這套做法本質(zhì)上是把離散動作的Q值提取轉(zhuǎn)化成回歸標簽避免了像分類任務(wù)那樣計算softmax交叉熵。2.3 損失函數(shù)的直覺理解源碼里loss用的是tf.square((self.q_target - self.reward_action))也就是均方誤差。為什么不用交叉熵因為DQN的輸出本質(zhì)是連續(xù)數(shù)值回歸網(wǎng)絡(luò)要擬合的目標不是概率分布而是每個動作的價值估計。q_target是目標Q值reward_action是當前網(wǎng)絡(luò)對已執(zhí)行動作的Q值估計訓練就是讓后者去逼近前者。這里有一個容易混淆的點q_target并不是固定不變的它本身由即時獎勵和下一狀態(tài)的最大Q值計算而來隨著網(wǎng)絡(luò)參數(shù)更新q_target也在漂移。這是DQN和普通監(jiān)督學習的本質(zhì)區(qū)別——監(jiān)督學習標簽固定DQN的標簽依賴網(wǎng)絡(luò)自身的預(yù)測。所以源碼中每次experience_replay()都在重新計算q_target而不是用預(yù)先算好的靜態(tài)標簽。我之前單獨跑過一個最小網(wǎng)絡(luò)驗證這個思路確認3層網(wǎng)絡(luò)加梯度下降能收斂再回到完整代碼里調(diào)參。新手建議也按這個節(jié)奏來先把網(wǎng)絡(luò)跑通再讓agent去探索否則網(wǎng)絡(luò)結(jié)構(gòu)錯了后面全是白費功夫。3. epsilon貪心與經(jīng)驗回放兩個參數(shù)直接決定能不能收斂3.1 select_action探索與利用的拉鋸戰(zhàn)select_action()是DQN里最容易翻車的函數(shù)之一它決定了agent是去嘗試沒走過的路探索還是走當前認知中最優(yōu)的路利用。源碼里的實現(xiàn)是標準epsilon-greedydef select_action(self, state_index): current_state self.state_list[state_index:state_index 1] if np.random.uniform() self.epsilon: current_action_index np.random.randint(0, self.action_num) else: actions_value self.session.run(self.q_eval, feed_dict{self.q_eval_input: current_state}) action np.argmax(actions_value) current_action_index action if self.step_index self.OBSERVE and self.epsilon self.FINAL_EPSILON: self.epsilon - (self.INITIAL_EPSILON - self.FINAL_EPSILON) / self.EXPLORE return current_action_index邏輯說明每次選動作時生成一個[0,1)區(qū)間的隨機數(shù)如果小于epsilon就走隨機策略在6個動作里隨便選一個否則把當前狀態(tài)輸入網(wǎng)絡(luò)取輸出Q值最大的動作。epsilon初始為0.1意味著訓練早期有10%的概率隨機探索隨著步數(shù)增加、epsilon逐步衰減到0.0001agent越來越依賴網(wǎng)絡(luò)判斷。參數(shù)說明INITIAL_EPSILON設(shè)成0.1其實偏低很多DQN實現(xiàn)會用0.9甚至1.0起步讓agent前期大量隨機探索。這個迷宮只有6個狀態(tài)0.1已經(jīng)夠用但換到更大的環(huán)境就得調(diào)高。EXPLORE設(shè)為3000000意味著衰減速度非常慢300萬步后才基本停止隨機探索配合OBSERVE1000前1000步只探索不訓練記憶池有足夠多樣本后網(wǎng)絡(luò)才開始學習。3.2 save_store記憶池的寫入與淘汰經(jīng)驗回放的核心意義在于打破時間序列樣本之間的相關(guān)性。如果每次采樣后立刻訓練相鄰兩個樣本高度相關(guān)網(wǎng)絡(luò)參數(shù)會朝一個方向持續(xù)偏置導致收斂困難甚至發(fā)散。源碼用deque實現(xiàn)記憶池def save_store(self, current_state_index, current_action_index, current_reward, next_state_index, done): current_state self.state_list[current_state_index:current_state_index 1] current_action self.action_list[current_action_index:current_action_index 1] next_state self.state_list[next_state_index:next_state_index 1] self.replay_memory_store.append(( current_state, current_action, current_reward, next_state, done)) if len(self.replay_memory_store) self.memory_size: self.replay_memory_store.popleft() self.memory_counter 1邏輯說明每次agent執(zhí)行一步動作就把(當前狀態(tài), 當前動作, 獎勵, 下一個狀態(tài), 是否結(jié)束)這個五元組追加到deque尾部。deque自帶popleft()當記憶數(shù)超過memory_size5000時自動淘汰最舊的記憶。這樣記憶池始終保留最近5000條經(jīng)驗既避免內(nèi)存無限增長也確保訓練樣本相對接近當前策略。參數(shù)說明memory_size設(shè)5000對6×6迷宮綽綽有余但要注意一個問題如果環(huán)境狀態(tài)空間很大5000條記憶可能覆蓋不到足夠多樣的狀態(tài)網(wǎng)絡(luò)會反復在少數(shù)狀態(tài)上過擬合。大場景一般設(shè)到100000以上。done這個變量在save_store里雖然存了但后面計算q_target時源碼并沒有真正使用它這是個隱患后面避坑章節(jié)細說。3.3 step函數(shù)獎勵矩陣的讀取規(guī)則step()是環(huán)境交互層代碼很短def step(self, state, action): reward self.r[state][action] next_state action done False if action 5: done True return next_state, reward, done邏輯說明next_state直接等于action意味著這是一個「搬到哪個房間」的決策問題而不是「往哪個方向走一步」的連續(xù)空間問題。6個動作對應(yīng)6個目標房間r[state][action]給出執(zhí)行該動作的即時獎勵。動作5是走出迷宮返回doneTrue。這里我最初踩過一個直覺錯誤我以為迷宮問題應(yīng)該用上下左右四方向動作看到6個動作還以為寫錯了。實際上這份源碼把「移動」抽象成「直接跳轉(zhuǎn)到目標房間」獎勵矩陣決定了哪些跳轉(zhuǎn)被懲罰、哪些被獎勵。理解了這點再看r矩陣就清晰了——負獎勵代表撞墻或非法移動0代表普通移動正獎勵代表到達目標。4. 訓練主循環(huán)與經(jīng)驗回放target_q是怎么一步步逼近的4.1 train函數(shù)先攢記憶再訓練train()是agent和環(huán)境交互的總控制器def train(self): current_state np.random.randint(0, self.action_num - 1) self.epsilon self.INITIAL_EPSILON while True: action self.select_action(current_state) next_state, reward, done self.step(current_state, action) self.save_store(current_state, action, reward, next_state, done) if self.step_index self.OBSERVE: self.experience_replay() if self.step_index 10000: break if done: current_state np.random.randint(0, self.action_num - 1) else: current_state next_state self.step_index 1邏輯說明主循環(huán)的流程是「選動作→執(zhí)行→存記憶→達到觀察步數(shù)后訓練→判斷終止」。兩個細節(jié)值得注意第一current_state初始化和done之后都用np.random.randint(0, self.action_num - 1)重新隨機上限是action_num - 1也就是5所以初始狀態(tài)永遠不是目標房間5避免agent一開始就站在終點第二訓練不是每一步都做step_index超過OBSERVE后才調(diào)用experience_replay()這是為了讓記憶池先攢夠樣本否則抽樣質(zhì)量太差。參數(shù)說明10000是硬編碼的最大訓練步數(shù)到達就退出。這個數(shù)字對6×6迷宮足夠了實際訓練中大概幾千步就能收斂但如果你增大迷宮規(guī)模這里也要跟著調(diào)大。np.random.seed沒有設(shè)置所以每次運行結(jié)果可能不同這是隨機訓練的常態(tài)。4.2 experience_replay樣本組裝與q_target計算experience_replay()的代碼最長但因為它是DQN的數(shù)據(jù)流核心值得仔細拆解def experience_replay(self): batch self.BATCH if self.memory_counter self.BATCH else self.memory_counter minibatch random.sample(self.replay_memory_store, batch) batch_state None batch_action None batch_reward None batch_next_state None batch_done None for index in range(len(minibatch)): if batch_state is None: batch_state minibatch[index][0] elif batch_state is not None: batch_state np.vstack((batch_state, minibatch[index][0])) # batch_action、batch_reward、batch_next_state、batch_done同樣處理 q_next self.session.run([self.q_eval], feed_dict{self.q_eval_input: batch_next_state}) q_target [] for i in range(len(minibatch)): current_reward batch_reward[i][0] q_value current_reward self.gamma * np.max(q_next[0][i]) if current_reward 0: q_target.append(current_reward) else: q_target.append(q_value) _, cost, reward self.session.run( [self.train_op, self.loss, self.reward_action], feed_dict{self.q_eval_input: batch_state, self.action_input: batch_action, self.q_target: q_target}) self.cost_his.append(cost) self.learn_step_counter 1邏輯說明先從記憶池隨機抽BATCH條樣本把五元組拆成五個獨立的batch數(shù)組。然后用batch_next_state喂給網(wǎng)絡(luò)得到所有下一狀態(tài)的動作Q值每個樣本取最大值np.max(q_next[0][i])乘以gamma后加上即時獎勵得到目標Q值q_target。最后用這個q_target和網(wǎng)絡(luò)當前預(yù)測的reward_action計算loss執(zhí)行一次梯度下降。這里有一個特殊處理if current_reward 0: q_target.append(current_reward)。翻譯成人話就是——如果這一步拿到了負獎勵撞墻或非法移動目標Q值直接等于這個負獎勵不做未來獎勵折損。為什么這樣處理因為負獎勵的下一步狀態(tài)可能也是死路如果繼續(xù)用reward gamma * max(q_next)會把負值通過折損傳導到前面的狀態(tài)導致所有狀態(tài)都學成負值。直接截斷讓agent明確記住「這一步不該走」。參數(shù)說明gamma0.9表示未來10步的獎勵折損到當前約0.35折損越快agent越短視折損越慢agent越傾向于考慮遠期收益。迷宮只有6個狀態(tài)0.9合適。BATCH20對這個小場景夠用大場景一般至少32或64。GradientDescentOptimizer的learning_rate0.001偏保守換來穩(wěn)定。4.3 網(wǎng)絡(luò)輸出與損失逼近的過程理解q_target這段代碼需要厘清一個容易混淆的地方代碼里用self.q_eval這個網(wǎng)絡(luò)同時計算了reward_action和q_next。也就是說目標Q值和當前Q值用的是同一個網(wǎng)絡(luò)、同一組權(quán)重。這在原始DQN的2013版本里是合法的做法但訓練過程中網(wǎng)絡(luò)權(quán)重一直在變化導致q_target也在不斷變化相當于用移動的目標訓練移動的模型容易震蕩。2015年Nature版DQN引入了Target Network來緩解這個問題——用另一組延遲更新的參數(shù)計算q_target讓目標在一段時間內(nèi)保持穩(wěn)定。如果這份源碼訓練不收斂優(yōu)先考慮改成雙網(wǎng)絡(luò)結(jié)構(gòu)。不過對這個6×6迷宮單網(wǎng)絡(luò)的實現(xiàn)已經(jīng)夠用我在本地跑通的過程里loss從幾千降到幾十的量級大概只需要幾百次experience_replay調(diào)用。5. 避坑清單DQN在6×6迷宮上的五個翻車現(xiàn)場5.1 訓練結(jié)束后測試路徑有問題agent仍然隨機跑現(xiàn)象pay()里打印測試路徑時agent從狀態(tài)0出發(fā)走到了3又從3跳回1來回繞圈完全看不出學到了最優(yōu)策略。原因select_action()在測試階段仍然有epsilon的隨機探索分支。如果epsilon衰減不到位或者訓練步數(shù)不夠隨機動作概率仍然偏高。這個坑的根源在于訓練主循環(huán)和測試共用同一個動作選擇函數(shù)沒有區(qū)分訓練模式和推理模式。解決在pay()里直接用self.session.run(self.predict, feed_dict{...})取argmax結(jié)果繞過epsilon判斷?;蛘咴O(shè)置一個self._is_training標志位測試時強制走貪心分支。我當時的做法是復制一份純貪心的動作選擇邏輯到pay()里確保測試路徑完全確定。5.2 負獎勵處的q_target直接截斷導致正負樣本訓練比例失真現(xiàn)象訓練過程中l(wèi)oss下降到一定值后就不再變化打印路徑發(fā)現(xiàn)agent只學會了避開明顯的負獎勵動作但對多個正獎勵動作沒有偏好路徑不唯一。原因experience_replay()里if current_reward 0直接截斷q_target這個邏輯的初衷是好的——避免負值傳導到前序狀態(tài)。但它會造成學習信號的不平衡大量負獎勵樣本的優(yōu)化目標是「等于負值」少量正獎勵樣本的優(yōu)化目標是「reward gamma * max(q_next)」后者被前者淹沒。解決如果追求最優(yōu)最短路我的建議是把所有非負獎勵統(tǒng)一走current_reward gamma * np.max(q_next)的計算路徑只在doneTrue時才強制q_target current_reward。也就是讓負獎勵也能通過折損向后續(xù)狀態(tài)傳導。改完之后agent會明顯更傾向于選擇能連到正獎勵的路徑而不是孤立地避開負獎勵。5.3 done標志物存了卻不用到達終點后的狀態(tài)沒有正確處理現(xiàn)象訓練收斂后從狀態(tài)1出發(fā)的路徑是1→3→5但從狀態(tài)0出發(fā)偶爾會走出0→4→3→1→3的環(huán)然后才到5路徑明顯不是最短。原因save_store()存了done標志但experience_replay()計算q_target時完全沒看它。當agent到達狀態(tài)5后next_state還是5q_next會輸出狀態(tài)5下所有動作的Q值其中動作5的Q值已經(jīng)被學成100左右折損后依然很大。這導致agent在非目標狀態(tài)也能學到「跳到5就能拿高分」的迂回路線繞遠路變成可接受行為。解決在q_target計算處對done做分支如果doneTrueq_target current_reward不再累加未來獎勵否則才計算current_reward gamma * np.max(q_next)。同時把batch_done正確轉(zhuǎn)成與batch_reward相同的維度格式否則batch_done[i][0]的取值方式會報錯。5.4 獎勵矩陣里用了100.0和1這樣差距懸殊的數(shù)值路徑穩(wěn)定但不夠短現(xiàn)象訓練后agent能找到一條到達目標5的路但路徑長度偏長不是最短路。原因獎勵矩陣中r[1][5]100.0和r[3][5]1差距太大。Q-Learning的收斂目標是最優(yōu)路徑但當正獎勵數(shù)值懸殊時agent會優(yōu)先選擇數(shù)值更大的獎勵路徑而不是步數(shù)更少的路徑。比如從狀態(tài)2出發(fā)可能傾向于走2→3→1→5拿100而不是2→3→5拿1再折損也可能大于1但數(shù)值上100更有吸引力。解決把正獎勵統(tǒng)一改成相同數(shù)值比如都設(shè)1讓agent通過gamma的折損自己去比較路徑長短?;蛘甙裷eward從稀疏大數(shù)值改成稠密小數(shù)值比如每一步給一個微小懲罰讓agent自然傾向走短路徑。我的經(jīng)驗是迷宮類問題盡量用稠密獎勵稀疏大獎勵容易讓agent學出「貪大」而非「求近」的策略。5.5 訓練樣本是時間序列記憶池沒攢夠就開訓loss震蕩劇烈現(xiàn)象把OBSERVE改成100甚至0訓練剛開始loss就在幾千到幾萬之間來回跳完全無法下降。原因經(jīng)驗回放的意義在于打破樣本相關(guān)性。如果記憶池里只有幾十條樣本random.sample抽出來的20條很可能仍然高度相關(guān)——它們都來自同一條探索軌跡。網(wǎng)絡(luò)對這20條樣本做梯度下降時會被同一方向的偏置帶跑下一步又抽到另一段軌跡損失又往反方向調(diào)整形成震蕩。解決OBSERVE1000不是玄學它保證記憶池里至少有1000條來自不同探索階段的樣本抽樣才能覆蓋足夠多樣的狀態(tài)轉(zhuǎn)移。如果環(huán)境狀態(tài)空間更大OBSERVE還要繼續(xù)加大。一個通用判斷標準是OBSERVE至少是BATCH的10倍且記憶池里應(yīng)該能看到每個狀態(tài)至少出現(xiàn)幾十次。避坑部分的這些現(xiàn)象源頭幾乎都指向同一個設(shè)計問題——訓練和推理共用邏輯、獎勵設(shè)計不夠精細、q_target計算沒有區(qū)分終止態(tài)。如果你改完代碼還是收斂慢優(yōu)先檢查這三處。6. 驗證與進階把打印路徑變成判斷收斂的真正標準pay()方法在訓練結(jié)束后會打印從每個狀態(tài)出發(fā)的移動路徑這是最直觀的驗證手段def pay(self): self.train() print(self.r) for index in range(5): start_room index current_state start_room step 0 target_state 5 while current_state ! target_state: out_result self.session.run( self.q_eval, feed_dict{self.q_eval_input: self.state_list[current_state:current_state 1]}) next_state np.argmax(out_result[0]) current_state next_state step 1 print(Agent 從, start_room, 出發(fā)走了, step, 步到達房間5)跑通這份代碼之后如果只驗證「能打印路徑」就結(jié)束其實漏掉了兩個更重要的檢查。第一把訓練參數(shù)learning_rate調(diào)大到0.01觀察loss曲線是否震蕩這能幫你直觀理解學習率對DQN穩(wěn)定性的影響第二把INITIAL_EPSILON從0.1改成0.9你會看到前期探索變多、收斂變慢但路徑多樣性更好這能幫你理解探索與利用的權(quán)衡。更進階的做法是畫loss曲線。cost_his列表在每次experience_replay()后追加當前l(fā)oss訓練結(jié)束后用matplotlib畫出來你會發(fā)現(xiàn)一個典型特征——loss不是單調(diào)下降而是先快速下降、再緩慢波動。這很正常因為q_target本身在變。如果loss完全不平滑、一直劇烈震蕩且幅度不縮小那大概率是學習率太高或BATCH太小。我的習慣是把pay()里的打印邏輯抽出來寫成獨立函數(shù)傳入任意起點狀態(tài)和最多步數(shù)這樣方便批量驗證也方便以后把迷宮換成更大的地圖時調(diào)試——只需要改r矩陣和state_num、action_num其他代碼可以復用。從那以后我每次跑強化學習Demo都會強制走一遍「先驗證網(wǎng)絡(luò)結(jié)構(gòu)→再調(diào)epsilon衰減→確認記憶池存量→最后看路徑合理性」這個流程避免基于一條偶然跑通的路徑就相信模型真的學會了。希望幫到你。本文還有配套的精品資源點擊獲取