
简介面向深度学习与人工智能方向的PDF资料系统讲解深度强化学习DQNDeepQNetwork的核心原理并结合经典迷宫问题演示如何用神经网络替代Q-Learning中的Q表解决状态与动作空间过大带来的存储和计算难题。内容涵盖Q-Learning基础、DQN训练流程、Experience Replay经验回放机制、损失函数推导、epsilon-greedy策略以及基于TensorFlow的完整代码实现对想入门强化学习或理解DQN落地细节的开发者具有较好的参考价值。资源为单个PDF文档压缩包大小205KB通过迷宫例子贯穿原理与代码讲解目前已有1774人学习下载适合机器学习初学者与人工智能方向学生阅读。1. 深度强化学习 DQN 是什么为什么迷宫问题是最适合的入门落地点第一次接触深度强化学习 DQNDeep Q-Network的人很多不是倒在公式上而是倒在“不知道这部分在解决什么问题”上。DQN 本质上是用一个神经网络去拟合 Q 值函数替代传统 Q-Learning 里那张 Q 表凡是状态空间大到表格装不下、或者状态输入是图像和连续量的场景它就成了更合理的选择。迷宫问题恰恰把这件事压缩到了最容易观察的尺度状态是离散格子动作只有四个方向奖励清清楚楚模型有没有学会一眼就能看出来。换句话说DQN 解决迷宫问题不是为了炫技而是为了让你在源码层面把“经验回放怎么存、目标网络怎么更新、epsilon 怎么退火”这三个核心机制彻底看懂。这篇笔记适合刚入门深度强化学习算法、跟着源码跑通第一个路径规划任务的开发者也适合手头有《深度学习原理.pdf》但缺一份落地代码的读者。2. 先理解 DQN 的原理从 Q-Learning 到 Deep Q-Network 的演进强化学习的最终目的是找到一个策略让智能体在环境里获得的累计奖励最大。Q-Learning 的思路是记住“在状态 s 下执行动作 a未来能拿到多少折扣累计奖励”这个值记作 Q(s,a)。每走一步就按公式更新一次Q(s,a) ← Q(s,a) α × (r γ × max a′ Q(s′,a′) − Q(s,a))其中 α 是学习率γ 是折扣因子r 是当前奖励s′ 是下一个状态。这个更新公式把所有线索都写在一个式子里当前预测和“真实回报 下一步最优价值”之间的差距叫作 TD 误差时间差分误差。问题在于Q 表只能覆盖遍历过的状态状态一多内存、时间、泛化能力三头都堵死。我倾向于把 DQN 理解为“用网络换表格”的升级输入状态输出每个动作的 Q 值。它保留了 Q-Learning 的更新逻辑但把更新对象从表格里的一格变成一个可微的神经网络。于是原本对每个格子单独更新一次的方案变成了“用一批样本更新网络权重让整个状态空间都能被学到”。这个换法让 DQN 能处理图像输入、连续状态、以及像迷宫这类离散但尺寸可变的任务。后面这两节会拆开讲清楚为什么一个看似不大的迷宫也能暴露出 Q 表的短板以及 DQN 能稳定训练靠的四个结构设计。2.1 为什么 Q-Table 在迷宫这种小状态空间里也能露馅很多刚接触迷宫问题的同学会先质疑一个 10×10 的迷宫只有 100 个格子每个格子 4 种动作Q 表最多 400 项为什么不用 Q-Learning这个质疑是对的小规模迷宫确实不需要上深度强化学习。但真实的价值在于当迷宫尺寸变大、起点和目标随机化之后Q 表会立刻失效。比如一个 20×20 的迷宫墙和通道是有结构的Q 表只能逐个格子记录路径墙的变化、入口的变化都要求重新探索和重新存储。换成神经网络后相邻格子因为输入特征相似Q 值会被网络自然地“平滑”出来即使地图换了一张同风格的迷宫网络早期学到的特征也能迁移一部分。这个差异在代码里体现为Q-Learning 需要一个二维数组遍历全部状态DQN 只需要一个前向推理函数。另一个隐性问题在探索效率上。Q-Learning 每走到一个新格子只更新当前格子的 Q 值如果终点在迷宫另一边早期完全碰不到奖励信号Q 表就会一直处于稀疏状态。DQN 即使同样拿不到奖励也会因为网络是全连接的把“靠近墙还是远离墙”这类特征从大量 visited 状态里提出来。至少在路径规划这类任务上神经网络在探索阶段的泛化优势是 Q 表无法覆盖的。下面用 NumPy 跑一个 4×4 小迷宫的单步 Q-Learning 更新看 TD 误差是怎么算的这样后面理解 DQN loss 会顺很多。import numpy as np # 4x4 迷宫0 可走1 是墙最后一个是终点 maze np.array([[0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 1, 0, 0]]) Q np.zeros((4 * 4, 4)) # 状态数 * 动作数 alpha 0.1 gamma 0.99 # 假设当前在状态 0执行动作 3左移到状态 1奖励为 0 s, a, r, s_next 0, 3, 0.0, 1 td_target r gamma * np.max(Q[s_next]) td_error td_target - Q[s, a] Q[s, a] alpha * td_error print(TD error:, td_error) print(Updated Q[0]:, Q[0])这段代码里的gamma * np.max(Q[s_next])就是“下一步最优价值的估计”。如果终点奖励没被探索到Q[s_next]全是 0TD 误差只来自当前的小幅更新学习会非常慢。这就是为什么迷宫问题里的奖励函数设计比 Q-Learning 时代更重要。2.2 DQN 的四个核心部件Q 网络、目标网络、经验回放、奖励函数DQN 的训练流程可以压缩成四个部件。第一个是 Q 网络它的输入是状态特征向量输出是每个动作的 Q 值。迷宫问题里状态就是格子坐标一般用 One-Hot 或归一化后的坐标值。第二个是目标网络。如果只有一个 Q 网络计算 TD 误差时max Q(s_next)也来自同一个网络相当于自己给自己打分每次更新后评分标准就变训练容易震荡甚至发散。目标网络的参数每隔若干步才从 Q 网络复制一次让 TD 目标在一段时间内保持稳定。这个“延迟更新”是 DQN 能够在 2015 年训练出稳定效果的关键改进。第三个是经验回放缓冲区。强化学习采样天然连续相关刚走完格子 1下一步多半是格子 2如果直接用相邻样本来更新网络梯度方向会反复横跳。经验回放把每一条(s, a, r, s_next, done)存进一个队列训练时随机抽一批相当于打断时间关联。迷宫环境的状态数量少常被误以为不重要实际上 DQN 在整个训练阶段能看到的数据都是靠这个缓冲区生成的容量不够或采样方式不对网络会反复忘记刚学到的东西。第四个是奖励函数。迷宫任务的“本质目标”是到达终点但奖励函数决定了网络从哪里获得梯度。如果只有到达终点给 1、其余全是 0那么早期全部样本的 TD 目标都是 0网络没有任何梯度方向的引导。需要在“到达终点”的大奖励之外给每一步一个小的负惩罚或者用势能差来给接近终点一个渐进信号。这一点我会在第三章具体展开。我一般会建议新手在跑通代码前先用这个框架去读源码找到 Q 网络的定义、目标网络更新函数、回放缓冲区数据结构、奖励计算位置。四个位置能对上DQN 就算入门了。3. 把迷宫问题转成强化学习环境状态、动作、奖励与终止条件DQN 不是写一个模型然后喂数据就能跑它需要一个完整的交互环境。环境定义方式直接决定训练的难度和代码的调试成本。迷宫问题虽然简单但至少有六个细节要提前定好状态如何编码、动作空间是几维、墙壁碰撞怎么处理、到达终点后怎么终止、奖励按什么规则给、是不是要把随机起点和随机终点也加进来。3.1 迷宫的离散状态与动作空间设计迷宫最直观的状态是(row, col)坐标。如果迷宫尺寸不大直接把坐标归一化后拼成长度 2 的向量网络也能学会但更稳定的做法是用idx row * width col转成一个整数再 One-Hot 成一个长度为width * height的稀疏向量。One-Hot 的缺点是维度会随迷宫尺寸变大但好处是给网络提供了明确的“位置”语义不会出现坐标归一化后在两个不同格子输入完全相同的情况。动作空间通常定义为四个方向0 上、1 右、2 下、3 左。这里有一个容易踩的细节如果智能体执行动作后撞到墙或迷宫边界一般有两种处理方式。第一种是原地不动状态不变给一个小负奖励第二种是直接给终止信号。对 DQN 来说我更推荐前一种因为“撞墙”本身是有信息量的负反馈能让网络学到边界的存在。直接终止则会减少一次有效探索而且会让智能体倾向于待着不动。终止条件只有两个“走到终点”和“超出最大步数”。最大步数建议设为迷宫格子数的 3 到 5 倍比如 10×10 的迷宫设 300 步。太小会切掉正常路径太大会让智能体在无限绕圈中浪费时间。下面这个MazeEnv类可以直接用来跑 DQN逻辑上跟 OpenAI Gym 风格一致。3.2 用 Python 实现一个最简迷宫环境类import numpy as np class MazeEnv: def __init__(self, maze, start(0, 0), goal(4, 4), max_steps200): self.maze np.array(maze) self.start np.array(start) self.goal np.array(goal) self.max_steps max_steps self.action_space 4 # 顺序上、右、下、左 self.dirs [(-1, 0), (0, 1), (1, 0), (0, -1)] self.state None self.steps 0 self.reset() def reset(self): self.state self.start.copy() self.steps 0 return self._get_obs() def step(self, action): self.steps 1 row, col self.state dr, dc self.dirs[action] new_row, new_col row dr, col dc # 撞墙或越界原地不动给一个小的负奖励 if (new_row 0 or new_row self.maze.shape[0] or new_col 0 or new_col self.maze.shape[1] or self.maze[new_row][new_col] 1): reward -0.1 done False else: self.state np.array([new_row, new_col]) if np.array_equal(self.state, self.goal): reward 1.0 done True else: reward -0.01 done False # 超过最大步数也直接结束 if self.steps self.max_steps: done True return self._get_obs(), reward, done def _get_obs(self): row, col self.state # 返回 One-Hot 向量 obs np.zeros(self.maze.shape[0] * self.maze.shape[1]) obs[row * self.maze.shape[1] col] 1 return obs这段代码里的关键设计有两个。第一_get_obs返回的是长度为“格子总数”的 One-Hot 向量而不是坐标元组这样后面训练网络的输入层维度就和地图尺寸绑定换地图时只需要改maze数组不用重写网络。第二奖励按“撞墙 -0.1、普通步 -0.01、到达终点 1.0”设置这个数量级差能防止 Q 值被单次大奖励带飞同时又不至于让负奖励太大而掩盖终点信号。max_steps这个参数在迷宫环境里不是可有可无。没有步数上限智能体完全有可能在 oss 路径上反复横跳训练永远不会终止经验回放里会堆满无意义的重复样本。上限设成 200 步至少保证一条完整 episode 的时间可控。3.3 奖励函数怎么设稀疏奖励与势能引导的取舍迷宫任务的天然奖励只在终点出现这是典型的稀疏奖励问题。稀疏奖励最直接的后果是早期 epsilon 大智能体乱走能碰巧走到终点的概率极低经验回放里存下来的样本绝大多数收益为 0网络只能学到“原地待着”或“不要撞墙”学不到方向。最简单的修复方案是给每一步一个负惩罚。这个负惩罚的绝对值很敏感。如果设成 -0.01100 步的 episode 累计也只有 -1和终点 1 接近网络需要仔细权衡如果设成 -0.1智能体会倾向尽快结束游戏甚至撞墙专门制造终止。我一个比较常用的策略是普通步 -0.01撞墙 -0.1终点 1.0。撞墙的惩罚要重于普通步因为撞墙意味着行为错误而且它会打断移动节奏。进阶做法是势能引导reward shaping也就是根据“当前格子到终点的曼哈顿距离”来给奖励增量。每走一步如果距离终点更近了给一个小的正奖励离终点更远则给负奖励。公式可以写成potential_old distance(state, goal) potential_new distance(next_state, goal) shaping 0.1 × (potential_old − potential_new)这种基于势能的奖励能显著加快收敛但它有一个副作用如果 shaping 系数设得太大智能体会贪图近处的小奖励忽略真正到达终点的大目标甚至走出一条“距离越来越近但被墙挡住”的死路。我的经验是把 shaping 系数控制在 0.050.2 之间并且只在训练的前半段开启后半段逐渐关闭让智能体回归到稀疏奖励下的真实目标。4×4 迷宫可以不用势能引导但如果你的目标是 20×20 以上的地图强烈建议加 shaping。下面这段代码展示了怎么在原有step里嵌入势能奖励def _distance_to_goal(self, state): return abs(state[0] - self.goal[0]) abs(state[1] - self.goal[1]) # 在 step 中移动到新格子后 old_dist self._distance_to_goal(self.state) new_dist self._distance_to_goal(np.array([new_row, new_col])) shaping 0.1 * (old_dist - new_dist) reward shaping加 shaping 之后要注意网络 Q 值的目标不再是“纯终点奖励”而是“势能差值 终点奖励”的组合。最后评估策略时一定要去掉 shaping 再看成功率避免被引导信号蒙蔽。4. 用 PyTorch 实现 DQN 解决迷宫核心代码与参数说明环境就绪后下一步是搭 DQN 训练脚本。很多人喜欢把训练逻辑和模型定义混在一起迷宫问题本来就小混着写能跑但换任务就废。我的习惯是拆成四块网络结构、回放缓冲区、动作选择策略、训练循环。下面按这个顺序给出可直接抄的代码并标注每个参数的意义。4.1 网络结构选择几层全连接才够用迷宫状态是 One-Hot 向量动作只有 4 个网络不需要很深。我的默认结构是三层全连接输入层到 64、64 到 64、64 到 4。迷宫数量级在几百个格子以内这个规模足够。层数再加深、神经元再加宽对迷宫没有任何正向收益只会拖慢训练并增加过拟合风险。激活函数建议用 ReLU最后一层不加激活因为 Q 值需要输出任意实数。这里有个新手常犯的错误在输出层加 Sigmoid 或 Tanh把 Q 值限制在 [0,1] 或 [-1,1] 范围内导致奖励大一些时梯度始终饱和训练不收敛。迷宫奖励的绝对值通常小于 2不用特殊缩放但如果换到复杂任务Q 值范围会远大于 [-1,1]输出层激活会让模型彻底失效。import torch import torch.nn as nn class DQN(nn.Module): def __init__(self, state_size, action_size): super(DQN, self).__init__() self.net nn.Sequential( nn.Linear(state_size, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, action_size) ) def forward(self, x): return self.net(x)state_size就是迷宫格子总数action_size固定为 4。如果换到图像输入这个全连接网络需要替换成卷积层但在迷宫路径规划中One-Hot 输入用全连接就够了。4.2 训练主循环采样、回放、软更新/硬更新训练主循环是 DQN 最核心的骨架。先看整体流程再解释为什么每一步都不能省。代码里以下replay是一个简单的回放缓冲区先用列表存数据容量超过buffer_size后覆盖最老数据采样时随机抽batch_size条。import random from collections import deque class ReplayBuffer: def __init__(self, capacity20000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) states, actions, rewards, next_states, dones zip(*batch) return (torch.FloatTensor(np.array(states)), torch.LongTensor(actions).unsqueeze(1), torch.FloatTensor(rewards).unsqueeze(1), torch.FloatTensor(np.array(next_states)), torch.FloatTensor(dones).unsqueeze(1)) def __len__(self): return len(self.buffer)采样后要把数据类型对齐状态用 FloatTensor动作必须要 LongTensor因为后面要用gather按动作索引 Q 值奖励和 done 都改成列向量方便广播。done这个值尤其重要如果终点的下一状态也参与 Q 值计算网络会被错误地引导终点之后没有任何动作不能再往前看一步。训练主循环如下def train_dqn(env, q_net, target_net, replay, optimizer, episodes1000, batch_size32, gamma0.99, epsilon_start1.0, epsilon_end0.01, epsilon_decay5000, target_update500): epsilon epsilon_start steps 0 for episode in range(episodes): state env.reset() done False total_reward 0 while not done: # 根据 epsilon 贪心选动作 if random.random() epsilon: action random.randrange(env.action_space) else: with torch.no_grad(): q_values q_net(torch.FloatTensor(state).unsqueeze(0)) action q_values.argmax(dim1).item() next_state, reward, done env.step(action) replay.push(state, action, reward, next_state, done) if len(replay) batch_size: states, actions, rewards, next_states, dones replay.sample(batch_size) q_pred q_net(states).gather(1, actions) with torch.no_grad(): q_next target_net(next_states).max(dim1, keepdimTrue)[0] q_target rewards gamma * q_next * (1 - dones) loss nn.MSELoss()(q_pred, q_target) optimizer.zero_grad() loss.backward() optimizer.step() steps 1 epsilon max(epsilon_end, epsilon_start * (1 - steps / epsilon_decay)) if steps % target_update 0: target_net.load_state_dict(q_net.state_dict()) state next_state total_reward reward if episode % 50 0: print(fepisode {episode}, reward {total_reward:.2f}, epsilon {epsilon:.2f})这段代码里最难理解的可能是q_target rewards gamma * q_next * (1 - dones)。q_next是目标网络给出的下一状态最大 Q 值但如果下一状态是终止状态那么就不该再有未来回报所以要用(1 - dones)把它清零。没有这一步终点状态会被当成熟知世界里的普通状态Q 值会被错误抬升。主循环里还有两个关键机制没写全但必须知道一是经验回放只在len(replay)超过batch_size后才开始训练否则缓冲区里样本太相关梯度方向会抖动二是目标网络必须用load_state_dict硬更新更新频率我一般取 500 步。软更新EMA也可以用但迷宫场景下硬更新更直观代码也更好排查。4.3 三个必调参数学习率、epsilon 衰减、batch size学习率是 DQN 里最玄学的参数之一。迷宫任务我一般从1e-3起步如果 loss 反复震荡或训练曲线剧烈跳动降到3e-4。学习率太大Q 网络会忘记早期学到的稳定策略学习率太小500 步的目标网络更新根本来不及传播。推荐先用 TensorBoard 记录 loss如果 loss 在 100 帧内上下浮动超过 5 倍优先调低学习率。epsilon 衰减步长epsilon_decay决定探索和利用的节奏。迷宫起点固定、地图固定5000 步的衰减足够如果起点随机衰减步长要加倍否则前期没探索全地图后期 epsilon 已经很小就会锁死在一条次优路径上。epsilon_start1.0意味着开局完全随机epsilon_end0.01意味着后期几乎全用贪心策略。这个“几乎”很重要因为环境或网络结构一变保留 1% 的探索能帮助策略跳出局部最优。batch size 在迷宫任务里影响不大32是个稳妥值。调大64会让单步训练更稳定但会拖慢每步更新速度调小16则收敛更快但更容易受异常样本干扰。经验回放缓冲区容量20000对迷宫足够这个值的核心作用是避免旧样本被过度覆盖导致模型遗忘。如果你的迷宫更大、episode 更长可以提高到50000。5. 训练与评估从模型不学到能走出迷宫我踩过的四个坑这部分内容不是理论推演是我用迷宫任务调 DQN 时真实翻车后记下来的排查清单。每一条都按照“现象 → 原因 → 解决”的顺序写希望在你能直接对照自己的训练曲线。5.1 loss 在降智能体却一直撞墙目标网络更新频率设错了我遇到过最迷惑的场景是loss 从 1.2 稳定降到 0.3但把 epsilon 调到 0 之后智能体还是原地乱撞成功率始终为零。表面看模型“学得越好”实际行为越糟糕。排查后发现问题出在目标网络更新频率上。原因目标网络每 50 步就复制一次 Q 网络参数TD 目标里的max Q_next和预测 Q 值来自几乎同一个网络每步更新后目标也同步漂移梯度的“靶心”一直在动。Q 值看似被压平实际网络只是把当前路径上的 Q 值统一压低并没有区分好与坏。解决把target_update调到 500 到 1000 步。迷宫每个 episode 大概 100 步左右500 步相当于让目标网络稳定 5 个完整回合足够让它成为“慢半拍”的稳定参考。如果还不行可以改成软更新每步做target_net tau * target_net (1 - tau) * q_nettau 取 0.005。硬更新适合迷宫软更新适合你已经确认是自己写训练循环有问题的场景。5.2 epsilon 衰减过快智能体没探索完地图就“锁死”了另一种经典翻车是训练中期成功率到 60%后期反而掉到 20%而且策略固定在一条绕远路线上。把 epsilon 打印出来才发现训练还没走到 3000 步epsilon 已经衰减到了 0.01。此时几乎没有随机动作智能体只能沿着已经学会的次优路径反复跑永远跳不出局部最优。原因我一开始把epsilon_decay设成 1000原以为 1000 步足够但忽略了一条路径上的有效探索步数只有几十步随机探索真正覆盖的状态范围非常小。尤其是终点在迷宫对角线方向时靠随机行走碰到终点的概率极低。解决把衰减步长调成5000甚至10000同时让 epsilon 的下降曲线不是一条直线而是先用 20% 的步长从 1.0 降到 0.5剩余步长再慢慢降到 0.01。最简单做法是在train_dqn里打印每 100 步的 epsilon确保 epsilon 从 1.0 到 0.01 至少经过了 3000 个有效训练步。如果训练到中途成功率不升反降先别调网络先把 epsilon 曲线拉出来看。5.3 固定随机种子之后结果依然不同环境与网络用两套随机源很多人问我为什么设置了torch.manual_seed(42)和np.random.seed(42)训练结果还是每次都不同。这个问题我排查了很久最后发现是 Python 的random库没有设种子。原因训练代码里选 epsilon 随机动作和回放缓冲区采样用的都是random.random()与random.sample它们属于 Python 的全局随机源。PyTorch 的torch.manual_seed管不到它np.random.seed也管不到它。只要random库种子不固定环境的探索路径和采样分布每次都会变训练结果自然天差地别。解决在训练前把三个随机源一起设上random.seed(42) np.random.seed(42) torch.manual_seed(42)如果使用 CUDA还要额外设置torch.cuda.manual_seed_all(42)。重置环境后再跑两次同样代码loss 和成功率曲线应当完全一致。这个操作不是“为了复现而复现”而是让你在调参时能确定改动的是参数还是运气。固定种子后如果发现同一组参数跑两次结果还不同说明代码里有未覆盖的随机源继续排查不要急着调参。5.4 Q 值一路涨到上万loss 变成 inf奖励没缩放、梯度也没裁剪迷宫奖励设计成 1 和 -0.01理论上 Q 值不会超过 2。但有次我把奖励改成“距离差×10”之后Q 值直接在 300 帧内冲到 5000loss 变成inf。这就是奖励量级被放大的连锁反应。原因神经网络的输出没有天然边界如果 TD 目标里的奖励项大到几十甚至一百网络就会不断放大预测值去追赶权重变得巨大梯度也进入爆炸区间。迷宫这种小任务尤其容易忽略这个问题因为 1 的奖励太温和换任务后才爆发。解决第一条防线是奖励缩放把所有奖励除以一个常数把量级压到 [-2, 2] 之间。第二条防线是梯度裁剪在loss.backward()后加一行nn.utils.clip_grad_norm_(q_net.parameters(), max_norm1.0)这样即使个别样本产生超大梯度更新步长也受限网络权重不会一次性被冲崩。第三条防线是调整学习率如果 reward 量级已经缩好但 Q 值还在涨再考虑降学习率。这三条要按顺序检查不要一上来就换网络结构。6. 把迷宫换成连续任务前先用可视化验证 Q 值是不是真学会了训练曲线在下降不代表策略正确成功率达标也不能排除过拟合。走完迷宫任务后我还会做三件事用来确认模型真的学到了空间结构而不是背下了路径。第一画 Q 值热力图。把迷宫里每个格子单独作为输入通过网络得到四个动作的 Q 值取最大值然后按格子位置填色。理论上终点附近格子热力高起点附近热力低墙边和死角热力明显偏低。如果热力图一片模糊说明网络根本没学到空间关系只是靠大概率走到终点。import matplotlib.pyplot as plt q_map np.zeros((env.maze.shape[0], env.maze.shape[1])) for row in range(env.maze.shape[0]): for col in range(env.maze.shape[1]): idx row * env.maze.shape[1] col obs np.zeros(env.maze.shape[0] * env.maze.shape[1]) obs[idx] 1 with torch.no_grad(): q_value q_net(torch.FloatTensor(obs).unsqueeze(0)).max().item() q_map[row][col] q_value plt.imshow(q_map, cmaphot) plt.colorbar() plt.show()第二用贪心策略连续跑 100 局统计成功率和平均步数。注意这里要把epsilon设成 0并且关闭任何 reward shaping只按 Q 值最大动作走。成功率要高于 95%平均步数要比随机策略有明显下降。如果成功率达标但平均步数异常高多半是策略里还有绕圈动作。第三把训练曲线、Q 值热力图、成功局数三条信息放到一起看。训练曲线的 loss 和成功率往往不完全同步loss 下降快但成功率提升慢是正常的反过来如果成功率提升但 Q 值热力图混乱可能就是过拟合当前初始位置。我会习惯用%形式记录一次调参前后这三项指标的变化而不是只记录 loss。这套“可视化 Q 值 贪心评估 多指标对比”的习惯在我后来做更复杂的路径规划和连续控制任务时也一直在用。很多训练问题如果只盯曲线会被表面看似正常的 loss 骗过把 Q 值和行为结果直接画出来往往一眼就能看出问题。希望你也能在这份源码基础上先跑通迷宫再去改地图、改奖励、改网络结构。只要训练脚本骨架不被破坏DQN 这套机制在更多任务里就能直接复用。希望帮到你。本文还有配套的精品资源点击获取