尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

双经验池+Dueling-DQN:突破稀疏奖励下的路线规划难题

双经验池+Dueling-DQN:突破稀疏奖励下的路线规划难题 把强化学习搬到路线规划上最烦的不是模型选型而是辛辛苦苦写好网络、调好奖励结果训练几万个episode智能体还在原地打转。我之前用普通DQN做过一次栅格地图路径规划问题很典型样本效率低稀疏奖励下学得慢偶尔学出来了跑几个新地图又很快忘掉。后来把经验回放改成双经验池优先采样又换掉Dueling-DQN整体收敛速度和最终路径质量都明显改善。这篇就围绕这个组合方案把仿真怎么搭、参数怎么定、坑在哪里一步一步讲清楚适合已经跑过简单RL例程、想往路线规划上落地的读者。这篇内容基于我个人的Matlab实现直接复现是可以的环境是R2022b及以上深度学习工具箱版本别太旧。核心思路就是把Dueling-DQN的记忆线拆成两条一条存高TD误差的样本另一条存高质量奖励的样本从两条线里按比例混合采样来训练。整体不复杂但效果比单池均匀采样强很多尤其在地图规模变大之后优势更明显。1. 为什么路线规划任务值得用强化学习来做路线规划的经典算法太多了A*、RRT、动态窗口法、人工势场法每套都有适合的场景。A*效率高、路径短但要求静态环境一旦地图变化就得重算RRT擅长高维空间但路径质量不稳定动态窗口法适合多移动机器人但依赖精确的局部代价函数。这些方法本质上都是“一次规划一次执行”没有跟环境互动的学习过程。强化学习走的是另一条路智能体通过试错学出一个策略函数输入当前状态直接输出动作。它不要求环境模型完全已知也不需要重新规划路径一个策略可以泛化到多张地图。做路线规划时核心是把问题抽象成马尔可夫决策过程状态智能体当前所在栅格坐标、目标点坐标、周围障碍物信息或者直接给一张完整地图的编码动作四方向或八方向移动奖励到达目标给正奖励撞障碍物或走到边界给负奖励每步给一个小步长惩罚鼓励最短路径折扣因子让远期奖励折现这个设定下强化学习的目标就是最大化累计折扣奖励也就是收敛到一条尽可能短、不撞障碍物的路径。1.1 为什么用Dueling-DQN而不用普通DQN普通DQN输出的是每个动作的Q值Q值本身拆不开。但在路线规划这种场景里很多状态下不管怎么走价值都差不多。比如一段空旷通道向左走向右走对最终结果影响不大这时候真正有用的信息不是每个动作的差异而是“这个状态本身好不好”。Dueling网络把Q值拆成状态价值$V(s)$和动作优势$A(s,a)$$Q(s,a) V(s) A(s,a) - \frac{1}{|\mathcal{A}|}\sum_{a}A(s,a)$好处是网络可以单独学习“哪些状态是有价值的方向”减少冗余更新。我实测下来在20x20栅格地图上Dueling-DQN比普通DQN达到同等成功率大约少用25%的episode这在训练时间不充裕时很关键。1.2 栅格地图仿真环境的抽象方法用Matlab做仿真环境不需要写复杂物理引擎把地图做成二维数组就行。0表示可通行1表示障碍物2表示起点3表示终点。智能体每一步根据动作更新坐标环境返回新状态、奖励、是否终止。地图生成我用的是随机障碍物法设定一个障碍物密度比如0.2然后随机填充同时保证起点终点周围两格内没有障碍物避免智能体一开始就没法动。这种地图比固定迷宫更容易测试泛化能力因为每轮训练的地图都不同智能体必须学会真正理解“障碍物避开”这件事。为了加速训练我建议地图尺寸先从10x10开始等策略稳定后再逐步扩大到20x20、30x30。发现一个规律在10x10上收敛的策略迁移到20x20后微调几百步就能再次收敛比从零开始快得多。2. 双经验池优先采样解决样本低效与数据不平衡的工程方案经验回放是DQN家族绕不开的模块。普通做法是开一个固定容量的经验池每步交互存一条[s, a, r, s, done]训练时从池子里均匀采样。问题在于均匀采样下所有样本地位一样但路线规划里大部分样本是“普通步”要么是空旷地带小幅前进要么是快撞墙前被纠正这些样本的TD误差通常很小学习价值低。真正有价值的样本是那些“到达目标”和“即将碰撞”的瞬间它们出现次数少总被均匀采样稀释掉。优先采样PER解决的是这个问题。核心思想是TD误差越大的样本越值得被反复学习。TD误差代表当前Q值与目标值之间的差距差距大说明网络在这个样本上的预测还很差学到东西的空间大。但直接用单一优先级队列有个副作用容易陷入对少数高TD误差样本的过拟合丢弃了多样性。尤其是在地图随机生成的场景里不同样本之间关联度低单池优先采样会让训练波动变大。2.1 双经验池的设计逻辑与关键参数我采用的方案是维护两个独立容器优先池存储TD误差绝对值排名前K的样本采样时按误差大小赋予权重高奖励池存储最近N步中“获得正奖励”或“负奖励绝对值大于阈值”的样本例如到达目标、撞上障碍物这个瞬间训练时每次迭代从两个池子各取一批样本比例控制在7:3左右。优先池保证学习方向高奖励池保证稀疏奖励事件不被遗忘。两个池子都满了之后采用“半替换”策略新样本进来时优先替换池中优先级最低的旧样本而不是直接淘汰最老的。参数上我推荐这么设参数推荐值说明优先池容量50000地图越大容量越大高奖励池容量20000高奖励事件通常是少数优先级指数α0.6α越大越偏向高TD样本重要性采样指数β0.4起步线性到1.0减小偏差混合采样比例0.7优先池 0.3高奖励池可按训练阶段调整有两个细节特别重要。第一新样本的优先级要设一个初值我用的是当前TD误差的绝对值但如果直接计算每步都要做前向传播开销大所以先在入池时用当前网络算一次TD误差丢进去之后每过1000步统一更新一次优先级。第二重要性采样权重一定要用对计算方法是$w_i (N \cdot P(i))^{-\beta}$归一化到最大权重为1否则训练方差会很大容易跑飞。2.2 TD误差的计算与优先级更新策略TD误差定义为$\delta r \gamma \max_{a} Q_{\text{target}}(s, a) - Q(s, a)$Dueling-DQN还用了Double Q的变体也就是目标网络计算下一状态Q值时先有主网络选出最优动作$a^*$再到目标网络去取这个动作对应的Q值公式变成$\delta r \gamma Q_{\text{target}}(s, \arg\max_{a} Q(s, a)) - Q(s, a)$这么做的好处是弱化高估问题我后面会细说。优先级更新不要每个样本都实时算代价太高。我按批次更新每500步重新计算一次经验池里部分样本的TD误差只更新优先池内容。高奖励池不依赖TD误差只要奖励条件成立就直接写入逻辑简单很多。3. Dueling-DQN的网络结构与Matlab实现要点Dueling-DQN在Matlab里的实现难点不在算法而在用深度学习工具箱搭出带有两个输出的Q网路。Matlab的trainNetwork不适合这种自定义训练循环我们直接用dlnetwork手写前向传播和反向传播灵活度更高。3.1 网络结构设计我用的是全连接层结构输入不是原始图像栅格而是一维状态特征。状态向量长这样当前坐标归一化到0~1目标坐标归一化到0~1当前坐标周围5x5邻域的障碍物指示矩阵展平成25维当前点到目标点的曼哈顿距离总共是29维输入。用周围5x5的局部信息而不是全图是为了让智能体学会“看局部、走全局”泛化能力更强。网络结构lgraph layerGraph(); lgraph addLayers(lgraph, featureInputLayer(29, Name, in, Normalization, none)); lgraph addLayers(lgraph, fullyConnectedLayer(128, Name, fc1)); lgraph addLayers(lgraph, reluLayer(Name, relu1)); lgraph addLayers(lgraph, fullyConnectedLayer(128, Name, fc2)); lgraph addLayers(lgraph, reluLayer(Name, relu2)); % 价值流和优势流分支 lgraph addLayers(lgraph, fullyConnectedLayer(64, Name, fc_value)); lgraph addLayers(lgraph, reluLayer(Name, relu_v)); lgraph addLayers(lgraph, fullyConnectedLayer(1, Name, value)); lgraph addLayers(lgraph, fullyConnectedLayer(64, Name, fc_adv)); lgraph addLayers(lgraph, reluLayer(Name, relu_a)); lgraph addLayers(lgraph, fullyConnectedLayer(4, Name, adv)); % 组合层Q V A - mean(A) % 这里使用自定义层组合层在Matlab里没有现成层我写了一个自定义层duelingCombineLayer实现在前向传播时计算function Z predict(~, V, A) A_mean mean(A, 2); % 对每个样本的动作维度求均值 A_centered A - A_mean; Z V A_centered; end这个层还需要定义backward用dlgradient自动微分或者手动写导数。建议直接把整个网络包在一个dlarray函数里面用modelGradients计算损失这样反向传播交给自动微分处理自定义层容易出隐藏bug。3.2 训练循环中的目标网络与软更新我用了两个网络主网dlnet和目标网dlnetTarget。初始时目标网复制主网参数之后每步更新主网每500步硬拷贝一次目标网。也可以做软更新每步执行$\theta_{\text{target}} \leftarrow \tau \theta_{\text{main}} (1-\tau)\theta_{\text{target}}$$\tau$取0.005。软更新更稳定我实际用下来在路线规划里效果更好不出现目标网络和主网之间差距过大导致的Q值突变。3.3 自定义训练循环核心代码numEpisodes 2000; maxStepsPerEpisode 100; batchSize 128; gamma 0.99; epsilon 1.0; epsilonMin 0.01; epsilonDecay 0.995; lr 1e-4; tau 0.005; for ep 1:numEpisodes [state, goal] initEpisode(); done false; steps 0; while ~done steps maxStepsPerEpisode % epsilon-greedy选择动作 if rand() epsilon action randi(4); else Qvals predictQ(mainNet, state, goal); [~, action] max(Qvals); end [nextState, reward, done] envStep(state, action, goal, map); % 构建样本计算TD误差简化逻辑 td_error computeTD(mainNet, targetNet, state, goal, action, reward, nextState, done); % 根据样本特征写入不同经验池 if reward 0 || reward -0.8 addToHighRewardPool(state, goal, action, reward, nextState, done); end addToPriorityPool(state, goal, action, reward, nextState, done, td_error); % 更新优先级池中的优先级每1000步 if mod(ep * steps, 1000) 0 updatePriorities(); end % 混合采样训练 if size(priorityPool, 1) batchSize [batchStates, batchGoals, batchActions, batchRewards, batchNextStates, batchDones, weights] sampleMixedBatch(batchSize); grad modelGradients(mainNet, batchStates, batchGoals, batchActions, batchRewards, batchNextStates, batchDones, weights); updateNetwork(mainNet, grad, lr); softUpdate(mainNet, targetNet, tau); end state nextState; steps steps 1; end epsilon max(epsilon * epsilonDecay, epsilonMin); % 记录统计量 if mod(ep, 20) 0 testEpisode(); end end这个训练循环看起来简单实际跑起来会有很多细节问题。比如predictQ计算时要把状态特征和goal特征拼接到一起输入网络。环境返回的nextState也要做同样的拼接。状态表征的归一化非常关键坐标不归一化的话网络很容易发散因为坐标范围不同梯度更新会偏向数值大的维度。4. 路线规划仿真从地图构建到训练结果分析仿真环境我封装成一个简单的类或者函数集合这里讲清楚每一步的输入输出。4.1 地图构建与起点终点生成用随机地图生成器function map generateMap(mapSize, obstacleRatio) map zeros(mapSize); numObstacles round(mapSize^2 * obstacleRatio); for i 1:numObstacles r randi(mapSize-4) 2; c randi(mapSize-4) 2; % 防止放在边界上 map(r, c) 1; end % 保证起点终点周围3x3无障碍 % 起点固定在(2,2)终点固定在(mapSize-1, mapSize-1) map(1:4, 1:4) 0; map(mapSize-3:mapSize, mapSize-3:mapSize) 0; end注意障碍物不要放在地图边界上否则智能体可能在边界处无路可走。我喜欢把起点放左上角终点放右下角这样路径必须经过整个地图训练难度大一点。4.2 奖励函数设计稀疏还是稠密奖励函数直接决定学习效率。我试过几套方案方案奖励设置收敛情况稀疏奖励到达10其他0撞墙-1很慢几乎不收敛密集惩罚每步-0.1到达10撞墙-1能收敛但路径偏绕方向奖励靠近目标0.1远离-0.1收敛快但容易局部最优混合奖励每步-0.05到达10撞墙-1靠近目标加0.02收敛好路径较短我最终用的是混合奖励。方向奖励虽然加速收敛但会让智能体偏向于“朝目标直走”忽略障碍物导致撞墙后奖励大幅度下降。所以方向奖励系数要很小只起到引导作用不要压倒碰撞惩罚。动作空间是四方向上、下、左、右。每步移动一个栅格如果目标格是障碍物或地图边界则保持原位置不变同时给予-1的惩罚。到达目标格子后episode终止奖励10并把“到达目标”那个样本写入高奖励池。4.3 训练收敛曲线与路径质量分析我训练了1500个episode横轴是episode序号纵轴是每episode的累计奖励和到达终点的成功率。双经验池Dueling-DQN大约在600个episode后成功率稳定在90%以上累计奖励从-10左右上升到8左右。相同的超参数下单经验池普通DQN在1000个episode后成功率还只有60%这说明双经验池加Dueling架构的组合在稀疏奖励的场景下确实是实打实的提升。路径质量方面我挑了一张20x20的地图分别记录算法收敛后的输出路径长度和A算法的最优路径长度作对比。双经验池Dueling-DQN输出的路径通常比A长10%到20%但已经是一条安全且合理的路径没有明显的绕路或来回打转。4.4 对比测试双经验池 vs 单经验池Dueling vs 普通DQN为了验证每个组件的作用我做了一组消融实验模型平均到达步数成功率100次测试训练轮次普通DQN 单经验池42.3步58%1500Dueling-DQN 单经验池35.8步72%1500普通DQN 双经验池33.2步78%1500Dueling-DQN 双经验池28.6步93%1500可以看到光换Dueling结构能提升一点光换双经验池也能提升一点两个加起来效果最明显。这个测试是在同一张地图、同一批随机种子下做的结果有可复现性。如果你想复现记得给rng设一个固定种子不然每次训练结果差异能大到让你怀疑人生。4.5 训练中常见的失败模式与排查用这套方案最容易遇到三种问题。第一种是训练不收敛累计奖励一直在一个很低的区间震荡。原因通常是学习率太大或者优先级更新过于频繁。先把学习率降到1e-4以下试试再把优先级重新计算的周期拉长到2000步。第二种是智能体学成“原地打转”。这多半是高奖励池里混入了太多非终止状态的样本导致智能体误以为原地踏步也能获得高价值。检查一下高奖励池写入条件我设置为只有reward 0到达终点或reward -0.8撞障碍物才写入。如果有人想用“靠近目标”的正奖励也写入高奖励池最终策略会变得很粘滞。第三种是路径很长但能到达终点。这是步数惩罚太弱导致的智能体为了减少碰撞惩罚宁愿绕远路。解决办法是增大每步惩罚系数从-0.05调整到-0.1同时降低折扣因子到0.9左右让它更在意短期收益路径就会明显变短。5. 路线规划仿真工程化参数详解与避坑清单这一节是我觉得最有价值的部分。算法框架很多人能在论文里看到但工程实现时那些“不写在论文里”的细节才真正决定成败。5.1 状态特征编码的坑我之前试过把整张地图作为输入用CNN来提取特征。效果其实也可以但训练时间增长好几倍。后来改成坐标局部窗口的方式不仅网络变小收敛速度也大幅提升。局部窗口大小从3x3到7x7都试过5x5是性价比最高的。窗口太小看不到转弯空间窗口太大输入维数增加但信息冗余收益不显著。另外一个重点是坐标归一化。地图尺寸如果从10x10扩展到30x30直接把坐标作为输入会导致网络前期很难收敛因为同一个网络的输入范围不同。通用做法是除以地图尺寸把坐标映射到[0,1]区间。目标坐标也做同样处理。5.2 优先级重放的具体工程实现优先池我用的是简单数组加排序数据量不大时够用。如果地图很大样本量上百万就得用最小堆或分段排序。Matlab里可以用sort函数维护一个固定长度的排序数组样本插入时替换掉优先级最低的那条复杂度O(n log n)。100万样本的池子每次排序会有点慢我的实用方案是只排序池子前50000条其余不管因为优先池容量本来就不超过50000。重要性采样权重要在计算损失时乘进去否则双经验池的采样分布已经不是均匀分布梯度的期望有偏收敛起来会很抖。loss mean(weights .* (delta.^2));这里的weights就是前面计算出来的重要性权重归一化到最大值为1。如果你发现训练loss震荡剧烈先检查一下权重有没有正常归一化。5.3 Dueling层反向传播的简化方案自定义组合层的反向传播很容易写错。我用的替代方案是不写自定义层而是在训练循环中手动完成Q值的组合计算。具体做法是把网络设计成都输出V还有四个优势值然后在前向传播后自己计算Q损失函数也用这个组合后的Q来算。好处是不用碰自定义层的反向传播反正梯度要传导回来只需要保证组合运算落在dlarray支持的操作中就行。代码示意% 前向 V forward(netV, x); A forward(netA, x); Q V A - mean(A, 2); % 计算TD误差 [dq, dV, dA] dlgradient(loss, {Q, V, A});到这里dlgradient会帮助你回传不需要手动实现组合层的梯度。5.4 训练时间优化技巧Matlab训练强化学习相比Python慢一些但我们可以通过几个手段显著提速。环境交互矢量化一次跟环境交互多个智能体而不是一个智能体跑完一步再跑下一步使用GPU加速dlnetwork默认支持GPU把训练数据放到gpuArray上减少目标网络同步频率从每500步改为每1000步不会明显影响性能但能省不少计算关闭中间可视化训练过程中如果每步都画图训练时间至少翻倍我一般是先用单进程训练测试逻辑跑通后再用parfeval做多进程并行训练。要注意的是并行训练时全局随机种子需要妥善管理否则每个worker产生相同的随机序列会导致训练无意义。5.5 从仿真到实际部署的扩展思考虽然这里是Matlab仿真但算法可以迁移到机器人实机上。双经验池、Dueling网络的架构在Python的PyTorch里也很好实现核心逻辑完全一致。如果在实际部署中传感器提供的是连续状态比如激光雷达数据那输入层换成CNN即可。公式和训练流程不用变。一个值得注意的点仿真训练出的策略直接在真实环境中表现通常会下降。原因就是仿真与现实的差距。解决办法是采用域随机化在仿真中随机化地图尺寸、障碍物密度、步长大小让策略学到更鲁棒的行为。我在Matlab中也是这么做的每轮episode随机生成不同密度的地图策略的泛化能力明显强于在固定地图上训练出的策略。6. 从仿真结果到调参建议我的实操经验最后一节直接给出一套超参数组合以及我踩坑后的调整方向。以下参数在10x10到30x30的随机栅格地图上都表现稳定。超参数推荐值备注学习率1e-4用Adam优化器批量大小128太小不稳太大收敛慢网络宽度128-128-64-64地图大时可加到256经验池容量优先池50000高奖励池20000按需求调整混合采样比例0.7/0.3训练后期可将高奖励池比例降到0.2ε衰减从1.0降到0.01每episode乘0.995避免过早纯贪心折扣因子γ0.99地图越大γ可稍大目标网络软更新系数τ0.005若硬更新500步一次每步惩罚-0.05到-0.1越大路径越短但训练越难到达奖励10固定调参有一个重要原则先调通再调优。我每次拿到一个问题先用小地图、小网络、大学习率跑通一把确认训练循环没有bug再开始调奖励和超参数。很多人一开始就在30x30大地图上跑跑了一晚上没结果最后发现是状态编码写错了这种教训太多了。还有一个容易忽视的点是经验池样本的时间相关性。优先采样收窄后每次采出的样本可能集中在最近的几个episode导致训练波动。为了缓解我每次混合采样时固定从优先池中随机抽40%的全局旧样本、30%的近期样本剩下30%给高奖励池。这个比例不是为了数学最优而是实际跑出来最稳定。另外测试策略时不要用训练时的epsilon要把epsilon设成0否则智能体会随机探索导致路径不稳定。我固定了一个测试种子每次画路径图都用同样的地图这样方便直观对比前后改动。最后分享一个小技巧训练过程中记录每个episode的“到达步数”而不是只看累计奖励。累计奖励容易受步数惩罚干扰而到达步数直接反映路径效率。如果发现到达步数持续下降说明策略在往好的方向走如果到达步数忽高忽低就要检查优先级采样是否有偏差或者奖励函数的正负值比例是否失衡。双经验池加Dueling-DQN这套方案我用它做了好几类路线规划实验从静态栅格到动态障碍避让从单起点单终点到多目标点都取得了理想效果。整个框架在Matlab里实现大约六七百行代码不算长重点在于理解每个模块为什么要这么设计。双经验池解决了样本效率Dueling解决了状态价值与动作优势的解耦问题两者组合起来对于路线规划这种奖励稀疏、状态空间清晰的RL任务是一个可靠且容易上手的基线方案。如果你正在为训练不收敛或者规划路径老是绕远路发愁不妨照着这个思路改造一下你的经验池和网络结构大概率会有惊喜。
返回列表