简介SAC-Auto路径规划与Soft Actor-Critic算法在激光雷达避障仿真中的应用是一套面向强化学习初学者、毕业设计及课程设计学生的完整代码资源融合SAC-PyTorch实现、Lidar仿真环境和静态/动态障碍物避障场景系统讲解从策略训练到路径规划部署的完整链路。资源压缩包共27个文件大小约12.97MB10个Python脚本覆盖环境构建、SAC智能体、Lidar仿真与训练/演示流程2个ONNX策略模型可直接加载运行7张PNG图片和2个GIF动图展示训练结果与仿真效果另有README说明与备份文件目录结构清晰方便按模块查阅。目前已有121人学习下载适合快速上手复现。核心训练与静态、动态演示脚本均可直接运行配合对应策略模型和可视化录屏能直观验证SAC在静态与动态障碍环境下的避障效果代码附带中文注释关键步骤有讲解项目经过调试可稳定运行功能完善、界面友好、操作简单。对需要完成期末大作业、课程设计或毕业设计的同学这套资源提供了从算法原理到工程落地的完整参考下载后即可按README指引复现实验也可在此基础上扩展改进具备实际应用价值。1. SAC-Auto 路径规划与 SAC 算法在激光雷达避障仿真里到底解决什么问题一个靠二维激光雷达做避障的仿真项目效果往往不是靠加高神经网络层数调出来的而是靠激光数据的预处理姿势和奖励函数的成本结构决定的。最早我把 SAC 算法直接接进机器人导航给它丢进上万个点云特征训练了一天也没看到可靠绕障行为后来把原始测距按扇形抽稀成 64 份配合相对目标的距离和夹角两个小时就看到了稳定绕障。SAC-Auto 并不是某个神秘新算法它是一条把 SAC 算法的熵温度自动调整、奖励缩放和雷达状态预处理串起来的自动化训练流水线目标是让路径规划模型在激光雷达避障仿真里真正落地。这篇文章照着这个方向把仿真环境怎么搭、SAC 参数怎么设、动作怎么接回路径规划、以及最容易翻车的坑走一遍适合正在做动态避障小车路径规划、想用强化学习替换传统局部规划器又不想在调参上耗几个月的工程师。2. 激光雷达避障仿真的环境与状态空间从点云到 SAC 输入的最短路径2.1 仿真器怎么选Gazebo 不是唯一解纯 2D 简化环境更利于 SAC 收敛做激光雷达避障仿真最常见的做法是直接上 Gazebo、ROS 和四轮小车模型在三维场景里挂一个激光雷达插件然后开始训练。这个方案的真实性好但落地成本也高SAC 算法属于样本效率比较靠中间地带的强化学习算法想要让一个带 360 度激光、动态障碍物、复杂地形的三维仿真收敛往往要跑到几百万步仿真速度就成了最大瓶颈。我一般会先从纯 2D 环境开始不急着把传感器做得很像。2D 环境里用二维平面地图加射线投射生成测距数据小车用简单的差速运动学模型写成前后轮约束重点模拟转向迟钝和半径限制而不是去模拟整车悬架、惯性张量这类细节。这样单步仿真时间能压在微秒级SAC 可以在几小时内跑完几十万步交互。选仿真器可以参考这张表先想清楚你要验证的是策略可行性还是某个型号激光雷达的机械特性仿真方案计算开销传感器保真度适合什么自写 2D 射线投射环境低中低但可快速渲染动态障碍物SAC 策略快速迭代、做仿真到真实的算法验证Gazebo 激光雷达插件中高高支持材质反射和多线扫描中后期需要验证特定雷达扫描特征时CoppeliaSim中中高做多传感器融合路径规划时如果你的目标雷达是速腾 16 线这类多线产品也先别急着在仿真里开 16 条线。2D 雷达避障的决策对象是水平面上的距离剖面多线数据在二维避障里通常做法是取某一层激光圈或者把所有线束投影到地平面上取最小距离。先把水平剖面做好多线数据的加工再放进一个输入适配层里而不是一上来就增加状态维度。2.2 激光点云降采样把上千个测距值压成 SAC 能消化的状态向量激光雷达一帧数据少则 360 个点多则 1080 个点。直接把这一串原始距离值拼给 SAC 的 Actor-Critic 网络理论上 MLP 也吃得下但训练效率会肉眼可见地变慢。原因有两层一是维度越高网络需要拟合的映射关系越复杂二是原始点云里远距离的空白区占了大量输入维度对避障决策几乎没有信息量。常见做法是把角度范围按扇形分箱抽稀譬如将 360 度平均分成 64 份每一份取该扇区内的最短距离。这样做保留了最关键的近距离障碍信息又把状态向量压到了适合 MLP 的长度。下面这段是我在项目里常用的处理函数可以直接作为 SAC 训练环境里的状态预处理模块import numpy as np # 把一帧激光测距数据压缩成固定维度的状态向量 NUM_BINS 64 # 扇形分箱数量也是状态维度的主要部分 MAX_RANGE 10.0 # 雷达有效量程超过这个值按无障碍处理 GOAL_RANGE 10.0 # 目标相对坐标的最大归一化参考值 def scan_to_state(scan_ranges, target_rel_pos, vel_linear): # scan_ranges: 原始测距数组可能是 360、1080 或任意可变长度 # 先将 NaN/inf 替换为最大量程避免把无效点当成贴着障碍物 valid np.nan_to_num( np.asarray(scan_ranges, dtypenp.float32), nanMAX_RANGE, posinfMAX_RANGE, neginf0.0 ) valid np.clip(valid, 0.0, MAX_RANGE) # 截断超量程和负值 # 均匀抽取 NUM_BINS 个采样索引相当于把 360 度分成 64 个扇区 indices np.linspace(0, len(valid) - 1, NUM_BINS, dtypeint) sampled valid[indices] / MAX_RANGE # 归一化到 0~1越小表示障碍越近 # 目标相对位置用极坐标展开距离和角度分别归一化 goal_dist np.linalg.norm(target_rel_pos) goal_angle np.arctan2(target_rel_pos[1], target_rel_pos[0]) goal_feat np.array([ np.clip(goal_dist / GOAL_RANGE, 0.0, 1.0), np.clip(goal_angle / np.pi, -1.0, 1.0) ], dtypenp.float32) # 当前线速度归一化SAC 需要知道小车当前是否在动 vel_feat np.clip(vel_linear / 2.0, -1.0, 1.0).astype(np.float32) return np.concatenate([sampled, goal_feat, vel_feat]).astype(np.float32)这段代码的核心逻辑是把雷达数据变成距离剖面而不是保留点云的坐标位置。NUM_BINS是第一个要调的参数64 份适合普通走廊和动态障碍场景90 度方向的分辨率大约是 5.6 度已经足够分辨左右两个相邻障碍物如果你的场景里障碍物特别密集比如窄巷子或者多 AGV 交汇区域可以提高到 96 或 128但不要轻易上 256状态维度变大之后 SAC 的探索会变得很慢。MAX_RANGE也很关键我习惯把它设为 10 米因为激光雷达远端的点簇噪声大而且对 2D 避障来说真正需要快速反应的障碍都在 5 米以内。归一化时把 10 米当作 1.03 米障碍就落在 0.3正好在 Actor 网络线性层最敏感的区域。2.3 奖励函数的三个旋钮到达、碰撞、姿态权重先给哪个把环境接口搭好就要解决 SAC 训练里最容易翻车的地方奖励函数。如果直接用稀疏奖励到达目标给一个 100碰撞给一个 -50SAC 在 2D 避障里也能学但收敛速度会让你怀疑算法是不是写错了。早期探索阶段小车几乎碰不到目标也没有碰撞每一轮的奖励都是 0策略梯度一点点往噪声里沉。我通常的做法是把奖励拆成三部分进度项、碰撞惩罚项、姿态惩罚项。进度项用当前离目标距离减去上一步离目标距离来衡量只要小车在往目标方向靠近就有正向反馈。姿态惩罚不是必须的但加了之后生成出来的轨迹会平滑很多不会出现原地反复打死方向盘那种情况。奖励函数长这样def reward_fn(obs, action, done, info): # info 里由环境提供上一帧距离、当前距离、碰撞和到达标志 if info[collision]: return -60.0 # 碰撞重罚数值远大于正常步进回报 if info[reached]: return 120.0 # 到达目标给出稀疏大奖励 progress info[last_dist] - info[cur_dist] # 正数表示靠近目标 turn_penalty -0.02 * abs(action[0]) # 转向幅度过大给轻惩罚 time_penalty -0.05 # 每步固定成本逼小车别原地转 return 5.0 * progress turn_penalty time_penalty这里turn_penalty和time_penalty的数值是血泪经验的产物。progress项系数给太小比如 1.0小车会为了减少时间惩罚而乱冲然后在墙角频繁碰撞给太大比如 20.0小车又会过度倾向朝目标直冲失去了绕障的余量。5.0这个值是我做过一组固定障碍物场景对比之后得到的中位数你可以把它当成起点而不是终点。碰撞惩罚 -60.0 和到达奖励 120.0 的比例也要注意。差距太大时SAC 会倾向于保守离障碍远远地绕路到达率反而下降差距太小小车又会为了抢进度奖励去贴身蹭墙。仿真里判断这个比例是否合理可以在训练了 2 万步之后人工检查几条轨迹看小车是在绕大圈还是贴边走。3. SAC-Auto 训练核心把 SAC 算法的熵温度与超参交给自动调谐3.1 SAC-Auto 到底“Auto”在哪熵温度、奖励缩放与回放采样SAC 算法的全称是 Soft Actor-Critic核心是最大熵强化学习它在标准回报之外再加一项策略熵让策略在追求高回报的同时保持足够的随机性。这个随机性由熵温度系数 α 控制α 越大策略越偏向探索α 越小策略越趋近于确定性贪心。但 α 怎么设是你拿 SAC 做仿真避障时遇到的第一个玄学问题。原始 SAC 论文里给出的方案是自动温度调节也叫双目标优化在训练过程中让 α 不断更新使得策略的平均熵逼近一个目标熵。目标熵一般取负的动作维度数比如动作空间是转向角和线速度两个维度目标熵就是 -2.0。SAC-Auto 在我这里的含义一是把 α 的自动调节真正跑通二是在这个基础上把奖励缩放、回放缓冲区采样比例也做成训练过程中的自动项而不是打开训练脚本以后才发现 lr 和奖励权重完全手工硬调。3.2 最小可复现的训练配置网络结构、batch、learning rate 一个不落先给出一份我跑 2D 激光雷达避障时验证过的默认配置后续调试都从这份表出发参数默认值说明状态维度6864 雷达 2 目标 1 速度对应上一章的 scan_to_state 输出动作维度2转向角、线速度连续动作空间单位分别是 rad 和 m/sActor 隐藏层[256, 256]MLP激活函数 ReLUCritic 隐藏层[256, 256]两个独立的 Q 网络Q1 和 Q2学习率3e-4Actor、Critic、α 三者都先用同一数值批量大小256每秒更新的 batch 大小回放缓冲区500_000存储过去交互样本太旧的数据没有训练价值折扣因子 gamma0.99长程回报权重软更新系数 tau0.005目标网络更新平滑度目标熵-2.0自动调节 α 的基准值训练步数300_0002D 环境下的参考收敛步数训练循环本身不复杂核心框架是先把样本存进回放池然后按批次更新两个 Q 网络再用 Q 值去指导 Actor 更新最后更新 α。下面这段是节选的训练伪代码重点在 SAC-Auto 自动调谐的三个位置# sac_auto_train_step.py # 每个训练步执行一次内部包含环境交互与梯度更新 def train_step(agent, env, buffer, total_step): # ---- 1. 环境交互用当前策略采样动作探索环境 state, _ env.reset() for t in range(env.max_episode_len): action agent.sample_action(state) # 训练阶段必须采样不是取均值 next_state, reward, done, info env.step(action) buffer.push(state, action, reward, next_state, done) if buffer.size agent.warmup_steps: # 过完预热步数才开始更新 update_once(agent, buffer) # ---- 2. Actor-Critic 与熵温度自动更新 def update_once(agent, buffer): s, a, r, s2, d buffer.sample(agent.batch_size) # Critic loss最小化 Q 值与目标 Q 值的 TD 误差 with torch.no_grad(): a2, logp_a2 agent.policy.sample(s2) q_target r agent.gamma * (1 - d) * ( torch.min(agent.q_target1(s2), agent.q_target2(s2)) - agent.alpha.detach() * logp_a2 ) q1_loss mse(agent.q1(s, a), q_target) q2_loss mse(agent.q2(s, a), q_target) # Actor loss最大化 Q 值 熵期望让策略往高回报方向更新 a_new, logp_new agent.policy.sample(s) actor_loss (agent.alpha.detach() * logp_new - torch.min(agent.q1(s, a_new), agent.q2(s, a_new))).mean() # 熵温度 α 自动更新目标熵设为 -动作维度 alpha_loss -(agent.alpha * (logp_new agent.target_entropy).detach()).mean() # 退避三个损失更新网络权重 agent.q_optimizers.zero_grad(); q1_loss.backward(); q2_loss.backward() agent.q_optimizers.step() agent.policy_optimizer.zero_grad(); actor_loss.backward() agent.policy_optimizer.step() agent.alpha_optimizer.zero_grad(); alpha_loss.backward() agent.alpha_optimizer.step() # 软更新目标网络 soft_update(agent.q_target1, agent.q1, agent.tau) soft_update(agent.q_target2, agent.q2, agent.tau)这里最容易忽视的是warmup_steps。SAC 刚启动时回放池是空的如果一开始就做批量更新网络拿不到足够多样本就会在优化早期剧烈震荡。我的习惯是至少攒 5000 步再开更新也就是小车在环境里随机游走大约 500 个回合。另外注意agent.sample_action和测试阶段用的deterministic_action是两个东西训练阶段不带随机性探索的话α 自动温度调节形同虚设。target_entropy也不是只能设 -2.0。动作维度越高、环境越复杂目标熵的绝对值要稍微放大一些比如无人机路径规划里控制量可能是四个维度的推力向量目标熵就要设成 -4.0 左右给策略更多发挥空间。多 AGV 路径规划强化学习里如果动作还包含等待决策熵目标也要按维度数重新调整不能沿用避障小车的配置。3.3 训练过程怎么看三个必须盯的指标训练脚本跑起来以后终端每秒都在刷日志但大多数指标不值得看。SAC-Auto 里我最关注的是平均每回合回报、策略熵、以及每 100 个回合的碰撞率与到达率。这三个指标配合起来才能判断训练是不是真的在收敛。平均回报和碰撞率都是计数器层面的统计需要隔一段时间打印一次我一般设置log_interval100个回合。策略熵看的是logp的平均值如果熵值一直往上飘并不下降说明策略一直在随机决策还没学到结构化行为反过来熵值快速跌到接近于 0说明策略过早陷入确定性后面遇到训练时没见过的动态障碍物时会明显反应不过来。有些训练日志里只有奖励曲线在涨但碰撞率居高不下这就是奖励函数里进度项权重过大的典型症状。小车发现直冲靠近目标获得的奖励远大于转向绕障的回报所以即使撞到障碍它也会反复尝试走这条近路。出现这个现象时先调奖励权重而不是调网络结构。4. 把 SAC-Auto 的输出接回路径规划动作空间、指令降频与安全滤波4.1 连续动作空间还是离散动作空间绕目标点还是给控制指令强化学习路径规划的实现层面第一个分叉是动作空间定义。SAC 默认支持连续动作这也是它在激光雷达避障仿真里比 DQN 那类离散算法更适合的原因。连续动作空间里最直观的定义是[转向角, 线速度]和实际底盘控制指令一一对应。另一个常见做法是定义成局部路径点偏移比如在小车前 2 米处生成一个目标点用转向角表示目标点相对航向的偏移速度单独输出。我倾向于直接用转向角加减速指令原因是避障仿真里最常见的地面小车控制频率是 10Hz 到 20HzSAC 的每一步决策直接落到速度给定量上逻辑链路最短。局部路径点偏移的方案更适合那种预测时域长、需要平滑曲率的路况比如泊车路径规划算法会考虑连续倒车和原地掉头这时候动作空间得追加里程和挡位信息复杂度就上去了。# action_to_cmd.py # 把 SAC 输出的归一化动作转换成底盘速度和控制指令 def transform_action(raw_action, max_steer0.6, max_speed1.5): steer float(raw_action[0]) * max_steer # 转向角映射到 [-0.6, 0.6] rad speed (float(raw_action[1]) 1.0) / 2.0 * max_speed # 线速度映射到 [0, 1.5] m/s speed max(0.0, min(speed, max_speed)) return {steer: steer, speed: speed}这里注意雷达避障小车不应该支持倒车。SAC 不会自动区分前进和后退哪个更安全你要是把速度下界设成负数策略很快就会学会原地倒车绕开障碍把路径规划变成原地扭动。我的经验是速度动作归一化到[0, max_speed]只在原地不敢走和冲撞临界点之间找平衡。4.2 策略输出到规划器的接线间歇决策、动作平滑与安全滤波SAC 训练好以后策略网络是纯前向推理不会有训练时那么多层随机采样。实际接路径规划系统时我会让 SAC 以 10Hz 频率输出动作同时在前端加两个业务层动作平滑层和安全滤波层。# sac_controller.py # 在实时控制循环里调用 SAC 策略并做平滑和安全校验 class SACController: def __init__(self, policy, action_smooth_ratio0.6, min_free_distance0.4): self.policy policy self.smooth_ratio action_smooth_ratio self.min_free min_free_distance self.last_action None def control_step(self, state, scan_ranges, goal_rel_pos, vel): # 1. 策略推理测试阶段用均值动作让输出稳定可复现 mean_action self.policy.deterministic_action(state) raw_action mean_action.cpu().numpy() # 2. 动作平滑一阶低通避免转向瞬间跳变 if self.last_action is not None: raw_action self.smooth_ratio * raw_action (1 - self.smooth_ratio) * self.last_action self.last_action raw_action # 3. 安全滤波前方最小距离小于阈值时强制降速 forward_min np.min(scan_ranges[len(scan_ranges)//2 - 10 : len(scan_ranges)//2 10]) if forward_min self.min_free: raw_action[1] min(raw_action[1], forward_min / self.min_free * 0.8) return raw_action动作平滑层的意义在于SAC 训练时使用采样动作策略输出天然带有随机成分如果不做平滑转向指令会高频抖动差速底盘的轮子会被磨得吱吱响。smooth_ratio取 0.6 时当前指令占六成、上一帧占四成既保留了实时响应又滤掉了高频跳变。安全滤波层其实是老工程师兜底的思路。就算 SAC-Auto 训练得再收敛也不可能覆盖所有 Corner case比如激光雷达被雨幕干扰或者仿真里没建模的低矮障碍突然出现在路中间。强制让车在正前方小于 0.4 米时降速是最后的后悔药。仿真里也建议保留这一层否则你验证出来的避障率在真机上会打很大折扣。4.3 全局路径怎么配合A* 负责宏观层SAC 负责微观避障SAC-Auto 做的是局部避障不应把全局可达性的任务也压给它。全局路径规划可以继续沿用 A*、Dijkstra 或 RRT 这类传统方案先在地图上生成一条从起点到终点的无碰撞粗路径再把这条路径的局部参考点交给 SAC 去跟随和动态避障。这种分层架构在动态避障小车路径规划里已经比较成熟也容易移植到多 AGV 路径规划强化学习的场景里。全局路径的跟随信号要以什么形式传进 SAC 的状态向量回到第 2 章的状态定义目标相对位姿target_rel_pos不需要是最终终点而是全局路径上的前瞻点取小车前方两到三米远的路径点即可。这样 SAC 不会只顾着避开当前障碍而彻底跑偏方向。状态向量里的目标点和奖励函数里的目标要保持一致。你如果在仿真环境里用最终终点做奖励修剪又给 SAC 喂前瞻点作为状态输入策略会学到一个错位的映射轨迹会绕着终点打转。这一点要严格对齐我在自己的仿真里把全局路径、局部目标点和奖励计算都集中在同一个get_goal_ref()接口里换场景时只改这一个函数。5. 激光雷达避障仿真的避坑清单6 个必踩的坑与排查路径5.1 原始点云直接当输入训练了十万步还是绕不过障碍现象是状态维度很高每次显示训练回报在缓慢上升但实际跑轨迹时小车永远撞墙。原因在于原始测距的远端空白区占据了大量输入维度MLP 很难在有限步数内从这些稀疏特征里提炼出近处障碍的结构。解决方式是按照第 2 章的scan_to_state抽稀到 64 或 128 维让输入信号集中在近距障碍上而不是把整张距离剖面图丢给网络。另一个排查点是观察状态向量里的sampled数值。打印几组极端场景的数据比如正前方 0.5 米有墙对应的扇区归一化值是否接近 0.05如果归一化逻辑写错比如除以了len(scan_ranges)那整个状态输入对碰撞都非常不敏感。5.2 奖励进度项权重给得太大小车贴着障碍墙走现象是小车能够到达目标但路径极其难看轨迹几乎贴墙甚至可以说是在墙上滑过去。原因是进度项在数值上远远盖过了碰撞惩罚SAC 发现贴着墙走能拿到更多靠近目标的进度奖励而碰撞惩罚没有真正把动作推开。解决方式是调大collision的惩罚绝对值或者把进度项乘一个距离衰减因子让小车接近目标后不再执着于微小进度而是把重心放在保持安全距离上。这个坑在仿真里特别容易隐藏因为仿真环境里墙面完美光滑、没有传感器误差贴墙误差累积也不会立刻造成碰撞。真机上一旦贴墙雷达反射点和运动学偏差叠加真实事故就出现了。所以看训练效果时一定要把平均离墙距离作为评估指标之一至少保持 0.3 米以上才算合格。5.3 SAC 温度 α 训练到后期乱跳策略失去了稳定性现象是训练前几万步回报稳步上升到了中段忽然出现回撤随后熵值曲线像拉链一样来回波动。原因多半是目标熵设得太大或太小或者学习率设置不适合当前环境的奖励尺度。比如目标熵设成 -0.5α 会持续压低策略过早确定性目标熵设成 -6.0α 又会向上狂涨探索得过猛连已经学会的绕障行为也被冲掉。排查路径是把 α 的日志画出来看它的量级是否在训练后段收敛到一个稳定范围。如果没有把 Actor 和 Critic 的学习率一起降到 1e-4 再重跑记住 α 的学习率不能独立调得特别高。SAC-Auto 的自动调温依赖稳定的梯度流动学习率的异常会让温度因子失去平衡。5.4 仿真里雷达数据太干净换到真实激光雷达之后避障失效现象是仿真里 SAC-Auto 的成功率刷到 95% 以上一旦拿到实机雷达数据测试碰撞率飙升。原因是仿真雷达没有模拟噪声和低反射率物体真实激光雷达的测距值在阳光下、黑色物体上都会跳变有时候甚至直接读出NaN或inf。第 2 章代码里我用nan_to_num把无效值替换成 MAX_RANGE就是为这个坑做的兜底。仿真往真实迁移时我一般会给雷达模拟器加三层噪声测距高斯噪声、随机丢点、以及黑色低反射区域的固定偏移。速腾 16 线这类多线雷达转成 2D 距离剖面时还要注意线束倾角对不同高度障碍物的取舍单层次回波和多回波模式选得不对低台阶可能直接被滤掉。5.5 状态没有历史帧窄巷道里策略原地打转现象是宽度不足两倍车宽的巷道里SAC 输出的转向角来回摆车速接近 0。原因在于当前帧状态只包含一帧距离剖面和一帧速度策略没有运动趋势信息分不清车是在直行还是正在转向就没法做前瞻性决策。解决方式有两种一种是在状态里拼接最近 3 到 5 帧的扫描剖面代价是状态维度翻倍另一种是干脆缩小当前帧的采样角度间隔让转向反馈更敏感。我偏向于拼接两帧而不是增加历史长度。两帧之间的差异基本可以代表障碍物的相对运动方向对动态障碍物避障也有效三帧以上在 2D 低速场景下边际收益非常有限反而增加了训练难度。如果你做的是无人机路径规划速度更快、动态性更强这条判断要重新评估。5.6 碰撞检测和雷达测距不一致出现穿模式避障现象是 SAC 在仿真里绕过了所有障碍但导航系统在局部代价地图上给出的路径仍然被规划器标记为不可通行。原因是环境的碰撞检测用圆半径近似激光雷达测距是精确的射线距离两者对同一障碍物的安全裕度不同导致强化学习环境认为安全的动作被下游规划器再过滤一次直接短路。解决方式是把碰撞半径和雷达扫描的有效车宽画在同一张图上验证。车宽 0.5 米、雷达安装位于中心时碰撞半径至少是 0.35 米小于这个值就会发生仿真中不碰撞、真机碰撞的尴尬。这个参数要把车轮外沿和雷达安装偏移量都算进去否则所谓避障仿真只是理论上不碰点真实底盘还是会被卡住。6. 把训练好的策略按验收脚本跑一遍成功率、平均时间与随机种子实验是关键模型训出来之后直接看 loss 曲线是不作数的要跑一套固定验收脚本。我的习惯是冻结策略权重切换成确定性推理在仿真环境里生成 200 个随机起点和随机目标点覆盖开阔地、单侧墙、窄道、动态障碍交叉四类典型场景统一记录三个指标到达率、平均用时、平均最小离墙距离。到达率低于 90% 就回去调奖励权重平均最小离墙距离低于 0.3 米就检查安全滤波阈值平均用时过长则观察动作平滑层的smooth_ratio是不是拖慢了响应。为了排除神经网络初始化带来的偶然性我会用三组不同的随机种子重新训练同一配置然后分别跑同一份验收测试集。SAC 不是完全可复现的算法熵探索和回放采样的随机性使得两次训练会得到略有差异的策略。三组种子中如果有一组成功率显著低于其他两组大概率是那一次训练陷入了局部极小值而不是环境或模型架构问题。检测时注意一个细节测试时的动作输出不要用采样动作直接取policy.deterministic_action或均值动作。采样动作会让相同状态下每次输出不一样验收数据全是噪声也就没法定位问题。最后一条习惯是在验收脚本里加一层雷达噪声开关单独跑一组有噪声版本如果噪声组和无噪声组到达率差距超过 10%说明策略对传感器噪声过度敏感需要回去在训练环境里补噪声样本。上次做动态避障小车路测时我在验收脚本里漏了动态障碍物这一项SAC-Auto 在仿真静态场景刷到漂亮的高分真机上一辆三轮车横穿路径就暴露了反应滞后。后来那条教训让我养成了验收场景必加随机动态障碍的习惯也是我把这条写进测试清单里的原因。希望帮到你。本文还有配套的精品资源点击获取