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