简介一份系统讲解深度强化学习DQNDeep Q-Network原理与实战应用的PDF文档面向从传统Q-Learning跨向深度Q网络学习的算法学习者。内容以经典迷宫问题为案例从强化学习的基本状态state与动作action概念讲起说明Q-Learning维护Q表在状态与动作增多时的存储瓶颈进而引出用神经网络拟合Q函数的核心思路。文档详细拆解了经验回放Experience Replay的设计动机、训练样本抽取方式、损失函数target_q与q的均方误差以及epsilon-greedy探索策略并提供了基于TensorFlow实现的完整示例代码包括6×6迷宫的状态矩阵、动作矩阵、记忆库存储与网络更新流程。资源为单文件PDF整体大小约205KB适合随时翻阅已有1774人下载学习可作为DQN入门讲解、课程参考或实现智能体避障小项目的底层参考。1. 深度强化学习DQN用神经网络替换Q表之前先想清楚这五件事深度强化学习DQNDeep Q-Network解决迷宫问题核心思路是用神经网络替代Q-Learning里的Q表。Q-Learning在状态少时很好用但状态一多、动作一多Q表的内存占用和计算量就会爆炸深度强化学习DQN的提出就是为了把这张表换成网络。我拆这份代码时最直观的感受是它把DQN最关键的三件事都讲透了——经验回放怎么存样本、epsilon-greedy怎么平衡探索与利用、loss怎么从目标Q值和当前Q值的差值中构造。适合刚学完Q-Learning、想知道神经网络怎么接进来的读者也适合做路径规划想快速跑通一个最小示例的从业者。资源里自带源码和PDF讲解如果手边恰好有Python和TensorFlow环境半小时内就能看到一个小agent在6个房间之间学会找目标。整个过程不算复杂但里面有几个参数和设计细节不拆开讲清楚照着敲完你也不知道它为什么能跑通。2. DQN的网络搭建与loss设计三层网络怎么替掉6×6的Q表2.1 状态与动作的编码方式one-hot是DQN里最容易被忽略的细节状态数和动作数在代码里直接定义为6这6对应的是迷宫里6个房间。状态矩阵用np.identity(6)生成意思是每个状态用一个6维的one-hot向量表示状态0是[1,0,0,0,0,0]状态1是[0,1,0,0,0,0]。动作矩阵也一样每个动作也是一个one-hot向量。这个设计不是随便来的——DQN的神经网络输入层必须接收固定维度的向量one-hot编码把离散的状态映射成了网络能消化的数值形式。self.state_list np.identity(self.state_num) self.action_list np.identity(self.action_num)state_num和action_num都是6所以这两个矩阵都是6×6。代码里后面取状态、取动作时都是切片操作比如self.state_list[state_index:state_index 1]拿到的是一个1×6的行向量正好作为网络输入。这里要注意等训练结束测试时也要用同样的one-hot方式喂给网络否则网络没见过这种输入格式输出值会完全没有意义。我自己第一次跑通后想直接喂一个整数索引进去测试结果Q值全乱了就是这个原因。2.2 网络结构各层的shape与参数为什么要选3个神经元的隐藏层网络结构是最简单的三层全连接输入层6个节点状态维度隐藏层3个节点输出层6个节点动作维度。隐藏层之所以选3并没有经过严格调参属于这个规模问题下的经验值——6维输入映射到3维隐藏特征再映射回6维输出参数总量小训练快不容易过拟合。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三个占位符的含义要分清q_eval_input是当前状态action_input是当前执行的动作one-hotq_target是目标Q值——它不在网络前向计算里而是在训练时由外部算好喂进来的。tf.matmul(q_eval_input, w1)是输入层到隐藏层的线性变换经过relu激活后进入第二层线性变换最终得到6个动作各自的Q值估计。偏置b1设为0.1是为了防止relu把负输入全部杀死导致神经元死亡这在网络层数浅时影响不大但养成习惯没坏处。学习率0.001配梯度下降参数更新步长小loss曲线会平稳一些——如果换成AdamOptimizer收敛会快不少但初始阶段容易震荡我在后面踩坑部分会细说。2.3 loss构造的完整拆解目标Q值与当前Q值的逼近逻辑以及reward_action的数学含义loss这部分是DQN和监督学习最像的地方。DQN把目标Q值看成监督学习里的“标签”把当前网络输出的Q值看成“预测值”loss就是两者差值的平方。这里有个细节很关键网络的输出是6个动作的Q值但当前只执行了一个动作所以在计算loss之前要先从6个Q值里把当前动作对应的那个分量抽出来。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)))tf.multiply是元素级相乘q_eval是6个Q值action_input是one-hot的当前动作向量两者相乘后只有当前动作对应的分量被保留下来其余全是0。reduce_sum按行求和得到的就是“当前状态下执行当前动作的Q值”。举个例子假设网络输出[0.81, 0.5, 0.24, 0.513, 0.9, 0.71]当前动作是动作3索引从0开始one-hot向量是[0,0,0,1,0,0]相乘后得到[0,0,0,0.513,0,0]求和得到0.513。如果目标Q值算出来是1.03loss就是(1.03 - 0.513)² 0.267。减法的方向很明确目标Q值是用当前奖励加上折扣后的未来最优Q值算出来的比网络当前的估计值更接近真实值所以训练就是让reward_action一点点向q_target靠拢。2.4 Q表失效的边界条件招架不住的状态爆炸问题Q-Learning在状态数少时完全够用但一旦状态变成连续量比如坐标、速度、角度或者离散状态数量达到百万级Q表就彻底失效了。DQN用神经网络替换Q表的本质是把“存储所有状态动作对的查表问题”转换成了“学习一个从状态到动作价值向量的函数逼近问题”。网络只需要保存权重参数就能泛化到没见过的状态——当然泛化质量取决于网络容量和训练数据质量。这份代码里状态数只有6神经网络的优势体现得不明显但你可以把state_num改成60甚至600改动网络的输入维度和隐藏层大小后仍然能训练而Q表在600个状态下就得有600×动作数条目存储和更新成本已经完全不是一个量级。这也是为什么把这份代码跑通之后理解DQN的参数泛化能力要比记住代码本身更重要。3. epsilon-greedy动作选择探索与利用的平衡直接决定能不能收敛3.1 epsilon初始值0.1的代价为什么说探索步数决定了训练效果的上限epsilon-greedy策略是DQN里控制“探索”与“利用”的天平。epsilon表示随机探索的概率初始值设为0.1意味着有10%的概率随机选动作90%的概率选当前网络认为的最优动作。这个比例在训练初期会影响收敛速度——10%的探索率在6个动作的规模下其实是够的因为状态只有6个随机试几次就能覆盖大部分转移关系。但如果状态空间变大0.1的初始值会显得探索不足agent很容易困在局部最优路径里。代码里还设置了FINAL_EPSILON 0.0001意思是训练后期几乎不再随机探索完全靠网络决策。这个衰减过程不是按步数线性递推的而是在select_action里每次调用时判断是否衰减。if self.step_index self.OBSERVE and self.epsilon self.FINAL_EPSILON: self.epsilon - (self.INITIAL_EPSILON - self.FINAL_EPSILON) / self.EXPLORE衰减公式的含义在EXPLORE 3000000步内把epsilon从0.1线性降到0.0001。每一步减少(0.1 - 0.0001) / 3000000 ≈ 3.33e-8是个非常缓慢的过程。注意衰减必须发生在step_index OBSERVE之后也就是前1000步只采集经验不训练epsilon保持不变。这个设计很像生产线上的预热环节——先让机器空转一段时间再正式投料。3.2 动作选择的双通道逻辑随机探索与网络决策在代码里的分岔口动作选择函数的核心逻辑就是一行判断随机数小于epsilon就随机选否则在网络输出里取最大值。代码里np.random.uniform()的取值范围是[0,1)所以epsilon0.1时正好是10%的概率走随机分支。随机分支是让agent在训练初期多踩“错路”的关键——因为迷宫里的负奖励-1会把agent从不可走路径上推回去而正奖励100会吸引agent靠近目标必须让agent有机会走到那些“看似绕路”的位置才能发现更优路径。网络分支则负责把当前状态喂进网络取6个Q值中最大的那个动作作为输出。def 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有个细节值得停下来看一下epsilon衰减放在动作选择函数里意味着衰减频率和决策频率一致决策一次就衰减一次而不是每训练一步衰减一次。在step_index小于1000时因为不衰减探索率保持0.1有利于前期的经验积累。我跑代码时观察过epsilon的实时值在训练到几万步后它依然在0.09左右说明这个衰减确实很慢。如果你希望探索更激进可以把初始值调到0.3或0.5但要注意训练前期的随机动作比例增高会导致路径更长、记忆库里的负奖励样本更多网络需要更长时间才能学到稳定策略。3.3 探索率衰减的时机检查为什么衰减太快会让agent变成“路痴”衰减速度本身是个需要反复试的参数。如果EXPLORE设得太小比如3万步epsilon会快速跌到0.0001agent会在没充分探索的情况下过早进入纯利用模式。一旦随机探索停止agent对某些状态的Q值估计可能一直是错的但网络又没机会再通过随机动作去修正——这就相当于一个人只走过一条路就认定这是唯一的路再也不看地图。我在调参时把EXPLORE改成过30万步收敛速度明显变慢但最终策略更稳改回300万步后前几万步基本看不到loss下降的趋势这是正常的因为在探索阶段网络就是在一堆互相矛盾的经验里来回震荡。有个判断收敛是否到位的土办法打印每个起点出发后的最终路径。如果agent总是走同一条路且路径最短说明网络已经收敛如果路径常变或绕远路说明探索还不充分或者epsilon衰减太早。3.4 状态转移的确定性设计R矩阵的负奖励与终止条件解读R矩阵是这份代码里“迷宫”的真正定义。6×6矩阵r[state][action]表示在state状态下执行action动作能拿到的即时奖励。从矩阵内容看不是所有状态都能动作自由移动——奖励为-1的位置代表不可走或者说不推荐走奖励为0的位置代表可走但无收益奖励为100的位置代表到达目标。值得注意的是可走的位置也不是对称的比如状态0执行动作4的奖励是0但状态4执行动作0的奖励也是0这说明状态转移是双向可逆的而状态1执行动作5的奖励是100动作5对应的就是到达目标。def step(self, state, action): reward self.r[state][action] next_state action done False if action 5: done True return next_state, reward, donestep函数里有个隐含假设执行动作a后agent一定转移到状态a。也就是说这个“迷宫”的拓扑结构是——从当前房间直接跳到目标房间只要动作编号等于目标房间编号就成功。如果你把动作理解为“移动方向”那这个设计只适用于房间之间一跳可达的图结构。换到更复杂的迷宫比如需要多步转弯的网格迷宫next_state的计算需要换成基于地图的转移逻辑R矩阵也要重新定义。理解这个边界扫一眼就能判断这份代码能不能直接用到自己的场景里。我在复现时最快踩到的坑就是这里改地图时只改了R忘了改step结果agent学会的策略和地图完全对不上。4. 经验回放与训练循环样本怎么存、怎么抽、怎么更新Q值4.1 为什么不 Q表还要经验回放样本连续性会拖垮梯度下降DQN最核心的机制之一就是经验回放Experience Replay。强化学习中agent连续探索环境得到的样本是一串时间序列相邻样本间高度相关状态s(t)和s(t1)只差一个动作。如果每得到一个样本就立即用它更新网络参数梯度方向会被这批相关性极强的样本带偏导致loss剧烈震荡、网络难以收敛。经验回放的做法是先把样本存进一个固定大小的存储区训练时再从里面随机抽一小批。随机抽样打乱了时间相关性相当于把一串互相依赖的事件变成了“独立同分布”的训练数据梯度更新才稳定。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()这段代码里的popleft()保证存储区只保留最近5000条经验超出后最老的记忆被丢弃。“为什么不用全部经验训练”也是个常见疑问——全部经验里包含大量早期探索期的随机动作记录这些记录对收敛后期的策略修正几乎没有帮助反而会拖慢训练速度。用deque的另一个好处是popleft是O(1)操作比list的pop(0)快得多。样本容量5000对这个6状态问题来说是相当充裕的跑一遍训练可能根本填不满这个池子——因为终止状态会频繁触发重置实际生成的样本量有限。但在更大规模的问题上memory_size通常要设到几万甚至几十万否则经验多样性不够网络学到的策略会很“偏科”。4.2 经验池的存储格式与内存上限deque、popleft和memory_size的配合经验池里的每条记忆是一个五元组当前状态、当前动作、即时奖励、下一个状态、是否结束。状态和动作在存储时就是one-hot向量奖励是一个浮点数。设计上有个小门道——最后一个done字段在代码里存了但没用上注释里还留着“游戏是否结束”的判断代码。这说明原作者的意图是区分终止状态和非终止状态的目标Q值计算方式但最终实现里没走到那一步。实际计算Q值时只用了一个if current_reward 0的判断奖励为负时直接把目标Q值设为当前奖励奖励非负时才加上后续的折扣Q值。这种简化在这个迷宫场景里是可接受的但放到一般DQN场景比如Atari游戏就明显不够了——终止状态的Q值不应该加上未来的折扣奖励因为游戏已经结束了。这就牵扯到一个问题原代码在这里的处理是否值得沿用我的建议是如果复用到其他场景把done的语义完整用起来按终止状态和非终止状态分别计算目标Q值。4.3 小批量抽样的实现细节边界条件下batch_size动态收缩的逻辑experience_replay函数里有一个容易看漏但很重要的细节当记忆池里的样本总数不足BATCH20时实际抽样的数量不是硬凑20条而是用当前记忆数做批大小。代码里是batch self.BATCH if self.memory_counter self.BATCH else self.memory_counter接着用random.sample从deque中抽样。这么做的原因很直接random.sample不能从不足20条的记忆池里抽出20条不重复的样本不处理这个边界条件程序会直接抛异常。这个边界条件出现的时机是训练前半段——OBSERVE是1000步训练从第1001步才开始经验池里此时已有1000多条记忆20条样本是够的但如果把OBSERVE调小或者把BATCH调大边界情况就会显现出来。batch self.BATCH if self.memory_counter self.BATCH else self.memory_counter minibatch random.sample(self.replay_memory_store, batch)抽样完成后代码把minibatch里的状态、动作、奖励、下一状态分别拼成矩阵。拼接逻辑是逐条用np.vstack堆叠第一次遇到的样本作为初始矩阵后续的样本垂直拼接。这个写法功能上没错但有个性能隐患——在不断vstack的过程中数组会反复复制数据量大时效率堪忧。我的习惯是先把minibatch按字段拆成list用np.array一次性转换效率和可读性都更好。4.4 目标Q值的计算方式奖励为负直接赋值奖励非负才做折扣累加目标Q值计算是整个训练过程数据流的核心交汇点。计算逻辑分两段先读出当前奖励再用贝尔曼方程计算当前奖励 gamma × max(Q(下一个状态的所有动作))。代码里对负奖励做了单独处理——如果当前奖励是-1目标Q值直接等于-1否则才使用折扣累加公式。这个处理的本意是把不可走路径奖励为-1直接标记为低价值让网络学到“这些动作不要选”。但这么做有个副作用目标Q值里-1是恒定值没有考虑下一个状态的情况相当于把“执行这个动作后进入的下一状态”的信息完全丢掉了。比如从状态0跳到状态3奖励为-1即使状态3本身距离目标很近网络也只学到“动作3在状态0处是危险的”学不到“跳到3其实离目标近了”。这在简单的6房间迷宫问题里影响不大但如果在网格迷宫里用这个简化逻辑agent会学到一些奇怪的绕路策略。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)这里的折扣因子gamma0.9意思是未来一步的Q值只按90%折算到当前。0.9在短路径问题里算合理——如果最长路径不超过3步0.9³≈0.729远期收益的打折还不算严重但路径一旦变长折扣因子太小会导致远期目标几乎不被重视agent会变得“短视”。在迷宫规模更大、路径更长时我一般会把gamma调到0.95到0.99之间否则目标房间的100分奖励传不到起点位置。4.5 训练主循环的流程拆解观察期、学习期和终止重置的协作方式训练主循环是DQN完整生命周期的主轴。初始状态随机从0到4号房间选一个注意不是5因为5是目标epsilon设为0.1然后开始无限循环。每轮循环里做四件事选动作、执行动作拿奖励、存入记忆池、判断是否进入训练阶段。观察期和学习期的分界线就是OBSERVE1000——前1000步只存经验不训练第1001步开始抽样训练。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终止重置的细节藏在最后三行done为真时当前状态重新随机初始化done为假时agent进入动作指向的下一个状态。总步数上限10000这个上限直接决定了训练时长和最终效果——跑完10000步epsilon大约在0.1左右远没衰减到FINAL_EPSILON也就是说训练结束时机和探索衰减节奏是脱节的。原作者应该是在这个规模下发现10000步已经足够学到好策略就把上限定在这了。如果你增大状态空间或迷宫复杂度这个10000的硬上限需要同步调大否则训练会提前终止在策略还没收敛的时候。我在复现时把步数上限提到50000发现loss能降到更低、路径更稳定但训练时间也线性拉长了。怎么平衡就得看你的实际场景了。5. DQN避坑记录房间导航场景下五个必踩的坑5.1 现象loss不降反升甚至直接发散成nanloss在训练初期不但不降反而一路飙升到几万甚至变成nan。原因通常是学习率过高导致梯度爆炸或者网络初始权重过大。代码里w1用的是tf.random_normal标准差为1的随机值。在6维输入映射到3维隐藏层时有18个权重它们的乘积和可能很大叠加relu激活后输出Q值可能到达几十甚至上百。这时配上0.001的学习率还好但如果把学习率调到0.01以上梯度一步跨出太远loss直接起飞。解决方法是把权重初始化改成标准差更小的分布比如tf.random_normal(..., stddev0.1)或者用tf.truncated_normal——这样初始化的网络输出会集中在0附近loss起点更低。我自己遇到过loss在几千步内从1左右涨到5000多的情况排查后发现是权重初始化范围太大把stddev改成0.1后训练恢复正常。5.2 现象epsilon衰减过快agent永远在绕远路训练完成后从起点出发的路径长度明显大于最优路径且路径不稳定每次测试走的路线都不一样。原因是无探索能力后网络对那些从未被随机探索过的状态动作对完全没有正确估计纯利用模式只会重复输出早期学到的次优策略。解决方法是把EXPLORE从3000000改大到更大的值或者在训练中期人为把epsilon重新拉高一段时间给agent一次“再探索”的机会。我调试时把EXPLORE改成6000000后路径长度明显缩短但训练时间也翻倍了。一个折中的做法是分阶段训练先用较高的epsilon跑一段时间等loss曲线开始下降后再切换到较低的epsilon继续微调——这本质上就是手动控制探索节奏。5.3 现象reward为负的样本导致策略卡死在局部最优agent学会了从起点走到一个“中间站”就停住不再往目标走或者总是在同一个位置来回跳。原因有二一是负奖励的目标Q值直接赋值current_reward即-1没有考虑下一状态的潜在价值导致网络高估了某些“看起来危险但实际通向目标”的路径的代价二是经验池里的负奖励样本没有按权重筛选大量随机探索期的“碰壁”样本淹没了少量到达目标的正样本。解决方法是先把reward的赋值逻辑改得更接近标准DQN负奖励也计算current_reward gamma * max(q_next)而不是截断赋值再把经验池改成带优先级的采样比如正奖励样本的抽样权重更高。本地验证的代码改动只需要把if current_reward 0这个分支删掉统一走折扣累加逻辑就能看到策略跳出了局部最优。5.4 现象训练到后期loss小幅回升然后永久停滞训练曲线先下降后回升之后无论怎么调学习率都回不到最低点。原因是经验池容量5000但训练步数超过观察期后一直在累积新经验老经验被popleft挤掉——后期池子里全是探索期的“陈旧样本”抽样训练时新旧样本比例失衡网络在刚学好的策略和旧经验之间反复横跳。解决方法是扩大memory_size比如改成50000让更多经验被保留或者把训练步数上限和memory_size联动调整保证整个训练周期里经验池的更新是平滑的。我还试过一个做法在loss进入平台期后把学习率从0.001降到0.0001继续训练几百步loss经常能再降一小截——这个操作在深度学习里叫“学习率退火”在DQN里同样有效。5.5 现象测试时从不同起点出发agent的步数差异很大训练完成后分别从0、1、2、3、4号房间测试有的起点一步直达目标有的起点绕很远的弯路。原因和5.2里说的探索不充分是同一个根源但这里更可能是状态空间的访问频率不均衡——在随机探索阶段某些状态被访问的次数远多于其他状态网络拟合的天平倒向了高频状态。比如状态0和状态1被随机初始化命中的概率高Q值估计准状态4很少被选为初始状态网络对它的估计偏差就大。解决方法是初始化时均匀覆盖所有非目标状态或在训练时对低频访问的状态做额外采样把它们的转移样本多存一些进经验池。跑测试时我习惯用脚本把5个起点的路径全部打出来一眼就能看出哪个状态的策略还不可信。6. 用测试循环验证训练结果五条路径与Q值可信度检查训练完成不等于策略可信这一步是把训练结果“验明正身”的关键环节。pay()函数里有一段测试逻辑分别从0到4号房间出发循环执行网络决策直到到达5号目标房间同时打印每一步的移动轨迹。这个循环的机制是把当前状态输入网络取Q值最大的动作作为下一步然后更新当前状态再重复这个过程。因为网络输出是6个动作的Q值所以next_state np.argmax(out_result[0])取的就是最大值对应的动作索引。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, 号房间出发用了, step, 步到达目标房间 5)测试结果我是这么解读的起点0到目标5的最优路径是0→4→5或0→3→52步起点1到目标5的最优路径是1→51步起点3到目标5的最优路径是3→51步。如果测试输出中某个起点的步数明显多于最优值说明这个状态附近的Q值估计还不够准需要回到训练阶段调整探索策略。Q值可信度检查的另一个方法是把某一状态下6个动作的Q值全部打印出来观察数值排序是否合理。比如在起点0时走向3和走向4的动作Q值应该明显高于走向1或走向2的Q值走向不可走位置的Q值应该非常低接近-1。如果打印出来的Q值排序和R矩阵的奖励结构对不上说明网络还没完全收敛需要继续训练。这个检查方法在多步路径迷宫里更有价值——它能直观展示网络内部对不同动作的“偏好强度”而不仅仅看最终路径结果。我习惯把测试输出的路径长度和loss曲线的最终值一起记录每次调参后都跑一遍对比积累几组数据之后就能看出哪些参数对路径结果有决定性的影响。从供给侧看memory_size、EXPLORE和learning_rate这三个参数出问题的概率最高——它们分别控制经验广度、探索时长和更新步幅任何一个失衡都会反映在测试路径上。从那以后我每次跑完训练都强制走一遍“五路径Q值打印”的验证流程确认无异常才认为这次训练真正合格。希望这份拆解能帮你少踩几个我已经替你踩过的坑。本文还有配套的精品资源点击获取