基于深度强化学习的双目标动态感知路径规划:Python源码实现与避障优化

📅 发布时间:2026/10/10 18:14:29
基于深度强化学习的双目标动态感知路径规划:Python源码实现与避障优化
简介这是一份面向计算机、人工智能及相关专业学生与开发者的深度强化学习路径规划项目源码针对犯罪风险与路径距离两个目标进行动态感知训练智能体生成兼顾安全与效率的最优路线推荐适合作为毕业设计、课程设计或算法进阶练习。压缩包共36个文件以29个Python源码为核心辅以2个pyc编译文件、2份md说明文档、1个txt、1个license及1个log日志整体约298KB目录中可见simulator、algorithm、tests等模块结构清晰便于按功能阅读。目前已有207人学习下载。代码经过完整测试运行成功答辩评审平均分达96分读者可据此掌握深度强化学习在动态路径规划中的建模思路、双目标奖励设计与仿真验证流程并在此基础上修改扩展实现其他功能下载后建议先阅读README.md仅供学习参考切勿用于商业用途。1. 双目标动态感知路径规划为什么单目标 DRL 在动态障碍前总是翻车做移动机器人或者 AGV 调度的人大概率踩过这个坑用深度强化学习DRL训出来的路径规划策略在静态栅格地图上跑得漂漂亮亮一旦场景里出现移动障碍物——尤其是那种速度方向会突变的动态障碍——智能体就开始画龙要么原地抖动要么一头撞上去。根子在于大多数开源实现只优化了一个目标路径最短或者到达最快。可真实场景里你既要路径短又要动态避障稳这两个目标在奖励函数里天然打架。基于深度强化学习的双目标动态感知路径规划方法核心就是把这俩目标拆开建模、联合优化让策略网络在每一步决策时同时权衡“走多远”和“离危险多近”。Python 源码层面常见做法是用双分支 Actor 输出动作均值与避障权重再配合动态感知模块处理障碍物运动预测。这套方案适合做动态避障小车、多 AGV 路径规划强化学习调度的工程师也适合想把无人机三维路径规划数学模型从 MATLAB 迁移到 Python 强化学习框架的人。下面按“原理选型 → 环境搭建 → 双目标实现 → 训练调参 → 避坑 → 进阶验证”的顺序拆开讲每一步都给可复现的代码和参数边界。2. 双目标 DRL 路径规划的原理与选型为什么不是简单加权2.1 双目标建模的两种主流路线把“路径短”和“避障稳”塞进一个标量奖励里做加权求和是最省事的做法也是最多人翻车的地方。权重调不好策略要么贴着障碍物边缘走要么绕大圈。更稳的路线是双目标分开建模常见有两种第一种是双 Critic 结构。两个 Q 网络分别估计“路径代价”和“碰撞风险代价”Actor 更新时用帕累托最优或约束优化方式融合两个 Critic 的梯度。这种结构在动态障碍速度变化快时表现更稳因为碰撞风险分支可以单独对动态障碍的运动趋势敏感。第二种是动作空间解耦。Actor 输出一个基础动作向量再输出一个避障增益系数实际执行动作是两者按动态感知模块给出的风险场加权。这种实现简单Python 源码里容易改适合从单目标 DRL 快速迁移。我一般会选第二种做原型因为调试直观避障增益系数直接反映策略对当前风险的重视程度训练日志里能一眼看出它有没有学会“该怂就怂”。等原型跑通再换双 Critic 做精细优化。选型时还要看动态感知模块的输入形式。如果障碍物状态是完整可观测的位置、速度、朝向直接用全连接层编码即可如果只有局部激光或深度图就得加卷积或注意力模块。热搜里“动态避障小车路径规划”和“多 AGV 路径规划强化学习”这两个场景前者通常用局部观测后者常用全局状态加通信选型时要分开对待。2.2 动态感知模块的最小实现动态感知不是把障碍物坐标喂进去就完事。关键是让网络理解障碍物的运动趋势。最小实现是给每个障碍物构造一个状态向量相对位置、相对速度、预测未来 T 步的轨迹点。T 一般取 3 到 5太少学不到趋势太多引入噪声。下面是一个动态障碍物状态编码的 Python 片段可直接嵌入你的环境封装import numpy as np def encode_dynamic_obstacles(obstacles, agent_pos, pred_steps3): obstacles: list of dict, each with pos (x,y) and vel (vx,vy) agent_pos: (x,y) current agent position pred_steps: number of future steps to predict returns: flat feature vector features [] for obs in obstacles: rel_pos np.array(obs[pos]) - np.array(agent_pos) rel_vel np.array(obs[vel]) # 预测未来轨迹点线性外推实际可换卡尔曼或LSTM pred_traj [rel_pos rel_vel * (t 1) for t in range(pred_steps)] # 拼接相对位置(2) 相对速度(2) 预测轨迹(2*pred_steps) feat np.concatenate([rel_pos, rel_vel] pred_traj) features.append(feat) # 按距离排序只保留最近的 N 个障碍物N5 是常见做法 features sorted(features, keylambda x: np.linalg.norm(x[:2]))[:5] # 不足 5 个用零填充保证输入维度固定 while len(features) 5: features.append(np.zeros(2 2 2 * pred_steps)) return np.concatenate(features)逻辑说明先算相对位置和相对速度再线性外推预测轨迹。参数pred_steps控制预测视野3 步在 10Hz 控制频率下约等于 0.3 秒前瞻足够应对大多数室内动态障碍。N5是经验值障碍物多于 5 个时只保留最近的避免输入维度爆炸。零填充保证网络输入固定这是 PyTorch 里做 batch 训练的前提。提示线性外推只适合匀速障碍。如果障碍物会突然转向把预测模块换成一个小型 LSTM 或卡尔曼滤波输入历史 5 帧位置输出未来 3 步预测效果会明显提升。2.3 双目标奖励函数的设计与参数边界奖励函数是双目标 DRL 的灵魂。我一般拆成三项到达奖励、路径代价、碰撞惩罚。到达奖励给稀疏大值路径代价每步给小的负值碰撞惩罚给大的负值并终止回合。def compute_reward(agent_pos, goal_pos, obstacles, collision, step_count): reward 0.0 # 到达奖励 if np.linalg.norm(agent_pos - goal_pos) 0.2: reward 100.0 done True else: done False # 路径代价每步负值鼓励短路径 reward - 0.1 # 动态避障惩罚距离障碍物越近惩罚越大 min_dist min([np.linalg.norm(agent_pos - obs[pos]) for obs in obstacles] or [10.0]) if min_dist 1.0: reward - (1.0 - min_dist) * 5.0 # 距离小于1米开始惩罚 # 碰撞终止 if collision: reward - 50.0 done True # 超时终止 if step_count 500: done True return reward, done参数说明到达奖励 100 和碰撞惩罚 -50 的比例很关键。碰撞惩罚太小策略会冒险穿障碍太大策略会过度保守绕远路。我一般让碰撞惩罚绝对值是到达奖励的 0.5 到 1 倍再根据训练曲线微调。路径代价 -0.1 每步500 步上限就是 -50和碰撞惩罚同量级这样策略不会为了省几步而冒险。避障惩罚的 5.0 系数控制风险敏感度动态障碍多时调到 8.0 到 10.0。注意奖励函数里不要同时用“距离目标越近奖励越大”的稠密引导和“到达给大奖励”的稀疏信号两者叠加容易让策略学会在目标附近绕圈刷分。要么纯稀疏要么稠密引导但到达奖励只给一次。3. 用 Python 搭一套可训练的双目标 DRL 路径规划环境3.1 环境封装从栅格地图到动态障碍训练环境建议基于 Gym 接口封装方便复用 Stable-Baselines3 或自己写 PPO。核心状态包括智能体位置、目标位置、静态障碍物占用图、动态障碍物编码向量。动作空间用连续二维速度或离散八方向连续动作更平滑但训练慢离散动作容易收敛但路径有锯齿。import gym from gym import spaces import numpy as np class DynamicPathEnv(gym.Env): def __init__(self, grid_size20, num_dynamic_obs3): super().__init__() self.grid_size grid_size self.num_dynamic_obs num_dynamic_obs # 动作连续二维速度范围 [-1, 1] self.action_space spaces.Box(low-1, high1, shape(2,), dtypenp.float32) # 状态智能体(2) 目标(2) 静态图(400) 动态障碍(5*10) self.observation_space spaces.Box(low-np.inf, highnp.inf, shape(2240050,), dtypenp.float32) self.reset() def reset(self): self.agent_pos np.array([1.0, 1.0]) self.goal_pos np.array([self.grid_size-2, self.grid_size-2]) self.static_map np.zeros((self.grid_size, self.grid_size)) # 随机放静态障碍 for _ in range(20): x, y np.random.randint(0, self.grid_size, 2) self.static_map[x, y] 1 # 动态障碍随机位置和速度 self.dynamic_obs [] for _ in range(self.num_dynamic_obs): pos np.random.uniform(5, self.grid_size-5, 2) vel np.random.uniform(-0.5, 0.5, 2) self.dynamic_obs.append({pos: pos, vel: vel}) self.step_count 0 return self._get_obs() def _get_obs(self): obs np.concatenate([ self.agent_pos, self.goal_pos, self.static_map.flatten(), encode_dynamic_obstacles(self.dynamic_obs, self.agent_pos) ]) return obs.astype(np.float32) def step(self, action): self.step_count 1 # 更新智能体位置 self.agent_pos action * 0.5 # 0.5 是步长缩放 self.agent_pos np.clip(self.agent_pos, 0, self.grid_size-1) # 更新动态障碍 for obs in self.dynamic_obs: obs[pos] obs[vel] * 0.1 # 边界反弹 for i in range(2): if obs[pos][i] 0 or obs[pos][i] self.grid_size-1: obs[vel][i] * -1 obs[pos][i] np.clip(obs[pos][i], 0, self.grid_size-1) # 碰撞检测 collision False for obs in self.dynamic_obs: if np.linalg.norm(self.agent_pos - obs[pos]) 0.5: collision True reward, done compute_reward(self.agent_pos, self.goal_pos, self.dynamic_obs, collision, self.step_count) return self._get_obs(), reward, done, {}逻辑说明_get_obs把静态图和动态障碍编码拼成固定长度向量。step里动作乘 0.5 是步长缩放防止智能体一步跳太远穿过障碍。动态障碍速度乘 0.1 是时间步缩放让障碍物移动速度与智能体匹配。碰撞阈值 0.5 是智能体和障碍物的半径和实际部署时按机器人尺寸改。参数说明grid_size20对应 20x20 米室内场景num_dynamic_obs3是中等难度训练初期可以降到 1 到 2 个。静态障碍 20 个约占 5% 占用率太密会堵死路径太疏学不到避障。3.2 双分支 Actor 网络的 PyTorch 实现双目标的核心在网络结构。我一般用共享特征提取层加两个输出头一个输出动作均值一个输出避障增益。避障增益经过 Sigmoid 限制在 0 到 1乘到动作上。import torch import torch.nn as nn class DualObjectiveActor(nn.Module): def __init__(self, obs_dim, action_dim): super().__init__() self.shared nn.Sequential( nn.Linear(obs_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), ) # 动作均值头 self.action_head nn.Linear(256, action_dim) # 避障增益头 self.gain_head nn.Sequential( nn.Linear(256, 64), nn.ReLU(), nn.Linear(64, 1), nn.Sigmoid() ) # 动作标准差可学习 self.log_std nn.Parameter(torch.zeros(action_dim)) def forward(self, obs): feat self.shared(obs) action_mean torch.tanh(self.action_head(feat)) avoid_gain self.gain_head(feat) # 实际动作 动作均值 * 避障增益 action action_mean * avoid_gain std torch.exp(self.log_std) return action, std, avoid_gain逻辑说明共享层提取状态特征动作头输出基础动作方向增益头输出对当前风险的缩放系数。tanh把动作限制在 -1 到 1Sigmoid把增益限制在 0 到 1。实际动作是两者相乘增益接近 0 时智能体减速或停止接近 1 时正常移动。训练时可以把增益值打印出来观察如果一直接近 0说明避障惩罚太重策略学会了“原地不动保平安”。参数说明隐藏层 256 是中等规模obs_dim 约 454 时参数量约 30 万单卡训练足够。log_std初始化为 0 对应标准差 1探索范围适中。如果训练初期动作抖动太大把log_std初始化为 -1。3.3 PPO 训练循环与关键超参用 PPO 训练这套网络核心超参如下表超参推荐值作用调整方向学习率3e-4网络更新步长训练不稳降到 1e-4折扣因子 γ0.99未来奖励权重动态障碍快时降到 0.95GAE λ0.95优势估计偏差方差权衡一般不动裁剪系数 ε0.2PPO 策略更新幅度训练震荡降到 0.1回合数10每批数据更新次数过拟合时降到 5批大小64每次更新样本数显存够就加大熵系数0.01鼓励探索收敛太快时加到 0.05训练循环骨架import torch.optim as optim actor DualObjectiveActor(obs_dim454, action_dim2) optimizer optim.Adam(actor.parameters(), lr3e-4) for episode in range(5000): obs env.reset() log_probs, rewards, values [], [], [] done False while not done: obs_tensor torch.FloatTensor(obs).unsqueeze(0) action, std, gain actor(obs_tensor) dist torch.distributions.Normal(action, std) act dist.sample() log_prob dist.log_prob(act).sum() obs, reward, done, _ env.step(act.detach().numpy()[0]) log_probs.append(log_prob) rewards.append(reward) # PPO 更新省略按标准实现即可 # 每 100 回合打印平均奖励和平均避障增益 if episode % 100 0: print(fEpisode {episode}, Avg Reward: {np.mean(rewards):.2f}, Avg Gain: {gain.mean().item():.3f})逻辑说明每回合收集轨迹PPO 更新时用裁剪目标函数。打印平均避障增益是关键调试手段正常训练时它应该从初始的 0.5 左右逐渐分化遇到障碍时降到 0.2 到 0.3空旷时升到 0.8 以上。如果一直卡在 0.5 不动说明增益头没学到东西检查避障惩罚是否太弱。参数说明5000 回合是中等难度场景的收敛量级简单场景 2000 回合够复杂动态障碍可能要 10000 回合。每 100 回合打印一次观察奖励曲线是否稳定上升。4. 训练调参与动态感知的坑避障增益不收敛怎么办4.1 避障增益饱和在 0 或 1现象训练日志里Avg Gain一直接近 0 或一直接近 1策略要么原地不动要么无视障碍。原因奖励函数里避障惩罚和路径代价的比例失衡。惩罚太重增益头学会输出 0 来避免任何风险惩罚太轻增益头学会输出 1 来跑最短路径。解决把避障惩罚系数从 5.0 开始每 500 回合观察一次碰撞率和到达率。碰撞率高于 20% 就加大惩罚到达率低于 50% 就减小惩罚。目标是把碰撞率压到 5% 以下同时到达率保持在 80% 以上。4.2 动态障碍预测滞后导致“鬼探头”碰撞现象智能体在障碍物直线运动时能避开但障碍物突然转向时撞上。原因线性外推预测假设匀速转向时预测轨迹偏离实际。动态感知模块输入只有当前帧没有历史信息。解决把预测模块换成 2 层 LSTM输入最近 5 帧障碍物位置输出未来 3 步预测。LSTM 隐藏层 64 维足够。训练时 LSTM 和 Actor 联合训练学习率降到 1e-4 防止梯度爆炸。4.3 静态障碍和动态障碍编码混在一起现象智能体在静态障碍多的场景里表现正常加入动态障碍后静态避障也变差。原因静态图展平后是 400 维动态障碍编码是 50 维共享层被静态图主导动态特征被淹没。解决给静态图和动态障碍分别过一层全连接降到 64 维再拼接送入共享层。或者用注意力机制让网络自己学习关注哪部分。我一般用前者改动小效果立竿见影。4.4 训练环境随机性太大导致策略方差高现象每次训练结果差异大有时收敛有时不收敛。原因动态障碍初始位置和速度完全随机某些回合难度过高梯度噪声大。解决课程学习。前 1000 回合只用 1 个动态障碍且速度范围 [-0.2, 0.2]1000 到 3000 回合加到 2 个障碍速度 [-0.5, 0.5]3000 回合后加到 3 个障碍。这样策略逐步适应收敛稳定。4.5 奖励稀疏导致前期学不到东西现象前 500 回合奖励一直是负的智能体随机游走。原因到达奖励 100 太稀疏随机策略很难碰巧到达目标。解决加距离引导奖励每步给-0.01 * 距离目标距离让智能体有方向感。但到达奖励要相应降到 50防止绕圈刷分。等到达率超过 50% 后把距离引导去掉只用稀疏到达奖励做精细优化。5. 进阶用课程学习加双 Critic 把碰撞率压到 2% 以下前面说的双分支 Actor 加避障增益在中等难度场景能把碰撞率压到 5% 左右。如果要进一步压到 2% 以下我一般上双 Critic 加课程学习。双 Critic 分别估计路径代价和碰撞风险Actor 更新时用拉格朗日乘子法把碰撞风险约束在阈值内。具体做法是Critic1 输出路径长度估计Critic2 输出碰撞概率估计Actor 的损失函数是路径损失 λ * max(0, 碰撞概率 - 0.02)λ 随训练动态调整。这样策略会在碰撞概率接近 2% 时自动保守低于阈值时正常优化路径。课程学习方面我习惯按动态障碍数量和速度分四个阶段阶段回合范围动态障碍数速度范围目标碰撞率10-10001[-0.2, 0.2] 10%21000-30002[-0.4, 0.4] 5%33000-60003[-0.6, 0.6] 3%46000-100004[-0.8, 0.8] 2%每个阶段结束时评估 100 回合碰撞率达标才进入下一阶段不达标就延长当前阶段 500 回合。这套流程跑下来在 20x20 米室内场景、4 个动态障碍、速度 0.8 米/秒的条件下碰撞率能稳定在 1.5% 到 2% 之间到达率保持在 85% 以上。验证方法上除了看训练日志我还会做三组测试第一组固定随机种子跑 100 回合看碰撞率和到达率的均值和方差第二组把动态障碍速度提高到训练时的 1.5 倍看策略泛化能力第三组在场景里加两个突然出现的障碍物模拟“鬼探头”看策略的紧急避障反应。三组都达标才认为策略可以往实车迁移。最后说个血泪经验别在训练环境里把动态障碍的运动模式写得太单一。我早期版本里障碍物只会匀速直线加边界反弹训出来的策略在实车上遇到会绕圈的障碍物直接傻眼。后来在环境里加了三种运动模式——匀速直线、正弦摆动、随机转向——各占三分之一概率策略的泛化能力明显提升。这个改动只花了半小时但省了我两周的实车调试时间。希望帮到你。本文还有配套的精品资源点击获取