实验条件方向传感器故障——机器人对自己的朝向感知永远是北。最终结果通关率 100%平均步数从 370 降到 89自动评测满分 50/50。具身智能状态估计路径规划BFSPOMDP目录1. 先看结果2. 实验设定坏掉的传感器3. 我的解决方案总览五个机制4. 机制一航位推算5. 机制二记忆地图6. 机制三BFS 最短路 三级目标优先级7. 机制四探索预算8. 踩坑记录六个问题与解决过程9. 深层算法这套玩具背后站着什么10. 复现方式11. 结语1. 先看结果指标醉汉 baseline优化后提升平均步数37089快 4.2 倍金币收集率58%97%1.7 倍通关成功率8/15 (53%)15/15 (100%)1.9 倍逐图明细每张图跑 3 次策略确定性3 次结果完全相同地图baseline 步数 / 金币 / 通关优化后 步数 / 金币 / 通关地图 1 新手村370 / 67% / 1÷381 / 3÷3 / 3÷3地图 2 小径319 / 75% / 2÷394 / 4÷4 / 3÷3地图 3 花园408 / 73% / 2÷390 / 5÷5 / 3÷3地图 4 迷宫入口333 / 50% / 2÷390 / 6÷6 / 3÷3地图 5 挑战迷宫419 / 24% / 1÷392 / 6÷6 / 3÷3① 状态估计 —— 我在哪、朝向哪本实验用坐标位移反推朝向 进阶扩展卡尔曼滤波、粒子滤波、SLAM② 世界建模 —— 环境长什么样本实验字典存二值地形空地/墙/金币 进阶占据栅格 log-odds 概率更新③ 路径规划 —— 怎么走过去本实验BFS 最短路每步重规划 进阶A*、D* Lite、带转向代价的状态栅格④ 目标决策 —— 现在该干什么本实验金币优先级 预算内探索 终点 进阶信息增益/距离启发式、POMDP 策略图 1 具身智能算法栈的四个层次以及本实验与工业界做法的对应关系2. 实验设定坏掉的传感器GridBot 是一个 10×10 网格世界的教学实验。机器人每步能拿到四周感知front / left / right / back取值是空地 / 墙壁 / 金币 / 终点 / 边界自己的坐标position以及终点坐标target公开信息记忆空间memory跨步持久的列表可读可写每步返回一个动作前进 / 左转 / 右转 / 后转。Baseline 是醉汉策略——随机走撞墙就随机转身平均 350 步通关、成功率 60%。而我的实验条件更狠方向传感器坏了perception[direction]永远返回北。 偏偏所有感知都是相对自身朝向的——朝向错了我看到的东西在地图哪一格就算不出来 建图、寻路、捡金币全部无从谈起。一句话概括这个实验的难点信息缺失 信息不可信只能用行动—反馈把丢掉的信息重新算出来。3. 我的解决方案总览五个机制先说全貌。整套方案就是五个机制叠起来每个机制解决一个具体问题#机制解决什么问题关键代码为什么有效1航位推算不知道自己的朝向坐标位移反推 转向叠加位移是绝对观测无法被故障传感器污染2开局校准第一步之前朝向未知先走一步走不动就右转一次移动就能反推出朝向成本 ≤ 4 步3记忆地图不知道环境长什么样相对感知 → 绝对坐标地图只增不减重复观测互相印证4BFS 三级决策不知道该往哪走金币 探索 终点每步都是最短路目标单调推进不会死循环5绕开终点格提前通关导致金币丢失BFS 的avoid_target终点是吸收态必须当障碍而非通路下面逐条拆开讲包括代码、为什么这么写、以及它成立的前提。4. 机制一航位推算 —— 用动作和坐标把朝向算回来4.1 思路方向传感器失灵但坐标传感器是好的。而我自己发的每一条指令我自己都记得于是 前进之后坐标变化了多少那个变化量的方向就是我的真实朝向。(0, -1) → 北 (1, 0) → 东 (0, 1) → 南 (-1, 0) → 西四种位移唯一对应四个绝对朝向没有歧义。这不是猜测是引擎给出的坐标事实。转向则更简单转向时位置不变但左转/右转/后转是我自己发出的指令必然执行成功 所以在已知朝向上叠加 ±90°/180° 即可撞墙没动朝向也不变。动作 u_t前进 / 左转 / 右转 / 后转自己发出的指令必然被执行成功预测θ̂_t θ_t−1 ⊕ u_t转向可精确叠加 ±90° / 180°所以预测无噪声观测位移 Δx 位置_t − 位置_t−1坐标传感器每步给出绝对位置无累积漂移校正θ_t 由 Δx 唯一确定(0,1)→南 (1,0)→东 (0,−1)→北 (−1,0)→西每步循环图 2 朝向估计的预测—校正闭环贝叶斯滤波的退化情形4.2 代码# 航位推算用上一步发生了什么更新真实朝向 last_pos state[0] 500if last_pos 500is 500not 500None 500and pos ! last_pos: # 上一步前进且真的移动了 → 位移方向 真实朝向 moved (pos[0] - last_pos[0], pos[1] - last_pos[1]) 500for d, delta 500in DELTA.items(): 500if delta moved: state[0] d state[0] 500True 500break 500elif state[0] 500in TURN_DELTA 500and state[1]: # 上一步执行了转向 → 在已知朝向上叠加转角 state[0] (state[1] TURN_DELTA[state[2]]) % 4 state[0] pos三条分支覆盖全部情形动了 → 反推转了 → 叠加撞墙没动 → 不变。4.3 为什么它不会累积漂移观测类型误差行为轮式里程计真机器人增量观测这一小步走了多远、转了多少度微小误差逐步累加位置误差 O(t)、朝向误差 O(√t) 发散本实验的position绝对观测我在 (x, y)是真值误差每步被观测直接清零不存在累积所以我这里其实不是估计而是解方程朝向是 4 个离散值之一 观测位移方向能把候选一次性收敛到唯一答案。 唯一的代价是没移动的那些步朝向无法被观测校正——正好由转向自己记 撞墙不变补齐。4.4 开局校准解决第一步之前朝向未知500if 500not state[0]: 500if perception[0] 500not 500in (1, 2): 500return 0 # 能走就走一步一动就校准成功 500return 0 # 走不动就换个方向再试代价上界最坏情况原地转 3 次找到通路第 4 步必然校准成功。 换来的是后面所有建图都建立在已证实的朝向上——否则错一步整张地图的坐标就全歪了。5. 机制二记忆地图 —— 把局部感知贴到全局坐标500for rel 500in range(4): abs_dir (heading rel) % 4 # 相对方向 → 绝对方向 dx, dy DELTA[abs_dir] cell_pos (pos[0] dx, pos[1] dy) # 贴到全局坐标 cell perception[REL_CELL[rel]] worldmap[cell_pos] 0 500if cell 1 500else cell三个设计决定每个都有原因地图存在memory[0]里字典{(x, y): 地形名}。memory是引擎给的跨步持久空间memory[0]放状态字典后续每步取出复用。地图外一律记成墙壁。后面的搜索天然不会跑出地图不用在每个算法里重复做边界判断。站上金币格要把它从地图里消掉。金币被捡走后若还当目标 就会陷入到那儿没东西 → 再去别处 → 又回来的死循环。地图必须与真实世界同步。6. 机制三BFS 最短路 三级目标优先级500def bfs(goal_test, avoid_target500False): queue deque([pos]); prev {pos: 500None} 500while queue: cur queue.popleft() 500if cur ! pos 500and goal_test(cur): path [] 500while cur ! pos: path.append(cur); cur prev[cur] 500return path[::-1] 500for dx, dy 500in DELTA.values(): nxt (cur[0] dx, cur[1] dy) 500if nxt 500in prev 500or 500not in_grid(nxt): 500continue 500if avoid_target 500and nxt target: # 别从终点身上踩过去 500continue 500if worldmap.get(nxt) 0: # 已知墙不撞 500continue prev[nxt] cur; queue.append(nxt) # 未知格子也允许走 探索能力 500return 500None优先级① 有已知金币 → 去最近的那个路上不许穿过终点 ② 没有已知金币且步数 75 → 去最近的探索前沿已知通路旁还有未知格子的位置 并在最短的若干条候选里优先挑第一步不用转身的转身也算一步 ③ 预算用完或没有前沿可探 → 直奔终点。为什么绝不撞墙BFS 只把非墙壁的格子入队而相邻格子必然已被感知记录过每步记录四周 4 格所以路径第一步永远是已知可通行格。为什么不会死循环每步都在缩短到当前目标的最短路距离而目标集合金币 → 前沿 → 终点是单调消耗的。为什么要avoid_target踩上终点是吸收态游戏立即结束。不把终点当目标和不从终点经过是两件事——后者我一开始漏了见第 8 节坑 4。7. 机制四探索预算 —— 一个真实的取舍问题探索越久金币越多但步数也越多评测规则要求步数 100才拿满分 20 分。 所以这本质上是带约束的最优化问题在步数 100 的约束下最大化金币收集率。探索预算平均步数金币率总分708389%50/50758997%50/50859797%50/5095 以上≥10097%45/5075 步是金币已经吃满里步数最少的一档离 100 步红线还有 11 步余量。 再往上加金币已经涨不动原因见坑 6步数却会突破 100 掉分。8. 踩坑记录六个问题与解决过程整个实验我一共栽了六次按现象 → 排查 → 根因 → 修复记下来。先看总览#问题根因修复1完全不知道自己的朝向direction永远返回北是废数据航位推算 开局校准2机器人卡在角落来回抖动前沿判定把地图外的格子也算成未知加地图范围检查3探索到一半就提前通关终点格也被当成了探索目标探索目标判定中排除终点4修完坑 3还是提前通关路径从终点格上穿过去路过也算踩上BFS 加avoid_target参数5金币与步数此消彼长探索预算固定两项评分互相拉扯扫参定在 75 步6金币率怎么调都卡在 97%地图 5 声明 7 个金币实际只有 6 个格确认是地图数据的天花板坑 1朝向完全不可知 —— 整个实验的出发点现象perception[direction]永远返回北。四个方向的感知都是相对自身朝向的 朝向错了就意味着我看到的东西在地图哪一格完全算不出来。排查确认这是实验条件强制的传感器故障。盘一遍还有哪些信息可信 四周格子可信、自己的坐标position可信、终点坐标公开、记忆空间可写。根因传感器坏了但我做过什么动作这件事我永远知道——因为动作就是我发出的。修复航位推算位移反推 转向叠加 开局校准见第 4 节。坑 2机器人卡在地图角落来回抖动现象步数暴涨但地图格子数停在 37 不再增长机器人在(9,0)和(9,1)之间反复横跳。排查打印每步的可探索前沿列表发现右上角靠边的格子永远在列表里—— 它旁边的未知格子是地图外面的坐标。根因前沿判定把地图外的格子也算成了未知区域靠边的格子永远满足条件BFS 一直把它当最近目标。修复500def frontier_goal(c): cx, cy c 500return any( 0 cx dx GRID_SIZE 500and 0 cy dy GRID_SIZE 500and (cx dx, cy dy) 500not 500in worldmap 500for dx, dy 500in DELTA.values() )坑 3机器人探索到一半就提前通关了现象地图 3 只走 43 步就结束5 个金币只捡到 2 个。疑点预算才用 43/75地图还有一大片没探。排查日志显示第 40 步机器人在(9,6)地图已有 68 格、还有 10 个前沿没去 但下一步就朝(9,7) → (9,8) → (9,9)走过去了——那是终点。根因终点格(9,9)在还没探索过它周围时也满足旁边还有未知格子 被当成普通探索目标而它恰好最近。踩终点不可逆。修复探索目标判定里排除终点把探索和去终点彻底分开500def frontier_goal(c): 500if c target: # 终点不算探索点 500return 500False修完地图 3 从 43 步 / 2 金币变成 90 步 / 5 金币全捡。坑 4修完坑 3还是提前通关现象地图 2 仍然只跑 38 步、4 个金币只捡 2 个。排查第 35 步机器人在(9,7)还有 14 个探索点没去、预算才用一半 但它连着三步(9,7) → (9,8) → (9,9)又踩上了终点。根因它的目标是探索点(9,8)合法但最短路径正好从终点格上穿过去。 不把终点当目标和不从终点身上路过是两件事。修复500if avoid_target 500and nxt target: 500continue # 别从终点身上踩过去修完地图 2 从 2/4 金币变成4/4 全捡。坑 5金币与步数此消彼长怎么调都有短板现象一开始拍了个 80 步金币 87%、步数 81修完绕行问题后同样 80 步步数涨到 93余量只剩 7 步。根因两项指标存在真实冲突——金币靠探索换来探索必然消耗步数且金币率有上限坑 6触顶后再加预算就是纯亏步数。修复扫参后定在75 步见第 7 节。坑 6金币率怎么调都卡在 97%现象预算加到 120 步地图 5 依然只有6/7。排查日志显示地图 5 已无可探索前沿整张图探完。于是去数地图数据本身500from maps 500import ALL_MAPS 500for m 500in ALL_MAPS: actual sum(1 500for row 500in m[0] 500for c 500in row 500if c 2) print(m[0], 1, m[2], 3, actual)地图 1 — 新手村: 声明金币3 实际3 差异0 地图 2 — 小径: 声明金币4 实际4 差异0 地图 3 — 花园: 声明金币5 实际5 差异0 地图 4 — 迷宫入口: 声明金币6 实际6 差异0 地图 5 — 挑战迷宫: 声明金币7 实际6 差异1 ← 问题在这根因MAP_5的元数据coins: 7与网格里真实金币格数量不一致。 地图 5 上限就是6/7全局天花板 (34566) / (34567)96%评测按每图比率取平均显示 97%。修复无需改代码但要确认它不是策略缺陷——地图上真实存在的金币我的机器人 100% 全部捡到了。 教训分数上不去时先分清我的算法不行还是数据本身有问题。9. 深层算法这套玩具背后站着什么9.1 状态估计我写了一个退化版的贝叶斯滤波预测bel⁻(θ_t) ∫ p(θ_t | u_t, θ_{t-1}) · bel(θ_{t-1}) dθ_{t-1} 校正bel(θ_t) ∝ p(x_t | θ_t) · bel⁻(θ_t)我的场景是它的退化情形状态只有 4 个离散值、运动确定性、观测无噪声 后验概率直接塌缩成一个确定值——不是估计是解方程。真实机器人有三代方法① 扩展卡尔曼滤波EKF状态x (x, y, θ)ᵀ信念 N(μ, Σ) 预测μ⁻ f(μ, u) Σ⁻ F Σ Fᵀ Q F ∂f/∂x |μ 校正K Σ⁻ Hᵀ (H Σ⁻ Hᵀ R)⁻¹ H ∂h/∂x |μ⁻ μ μ⁻ K (z − h(μ⁻)) Σ (I − K H) Σ⁻直观理解Q是我对自己动作有多不信R是我对传感器有多不信 卡尔曼增益K就是在两个不信任度之间做加权平均。② 粒子滤波Particle Filter初始化采样 N 个粒子 {x_i}权重 1/N 循环 1. 预测每个粒子按运动模型采样 x_i ← f(x_i, u) 噪声 2. 加权w_i ∝ p(z | x_i) 谁0观测谁就更重 3. 重采样按权重有放回地重抽 N 个粒子权重大的被复制小的被淘汰杀手级场景是全局定位信念可能同时有几十个峰几条长得一样的走廊高斯分布表示不了粒子群可以。③ SLAM当位姿和地图互相依赖不知道自己在哪 → 地图画歪 → 定位更不准必须联合估计位姿与地图。 EKF-SLAM 把地图特征点拼进状态向量协方差随特征数平方增长现代方案是图优化 把位姿与观测建成图用最小二乘整体求解。本实验若换成坐标定位故障条件问题立刻退化到这个难度的下限版本。9.2 建图从二值字典到占据栅格L(m_i) ← L(m_i) log[ p(m_i | z_t) / (1 − p(m_i | z_t)) ] − L_0 其中 L log[ p / (1 − p) ]数值稳定性概率连乘会下溢到 0log-odds 把乘法变加法可累积同一格被反复观测每次加一个证据项置信度自然增长需要逆观测模型p(m_i | z_t)传感器只说这条激光被挡住了 要从射线打到了东西反推沿途格子大概是空的、终点那格大概有障碍。9.3 路径规划BFS 只是这条链的起点算法关键思想复杂度适用场景BFS我的实现无权图逐层扩散O(V E)格子等权、小地图Dijkstra带权图按已用代价出队O((VE) log V)地形代价不同A*f g h启发式引导通常远快于 Dijkstrah 可采纳时才保证最优D* Lite环境变化后增量修复路径比重算便宜一个量级边走边发现障碍状态栅格 / Hybrid A*状态扩成(x, y, θ)状态数 ×4 起步真车不能原地转必须走圆弧关于 A* 的h四邻接网格里取曼哈顿距离是可采纳的实际最短步数不可能小于它 所以 A* 既搜得快、又保证最优。BFS 其实就是h ≡ 0 的 A*Dijkstra 是h ≡ 0 的带权版。一个诚实的瑕疵我的 BFS只数格子、不数转身而转身在规则里也算一步。 我靠等长候选里优先不转身打了个补丁正确做法是把状态扩成(x, y, θ)或给换方向的边加权重# 改造示意把 (位置, 朝向) 作为搜索状态边权 动作步数 500for action 500in (0, 1, 2, 3): cost 1 # 每个动作都消耗 1 步 nxt_state apply(pos, heading, action) # 前进会移动转向只改朝向 push(nxt_state, g cost) # 4 倍状态空间的 Dijkstra/A*9.4 金币收集顺序一个 TSP 的在线版本按什么顺序把金币全捡了、总步数最短就是旅行商问题TSP——NP 难。 我用的每次奔最近的金币是最邻近贪心O(n²) 很快但近似质量不保证。进阶做法2-opt 局部搜索反复尝试交换路径里两条边的连接方式能变短就接受Christofides 算法对度量 TSP 给出 1.5 倍近似保证最小生成树 最小权完美匹配 欧拉回路短接。# 2-opt 伪代码 improved 500True 500while improved: improved 500False 500for i 500in range(len(route) - 1): 500for j 500in range(i 2, len(route)): 500if dist(route) dist(reverse_segment(route, i 1, j)): route reverse_segment(route, i 1, j) improved 500True9.5 目标决策POMDP 与探索—利用权衡我真正的困难不是排序而是金币位置未知——典型的探索与利用权衡。 形式化模型是POMDP部分可观测马尔可夫决策过程POMDP ⟨ S, A, T, R, Ω, O ⟩ S 状态我在哪、地图长什么样、金币在哪 A 动作前进/左转/右转/后转 T 转移概率 p(s | s, a) R 奖励捡到金币 撞墙 −到达终点 Ω 观测四周格子 坐标 O 观测概率 p(o | s, a) 策略 π(a | b) 作用在信念 b(s) p(s | 历史) 上 信念更新b0) ∝ O(o | s1 | s, a) b(s)要点状态看不见所以策略不能依赖状态只能依赖信念。 工程上常用简单启发式——给每个探索点打分score(frontier) 信息增益(frontier) / 路径代价(frontier)即能新看到多少格子 ÷ 跑过去要多少步。我那 75 步预算是这个打分函数的极简替代品 先做高分动作预算耗尽就收工。9.6 一个被复用的工程思想滚动时域循环 1. 基于当前信息求解未来一段的最优计划 2. 只执行计划的第一步 3. 世界变化 / 拿到新观测 → 丢掉剩余计划回到第 1 步为什么不用一次性完整计划因为信息不断更新新发现的墙会让原计划作废计划越长越容易失效。 短视 勤重算在动态环境里比一次算到底更鲁棒。9.7 如果继续升级这个实验我会做三件事升级具体改动预期收益A* 转向代价搜索状态从(x, y)扩成(x, y, θ)动作边权统一为 1 步让少转身成为算法内生的解而不是补丁2-opt 金币顺序对已发现的金币做局部搜索排序替换每次奔最近多金币地图上减少绕路粒子滤波实验人为给position加噪声对比航位推算漂移与粒子滤波恢复亲眼看懂为什么真机器人要 EKF / SLAM10. 复现方式cd gridbot python run.py --evaluate # 跑完整评测输出分数报告 python run.py --map 5 --fast # 可视化跑地图 5 python run.py --baseline # 跑醉汉 baseline 做对比代码要点集中在my_bot.py的decide()函数里航位推算 → 写记忆地图 → BFS 三级目标规划 → 动作换算。11. 结语智能不是看懂全局而是在传感器残缺、信息不完备时 靠行动—反馈不断修正自己的内部世界模型再据此决策。方向传感器坏了不可怕——可怕的是你只有那一个传感器。 只要你还能动、能动完还能看到一点反馈你就能把丢掉的信息重新算回来。 这也是我从这个 10×10 的小网格里第一次具体地摸到具身智能的形状。本文记录的实验代码与数据均可复现实验条件由学号分配的传感器故障类型决定。
阅读完成 · 觉得有帮助?