尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MP-DQN强化学习复现指南:栅格环境、经验池与奖励函数工程实践
简介面向具备Python编程与机器学习基础、熟悉TensorFlow和强化学习理论的研究者与从业者文档完整复现了MP-DQN算法在无人机自主避障与目标追踪中的实现流程。资源包仅包含1个docx文档大小28KB以单文件形式提供全部可运行Python代码、参数定义与逐段解释便于直接阅读和实验。文档从环境参数设置如栅格地图、速度关系、探索策略、两层全连接QNetwork与目标网络软更新到成功/失败经验池的差异化采样、奖励函数中距离与方向奖惩的量化设计再到ε-greedy探索策略的细化和批处理梯度下降训练流程均有清晰可执行的代码片段并给出独立测试环节中平均追捕成功率的统计方式。随文还探讨了引入CNN增强地图表征、增加静态障碍物及非均匀重播等改进思路可支撑后续研究扩展。目前已有382人浏览学习适合需要快速上手并深入理解MP-DQN实际调参和工程实现的算法研发者。1. MP-DQN 这篇复现究竟是什么能跑、能改、能复盘的强化学习源码包先说结论这份 PDF 不是那种贴个公式就完事的论文复现而是把深度强化学习里的 MP-DQN多感知 Deep Q-Network完整落到 Python 代码上的可运行实现。它解决的是无人机自主避障与目标追踪里最核心的一类问题一个智能体如何在一个 12×12 栅格地图上一边避开边界惩罚一边持续逼近一个随机移动的目标。和常见 DQN 教程相比它多做了三件很实际的事评估网络与目标网络分离、成功/失败/临时三类经验池分开管理、奖励函数拆成终止、步进、距离、方向四个分量。正因为这些细节它比那种调两小时还收敛不了的玩具代码靠谱得多。适合有 Python 基础、懂一点 DQN 原理、急着把一篇论文变成自己实验基座的人。直接上手改环境参数比从零搭环境快很多。2. 环境和超参数先立住12×12 栅格、ε-inspire 探索与四奖惩的取舍复现这类算法有一个共识网络结构决定上限环境设计和奖励函数决定能不能达到这个上限。MP-DQN 也不例外。这一章先把代码里定义的那一堆参数讲透特别是几个很容易被当成“随便填的数字”实际上有明确取舍的地方。2.1 为什么用 12×12 栅格和一格/半格的速度差代码里GRID_SIZE 12追踪者每步移动 1 格逃避者速度EVADER_SPEED 0.5意味着逃避者每步只走半格。这个速度差不是随意定的它直接决定了任务难度。追踪者如果速度等于逃避者在纯随机策略下几乎不可能追上如果速度远大于逃避者又不需要学任何策略。0.5 这个值让逃避者大约有一半的步数在“原地踏步”追踪者只要方向大致正确多走几步就能碰到。这样设计的好处是训练前期成功池里能积累出真实的正样本网络才有东西可学。注意逃避者位置更新时用了int()截断所以它的坐标始终是整数不会出现 0.5 格的中间位置这一点后面第 4 章还会专门讲是个容易造成误判的坑。状态编码也很有代表性。每个状态由 6 维向量组成第 1、2 维逃避者相对追踪者的坐标差除以 GRID_SIZE 归一化到 [-1, 1] 区间第 36 维追踪者上下左右四个邻居位置是否可以走可走为 1越界为 -1。这里用邻居指示而不是完整栅格图是刻意为之。12×12 地图下全连接网络不需要看整张图只看相对位置和附近可走方向就能做出合理的决策。真正要上 CNN 看全图是第 5 章的事。参数值作用GRID_SIZE12栅格地图边长MAX_STEPS60单回合最大步数EVADER_SPEED0.5逃避者速度追踪者的一半MAX_EPISODES1000最大训练回合数FAIL_THRESHOLD50连续失败次数阈值FORGET_EXPLORATION_RATE0.8遗忘探索率代码里定义了但训练循环没真正调用2.2 ε-inspire不是标准 ε-greedy是带“遗忘”的探索策略标准 ε-greedy 是学习率随训练回合衰减ε 从大往小走。MP-DQN 里的 ε-inspire 走的是另一条路探索率由连续失败次数驱动这一点很容易被忽略。看代码里的逻辑epsilon EPSILON_MIN if total_fail_count FAIL_THRESHOLD else min( EPSILON_MAX, EPSILON_MIN total_fail_count * EPSILON_INCREMENT)参数取值EPSILON_MIN 0EPSILON_MAX 0.99EPSILON_INCREMENT 0.001FAIL_THRESHOLD 50。它意味着训练刚开始时如果一次都还没失败ε 0完全按网络输出的 Q 值选动作每失败一次ε 增加 0.001探索增加一旦连续失败达到 50 次ε 直接归零也就是“遗忘掉”这一轮探索路径从头再试。这和直觉是反着的。一般 DQN 是越训练越少探索这里的逻辑是“失败越多越要探索但连续失败太多就强制回到稳定策略”。从工程角度说这种策略更适合稀疏奖励、成功率很低的任务因为它避免了智能体在一套完全无效的策略上反复打转。实际操作时我一般会盯着total_fail_count和epsilon两个实时量一旦发现 ε 长期卡在 0就要考虑FAIL_THRESHOLD设置是否合理这件事第 4 章会展开。提示FORGET_EXPLORATION_RATE 0.8在代码里只定义了训练循环里没有实际使用。原意是连续失败达到阈值后用 0.8 的比例重置部分网络参数但这份代码并没有实现这个动作。2.3 奖励函数四个分量先保命再转向最后缩距离奖励函数是这份复现里最有嚼头的部分一共四个分量叠加。终止奖惩r_T成功doneTrue时 20失败时间耗尽或出界时 -20步进奖惩r_S出界惩罚 1普通步进惩罚 0.5距离奖惩r_D按当前距离和动作后距离的变化方向给一个高斯加权值系数DISTANCE_REWARD_SCALE 2DISTANCE_REWARD_SIGMA 0.5方向奖惩r_theta根据动作方向和“追踪者指向逃避者”的方向夹角 θ 给奖励系数DIRECTION_REWARD_SCALE 5。这四个分量的量级排序值得注意分量量级特征策略引导作用终止奖惩 r_T±20决定成败权重最高方向奖惩 r_theta最大约 ±2.5先学会朝目标方向走距离奖惩 r_D最大约 1.6在方向正确的基础上缩短距离步进奖惩 r_S0.51避免出界和无意义动作方向奖惩比距离奖惩权重大这是有意为之。如果只看距离追踪者容易在一个“距离在缩短但方向是绕圈”的路径上学歪先强调方向一致性再叠加距离奖惩策略才会既瞄准又逼近。实际复现里如果你想调快收敛可以把DIRECTION_REWARD_SCALE先调大等策略稳定后再把距离奖惩的系数抬起来这也是这类多分量奖励函数最常见的调参顺序。3. 从网络到训练循环三类经验池和软更新把算法跑通这一章直接进入能抄作业的部分。MP-DQN 的代码骨架并不复杂但每一块都有为什么要这么写的逻辑。3.1 QNetwork 是两层全连接代码比自己想的简单网络结构去掉注释其实很短class QNetwork(tf.keras.Model): def __init__(self): super(QNetwork, self).__init__() self.dense1 tf.keras.layers.Dense(60, activationrelu) self.dense2 tf.keras.layers.Dense(4) # 4个动作对应上下左右 def call(self, x): x self.dense1(x) return self.dense2(x) eval_network QNetwork() target_network QNetwork() optimizer tf.keras.optimizers.Adam(learning_rateLEARNING_RATE)输入是 6 维状态第一层 60 个神经元输出 4 个动作价值。60 这个数字不需要过度解读状态维度只有 6动作只有 4太宽的网络反而容易在小样本任务上过拟合。实际用 64 也不会差太多但 60 在原作者的数据量级下收敛更稳。输出层没有激活函数因为 Q 值回归任务需要的是原始数值不是概率。注意这里创建了两个 QNetwork 实例eval_network负责选动作和计算损失target_network负责生成目标 Q 值二者结构完全一样但参数不同步更新。目标网络参数的更新用的是软更新不是每 N 步硬拷贝for target_weight, eval_weight in zip(target_network.trainable_variables, eval_network.trainable_variables): target_weight.assign(TAU * eval_weight (1 - TAU) * target_weight)TAU 0.01意思是每次更新只把评估网络参数的 1% 融合进目标网络。这样目标网络的 Q 值变化非常平滑不会因为某一步奖励跳变导致训练目标剧烈震荡。这对 Make 处理稀疏奖励的任务尤其重要因为终止奖惩 ±20 的量级很大硬拷贝容易让网络在几个回合内把参数抖散。3.2 三类经验池的分工和采样比例经验池部分可以说是 MP-DQN 区别于普通 DQN 最核心的地方class ExperiencePool: def __init__(self, capacity): self.capacity capacity self.buffer [] self.index 0 def store(self, experience): if len(self.buffer) self.capacity: self.buffer.append(experience) else: self.buffer[self.index] experience self.index (self.index 1) % self.capacity def sample(self, batch_size): return random.sample(self.buffer, batch_size) success_pool ExperiencePool(SUCCESS_POOL_CAPACITY) fail_pool ExperiencePool(FAIL_POOL_CAPACITY) temp_pool ExperiencePool(TEMP_POOL_CAPACITY)三个池的容量分别是SUCCESS_POOL_CAPACITY 2000、FAIL_POOL_CAPACITY 2000、TEMP_POOL_CAPACITY 15。临时池只装 15 条这是一个精心设计的数值一个回合最多 60 步15 条相当于“最近四分之一回合的经验”它会一直滚动存放当前回合最近一小段轨迹。采样时有一个关键比例success_batch_size int(BATCH_SIZE * SUCCESS_POOL_RATIO) success_batch success_pool.sample(success_batch_size) fail_batch fail_pool.sample(BATCH_SIZE - success_batch_size) batch success_batch fail_batchSUCCESS_POOL_RATIO 0.85BATCH_SIZE 32意味着每次从成功池采 27 条、失败池采 5 条。这背后的逻辑是任务本身成功率偏低失败样本远远多于成功样本如果按真实比例采样网络会被“失败经验”淹没学到的一直是“怎么撞墙”。把成功样本的比例顶到 85%是在人为纠正样本分布。但这里有一个执行细节只有当两个池子里的样本数都达到BATCH_SIZE时才会触发一次训练更新。也就是说训练初期成功池只有零星几十条数据时网络是根本不会更新的。这不是 bug而是这份算法刻意设计的启动条件——等正负样本都攒够了再开始批量更新。复现时如果发现前几百个回合 loss 一直是初始值不用慌先看池子水位。3.3 从状态构造到梯度更新一整轮能直接照抄的核心代码训练循环里最值得抄的一段是批量更新部分states np.array([exp[0] for exp in batch], dtypenp.float32) actions np.array([exp[1] for exp in batch], dtypenp.int32) rewards np.array([exp[2] for exp in batch], dtypenp.float32) new_states np.array([exp[3] for exp in batch], dtypenp.float32) dones np.array([exp[4] for exp in batch], dtypenp.bool_) actions_onehot tf.one_hot(actions, 4) with tf.GradientTape() as tape: q_values eval_network(states) q_value tf.reduce_sum(tf.multiply(q_values, actions_onehot), axis1) target_q_values target_network(new_states) max_target_q tf.reduce_max(target_q_values, axis1) target rewards GAMMA * max_target_q * (1 - dones) loss tf.reduce_mean(tf.square(target - q_value)) gradients tape.gradient(loss, eval_network.trainable_variables) optimizer.apply_gradients(zip(gradients, eval_network.trainable_variables))这段代码的执行顺序是把 batch 里的经验拆成五组数组用tf.one_hot(actions, 4)把动作转成 one-hot 向量前向计算得到每个样本实际执行动作的 Q 值再用目标网络计算下一状态的最大 Q 值按rewards GAMMA * max_target_q * (1 - dones)构造回归目标最后算 MSE 损失并回传梯度。几个参数的理解要到位GAMMA 0.9是折扣系数下一状态的价值对当前的影响打九折步数越长衰减越多这会让智能体倾向于尽快追上目标而不是绕远路(1 - dones)是为了让终止状态的目标值只等于即时奖励不再叠加未来价值用tf.reduce_max取目标网络的最大 Q 值这是 DQN 系列的经典做法不用管动作是否实际可选因为在小地图上出界的动作已经被回退逻辑处理了。状态构造那一段代码在训练和测试里会反复出现原代码里写成了内联逻辑真正工程化时建议封装成函数。最简单的做法def get_state(tracker_pos, evader_pos, GRID_SIZE): state [ (evader_pos[0] - tracker_pos[0]) / GRID_SIZE, (evader_pos[1] - tracker_pos[1]) / GRID_SIZE ] for i in range(4): neighbor_x tracker_pos[0] [-1, 1, 0, 0][i] neighbor_y tracker_pos[1] [0, 0, -1, 1][i] if 0 neighbor_x GRID_SIZE and 0 neighbor_y GRID_SIZE: state.append(1) else: state.append(-1) return np.array(state, dtypenp.float32)这里[-1, 1, 0, 0]对应左右方向[0, 0, -1, 1]对应上下方向四个循环分别编码四个邻居的可走状态。封装成函数后训练和测试两处调用同一个逻辑后续修改状态定义时只需要改一处。4. MP-DQN 复现避坑五个容易翻车的位置和对应解法复现这份代码最容易卡住的不是强化学习理论而是一些看起来很小的工程细节。下面的每一条我都按「现象 → 原因 → 解决」来写照着排查能省下大半天时间。4.1 TensorFlow 2.2 和 numpy 版本冲突import 就崩现象按代码清单里pip install tensorflow2.2.0装完后import tensorflow as tf直接报ModuleNotFoundError: numpy.core.multiarray failed to import。原因TensorFlow 2.2 是 2020 年的版本它只兼容 numpy 1.x。如果你后续又装了新版本的 numpy尤其 2.0TF 底层 C 扩展加载时就会失败。解决建一个干净的 Python 环境先固定 numpy 版本再装 TensorFlowpython -m venv mpdqn_env source mpdqn_env/bin/activate # Windows 下用 mpdqn_env\Scripts\activate pip install numpy1.19.5 pip install tensorflow2.2.0如果已经装了高版本 numpy先pip uninstall numpy -y再重装。检查版本用python -c import numpy; print(numpy.__version__)。4.2 状态输入少了一对方括号Q 值形状直接错现象单步测试时调用eval_network(state)返回的形状是(4,)而不是(1, 4)再执行tf.argmax(q_values, axis1)就会报轴越界。原因tf.keras.Model的 Dense 层会保留输入的第一维作为 batch 维。如果传入的 state 本身是(6,)的向量网络输出会变成(4,)只有传入(1, 6)才会输出(1, 4)。代码里写的是tf.convert_to_tensor([state])多了一对中括号一旦你在测试阶段自己手写状态构造时漏掉这层括号形状就全乱了。解决在所有单步推理的地方保证输入是二维的state_tensor tf.convert_to_tensor([state], dtypetf.float32) q_values eval_network(state_tensor)训练阶段从经验池取出的states本身就是(batch, 6)不会踩这个坑最容易漏的就是测试循环里对单条状态做推理时。4.3 成功池被临时池转储污染成功率虚高又突然崩现象训练中后期测试成功率一度不错但 loss 不降反升成功率曲线上下剧烈抖动。原因临时池temp_pool每存满 15 条就会执行一次success_pool.store(temp_pool.buffer.pop(0))也就是说不管当前回合最终成功还是失败只要临时池满了最老的那条经验都会被塞进成功池。一个失败的回合里前面 15 步的片段也可能进入成功池等于给成功池注入了大量“伪成功样本”。一旦这些样本占到主导网络学到的 Q 值就是偏的。解决严格区分两类转储逻辑。临时池满了溢出的旧样本不应自动进成功池只有回合结束且reward TERMINAL_REWARD时才把整个临时池的内容转入成功池失败回合的临时池内容转入失败池。如果你不想大改代码至少把溢出的那条经验转存到 fail_pool避免污染成功池。注意原代码里临时池溢出转储到成功池的逻辑是真实存在的很多复现者会误以为这是 MP-DQN 的设计精髓。实际上这是为了保留“接近成功的路径片段”但如果训练效果异常优先检查这里。4.4 逃避者 0.5 速度被 int() 截断基线成功率虚高现象测试成功率跑到 60% 以上但人眼观察模拟过程发现逃避者大部分时间留在原地追踪者只要偶尔路过就能撞上。原因new_evader_pos [max(0, min(GRID_SIZE - 1, int(pos))) for pos in new_evader_pos]里的int(pos)把 0.5 的移动量直接截断成 0。逃避者每步实际只走 0 或 1 格速度不是预设的“0.5 步/回合”而是“一半概率不动”。解决把逃避者坐标改成浮点数存储只在判断碰撞和越界时取整或者把 EVADER_SPEED 改成随机游走开关——每步以 50% 概率移动 1 格以 50% 概率不动。两种改法都会让基线成功率明显下降但这才反映真实任务难度。4.5 训练卡死不动先查 ε 是否被 FAIL_THRESHOLD 压到 0现象训练了几千回合成功率曲线几乎是平的loss 一直很大但训练循环没报错。原因当total_fail_count FAIL_THRESHOLD50 次时epsilon EPSILON_MIN 0探索彻底关闭。如果评估网络当前的策略很烂但又不探索新动作就会一直重复同样的烂路径形成死循环。更隐蔽的问题是FORGET_EXPLORATION_RATE定义了 0.8 却被闲置理论上连续失败 50 次后应该用 0.8 的遗忘率重置网络但代码没做这一步所以智能体根本没有“重来”的能力。解决训练前期把total_fail_count、epsilon、每个回合的累计奖励打印出来。如果 ε 长期为 0先把FAIL_THRESHOLD降到 10或者给 ε 设置一个下限比如 0.05保证任何时候都有最低限度的探索更彻底的做法是真正实现遗忘逻辑当连续失败达到阈值时把评估网络的权重按 0.8 的比例重置或重新初始化成功池让训练重新开始。否则这个参数就是个摆设光看代码很容易误以为它是激活的。5. 从论文示例到可用工程障碍物、CNN 与优先经验回放的改造原代码是最简化的栅格追逐环境真实工程里不会这么空。这一章把资源里提到的三个扩展方向逐个落地加障碍物、换 CNN、改优先经验回放。5.1 加静态障碍物环境从“空旷”变为“有约束”在环境初始化时随机生成几个障碍物位置追踪者和逃避者移动时都要检查碰撞OBSTACLE_NUM 5 obstacles [] for _ in range(OBSTACLE_NUM): obstacle_pos [random.randint(0, GRID_SIZE - 1), random.randint(0, GRID_SIZE - 1)] obstacles.append(obstacle_pos) def is_collide_with_obstacle(pos, obstacles): for obstacle in obstacles: if np.array_equal(pos, obstacle): return True return False移动判定要同时检查越界和障碍new_tracker_pos [tracker_pos[0] action[0], tracker_pos[1] action[1]] is_out_of_bound new_tracker_pos[0] 0 or new_tracker_pos[0] GRID_SIZE or new_tracker_pos[1] 0 or new_tracker_pos[1] GRID_SIZE if is_out_of_bound or is_collide_with_obstacle(new_tracker_pos, obstacles): new_tracker_pos tracker_pos tracker_pos new_tracker_pos这里的关键点有两个。一是障碍物数量OBSTACLE_NUM 5在 12×12 的地图上意味着 5/144 ≈ 3.5% 的格子被占用密度不高够训练但不会让任务变成纯猜谜。二是障碍物位置是随机生成、不避开初始点的如果正好生成在追踪者或逃避者的初始位置会导致回合一开始就卡死。工程上稳妥的做法是生成障碍物时排除起点附近两格的范围。还有一个需要决策的点逃避者撞到障碍物是原地等待还是绕行原代码直接回退到原地这是最简单也最稳定的处理方式。如果想让逃避者会绕障碍那不是改这个逻辑能解决的得给逃避者也单独训练一个避障策略。状态编码在加了障碍物之后也应该同步更新现在四个邻居不仅可能越界还可能被障碍物占据原来的if 0 neighbor_x GRID_SIZE判断要扩展成“越界或碰撞都视为不可走”。5.2 CNN 化 QNetwork观察从 6 维向量升级为整张栅格图当地图从 12×12 扩大到 32×32 甚至更大6 维状态向量信息量就不够了。原代码给出的改造方向是把输入换成完整栅格图class QNetwork(tf.keras.Model): def __init__(self): super(QNetwork, self).__init__() self.conv1 tf.keras.layers.Conv2D(16, kernel_size(3, 3), activationrelu, input_shape(GRID_SIZE, GRID_SIZE, 1)) self.flatten tf.keras.layers.Flatten() self.dense1 tf.keras.layers.Dense(60, activationrelu) self.dense2 tf.keras.layers.Dense(4) def call(self, x): x tf.expand_dims(x, -1) # 添加通道维度 x self.conv1(x) x self.flatten(x) x self.dense1(x) return self.dense2(x)tf.expand_dims(x, -1)是把(GRID_SIZE, GRID_SIZE)的矩阵变成(GRID_SIZE, GRID_SIZE, 1)让 Conv2D 能识别出通道维。Conv2D 用 16 个 3×3 卷积核对栅格地图来说 3×3 的局部感受野足够捕捉邻近障碍物关系不需要更大的核。input_shape在这里其实可以省略因为第一个卷积层的输入形状已经由实际数据决定了保留它主要是为了代码可读性。我的建议是12×12 的地图没必要换 CNN全连接网络已经能轻松拟合地图边长超过 24 之后全连接网络的参数量会急剧膨胀这时才值得换。训练时要注意把状态从 6 维向量改为(GRID_SIZE, GRID_SIZE)的矩阵比如把障碍物位置标为 1、目标位置标为 2、空地标为 0通道维由expand_dims补上。改动后 batch 输入的维度是(batch, GRID_SIZE, GRID_SIZE, 1)训练循环里的states np.array(...)要同步调整reshape。5.3 优先经验回放把低优先级样本从训练中“筛”出去原代码用random.sample从经验池均匀采样这在小经验池里够用但遇到奖励稀疏的任务大量低价值样本会稀释训练信号。优先经验回放PER的思路是让 TD 误差大的样本有更高概率被抽到import heapq class PrioritizedExperiencePool: def __init__(self, capacity): self.capacity capacity self.buffer [] self.index 0 self.priorities [] def store(self, experience, priority): if len(self.buffer) self.capacity: self.buffer.append(experience) heapq.heappush(self.priorities, (-priority, self.index)) else: old_index self.priorities[0][1] self.buffer[old_index] experience heapq.heapreplace(self.priorities, (-priority, self.index)) self.index (self.index 1) % self.capacity def sample(self, batch_size): batch_indices [] for _ in range(batch_size): priority, index heapq.heappop(self.priorities) batch_indices.append(index) heapq.heappush(self.priorities, (priority, index)) return [self.buffer[i] for i in batch_indices]这段代码用heapq维护一个按优先级排序的小顶堆弹出的都是当前优先级最高的样本。实际使用时需要把训练循环里的 TD 误差作为 priority 传进去td_error np.abs(target - q_value) priority td_error 1e-6 1e-6是防止某个样本的 TD 误差为 0 时永远不被采到。标准的 PER 还会加一个指数 α 控制优先级的影响力α 取 0 就是均匀采样取 1 就是完全按优先级采样工程上一般用 0.6 左右。需要注意存储时的 priority 是那一时刻的 TD 误差经验被采样训练后 TD 误差会变严格做法是训练完重新计算并更新到池子里上面这版为了可读性省略了回写逻辑实际项目里要补上否则优先级会越用越失真。顺便提一下原代码第 9 节提到的函数封装把状态构造、奖励计算都抽成独立函数这对扩展改动非常重要。加完障碍物之后我只改get_state和移动判定两处训练循环一行都不用动。6. 验证习惯比调参更重要先看随机基线再谈收敛我复现这套 MP-DQN 时犯过一个很典型的错误第一次训练完看到测试成功率 80% 就觉得算法没问题了后来顺手把EVADER_SPEED从 0.5 改成 1.0成功率直接跌到 30%这时才意识到之前的 80% 里有多少是逃避者原地罚站带来的水分。从那以后我每次复现 RL 代码都强制自己走三遍验证流程。第一步先跑随机基线。训练前用纯随机策略跑 1000 个测试回合记录成功率。这个数字是后面的参照系如果训练后成功率只比随机高几个点说明奖励函数或者探索策略有问题不值得继续调参。第二步看训练日志而不是只看测试成功率。我一般每 100 个回合打印一次平均累计奖励、连续失败次数、当前 ε 和 loss# 按回合记录训练日志便于画曲线 episode_rewards [] episode_epsilons [] episode_reward 0 for step in range(MAX_STEPS): action epsilon_inspire_policy(state, epsilon, eval_network) # ... 环境交互 ... episode_reward reward if episode % 100 0: print(fepisode{episode}, avg_reward{np.mean(episode_rewards[-100:]):.2f}, fepsilon{epsilon:.3f}, fail_count{total_fail_count}) model_save_path mpdqn_eval_network.h5 eval_network.save_weights(model_save_path)loss 下降不代表策略变好但 loss 长期不降一定有问题连续失败次数和 ε 的变化能告诉你探索有没有真正发生这两个量比 loss 更能反映训练状态。第三步固定随机种子多跑几个种子取平均。强化学习训练对随机种子极其敏感同一个参数种子 0 可能跑出 90% 成功率种子 1 只有 40%。我会用tf.random.set_seed(seed)和np.random.seed(seed)固定然后至少在 3 个种子上各训练一轮取测试成功率的平均值和方差。只报单次结果在论文复现和工程验证里都没有说服力。模型保存用 Keras 原生的save_weights就够不保存整个模型对象因为网络结构本来就在代码里定义着。测试时加载权重把epsilon固定为 0这样和训练时的探索策略完全隔离开。这套流程看起来笨但能挡住大多数“看起来很成功”的假象。从那以后我每次复现 DQN 系算法都会先跑随机基线再强制走一遍日志检查和多种子平均确定数字靠谱了才敢往下改环境或者动网络结构。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

微信小程序开放接口实战:运动数据、地址选择与生物认证

微信小程序开放接口实战:运动数据、地址选择与生物认证

做小程序开发这几年,我见过太多团队把精力全放在页面和交互上,最后却在“用户身份数据怎么安全拿到”这个问题上翻车。尤其是一些看起来不起眼的开放接口——运动数据、收货地址、生物认证,它们单拎出来都不复杂,但一旦放进真实业…

📅 2026/10/7 22:23:57
超声波清洗机维修实操:从换能器原理到驱动板故障排查

超声波清洗机维修实操:从换能器原理到驱动板故障排查

超声波清洗机坏了,拿去维修摊,很多老板看都不看就报价,一百起步,还一副“修这玩意儿不如买新的”的表情。其实这种设备的结构远比你想的透明:一个不锈钢水槽,槽底粘着一只压电换能器,底座里藏着…

📅 2026/10/7 22:23:57
微信小程序开放接口实战:运动数据、收货地址与生物认证全解析

微信小程序开放接口实战:运动数据、收货地址与生物认证全解析

1. 项目概述先把这个项目的真实面目拆给你们看。标题里写着“小程序——开放接口(运动、收货地址和生物认证)”,听上去像是一堆API的罗列,但它背后真正要解决的,是微信小程序里三个经常让开发者头大的“系统级能力”接…

📅 2026/10/7 22:23:57
MORE NEWS

更多资讯

📰

基于JavaWeb的员工信息管理系统:毕业设计从源码到部署全指南

简介:基于JavaWeb的企业员工信息管理系统源码与数据库脚本,面向正在准备毕业设计的计算机专业学生和需要项目实战的Java学习者,可直接作为毕业设计、课程设计或期末大作业使用。这套管理系统采用B/S架构,结合JSP与MySQL&#xff0…

📰

果蔬识别实战:基于YOLOv8的数据系统与训练避坑全指南

简介:这是一份基于YOLOv8的果蔬识别数据系统项目,适合人工智能、深度学习方向正在准备课程大作业或毕业设计的本科生,也适合需要完整实战案例的初学者。项目源码经过本地编译调试,配套数据集、标注缓存与说明文档,可直…

📰

Agent-Reach:轻量级Python多智能体CLI协调器

1. 项目概述:Agent-Reach 是什么,为什么值得花时间搞懂它 Agent-Reach 不是一个抽象概念,而是一个真实存在于 GitHub 上、带完整 CLI 接口、采用 MIT License 开源的 Python 工具项目。它不是玩具级 demo,也不是教学示例&#xff…

📰

智能体工程化与业务落地:从GitHub趋势到系统架构实战

这周把 GitHub Trending 从头到尾翻了几遍,最明显的变化不是又出了哪个惊艳的 Demo,而是智能体相关项目的味道变了:框架、编排、审计、评测、多智能体协作、业务场景集成,这些词开始扎堆出现。如果说前半年大家还在用智能体炫技&a…

📰

Cadence Allegro异形焊盘设计:Shape Symbol从入门到实战

项目标题: "【Cadence Allegro16.6实战】巧用Shape Symbol:从零到一构建异形焊盘" 一说起异形焊盘,很多刚开始用Cadence Allegro 16.6的工程师第一反应就是去Pad Designer里把Regular Pad、Thermal Relief、Anti Pad三个选项卡的Geometry挨个…

📰

国庆节快乐!从美少女大战丧尸开始:节日主题游戏企划全解析

1. 从一句节日祝福到一场末日狂欢:这个标题到底在说什么 国庆假期,朋友圈里刷屏的无非是两种内容:一种是高速堵车、景区排队的实况转播,另一种就是各种游戏开黑截图。而“国庆节快乐!从美少女大战丧尸开始!…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬