51 KiB
title, date, draft, tags, categories
| title | date | draft | tags | categories | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chapter 19 — PRISM 2.0:仿脑空间记忆架构升级 | 2026-05-20 | false |
|
|
Chapter 19 — PRISM 2.0:仿脑空间记忆架构升级
本章目标:基于人类空间记忆的神经科学机制(详见
research/human_spatial_memory.md),将 PRISM 从 v1.5 升级到 v2.0。逐模块给出设计原理、伪代码/接口、与现有架构的衔接方式。前置阅读:
02_architecture.md(四层架构)、18_lyra_inspirations.md(Lyra 设计原则)、research/human_spatial_memory.md(人类空间记忆机制)
19.0 一句话总结
PRISM 2.0 在四层架构中嵌入 8 个仿脑模块——GridMetric(网格细胞)、DG 正交化(模式分离)、PredictiveLayer(预测编码)、CA3 双确认(模式完成)、ExplicitReplay(海马重播)、Reconsolidation(再巩固)、SalienceGate(显著性门控)、BoundaryDistance(边界细胞)——让机器人的空间记忆从"精准度量 + 被动记录"升级为"泛化度量 + 预测驱动的主动更新"。
19.1 为什么是脑?动机与设计哲学
19.1.1 PRISM 1.5 的四个根本局限
| # | 局限 | 现象 | 大脑怎么解决的 |
|---|---|---|---|
| 1 | 无法泛化度量 | OctoMap 绑定到具体房间坐标,换个房间要重建 | 网格细胞提供跨环境的周期性度量编码 |
| 2 | 相似场景混淆 | 两个布局相似的酒店房间,CLIP embedding 高度接近,重定位出错 | 齿状回 (DG) 做模式分离,强制正交化 |
| 3 | 被动接受数据 | ZED 来什么写什么,差异检测只是"发现差异"而非"预期差异" | 预测编码——大脑不断预测下一刻,只在预测失败时更新 |
| 4 | 巩固太粗糙 | Pipeline D 只是"delta 分类 → 应用",缺少重播和自纠错 | 海马重播 + CLS 双系统 + 自增强 |
19.1.2 仿脑不是仿所有
我们借鉴的是大脑的"计算原理",不是生物细节。 类比:飞机不扇翅膀但利用了空气动力学。PRISM 2.0 借鉴的是稀疏编码、周期性度量、预测驱动更新、重播巩固这些计算策略,而不是神经元的离子通道。
19.1.3 P0/P1/P2 优先级总览
| 优先级 | 模块 | 一句话 | 本章节 |
|---|---|---|---|
| P0 | GridMetric | 网格细胞式跨环境度量泛化 | § 19.2 |
| P0 | DG 正交化 | 模式分离:区分相似房间 | § 19.3 |
| P0 | PredictiveLayer | 预测编码:预期→误差驱动更新 | § 19.4 |
| P1 | CA3 双确认 | 模式完成:部分线索→双向验证 | § 19.5 |
| P1 | ExplicitReplay | 海马重播:轨迹压缩重放巩固 | § 19.6 |
| P1 | Reconsolidation | 再巩固:解锁→更新→重新封印 | § 19.7 |
| P1 | SalienceGate | 显著性门控:不是所有东西都该记 | § 19.8 |
| P1 | BoundaryDistance | 边界细胞:以墙为骨架编码位置 | § 19.9 |
| P2 | SuccessorRepr | 继任表征:预测性地图 | § 19.10 |
| P2 | LandmarkRelative | 物体向量细胞:地标相对定位 | § 19.11 |
| P2 | EpisodicBinding | Where/What 紧耦合 | § 19.12 |
19.2 [P0] GridMetric — 网格细胞式跨环境度量
19.2.1 问题
当前 PRISM 的 L2 度量层是纯笛卡尔坐标:
- OctoMap / TSDF 的每个 voxel 位置是绝对坐标
(x, y, z) - 换一个房间 → 全新的坐标空间 → 度量知识归零
- 无法回答"这个走廊和上次那个走廊差不多长"——每进一个新环境都要重学
大脑的解决方案:网格细胞用多个不同间距的六边形周期对空间进行编码,同一组网格细胞在所有环境中复用——间距 30 cm 的那个细胞在卧室和客厅的放电模式相同,只是相位偏移。
19.2.2 设计
在 L1 和 L2 之间插入一个新的 GridMetric 层:
L1 (感知缓冲) ──→ GridMetric ──→ L2 (度量)
│
└──→ L3 路由 (替代 L2 做路由)
GridMetric 的输出不是绝对坐标,而是一组周期性的"位置编码":
@dataclass
class GridMetricLayer:
"""
仿网格细胞的周期性度量编码。
由 K=4 个不同间距的"模块"叠加而成,
每个模块输出一个 (M×M) 的相位向量。
"""
# 4 个模块,间距分别为 σ ∈ {0.25, 0.5, 1.0, 2.0} 米
# (对应大脑中从背侧到腹侧的网格间距梯度)
modules: List[GridModule]
@dataclass
class GridModule:
spacing: float # 网格间距 (米)
orientation: float # 网格方向 (弧度,默认 0)
dim: int = 16 # 相位向量维度 (两个 60° 基向量的编码)
def encode(self, position: np.ndarray) -> np.ndarray:
"""
输入:(3,) 绝对位置
输出:(K*M,) 周期性相位向量 — 这就是"网格细胞放电率"
"""
codes = []
for mod in self.modules:
# 将 xy 位置投影到六边形网格的两个基向量
phi = self._hexagonal_phase(position[:2], mod.spacing, mod.orientation)
# 用傅里叶基编码相位 → 连续且周期性的向量
codes.append(self._fourier_encode(phi, mod.dim))
return np.concatenate(codes)
def decode_position(self, code: np.ndarray) -> List[np.ndarray]:
"""
从网格编码解码回位置候选(多解——因为周期性)
需要结合其他线索(如上一帧位置)消歧。
"""
# 中国剩余定理式解码:多个互质间距的网格共同确定唯一位置
...
19.2.3 为什么这解决了泛化问题
场景 A: 卧室 3×4m 场景 B: 客厅 5×6m
┌──────────┐ ┌──────────────┐
│ · · · · │ │ · · · · · · │
│ · · · · │ │ · · · · · · │
│ · · · · │ │ · · · · · · │
└──────────┘ │ · · · · · · │
└──────────────┘
GridMetric 编码 (σ=1.0m 模块):
卧室角落 → [0.3, 0.7, 0.2, 0.8, ...] ← 这些向量
客厅角落 → [0.3, 0.7, 0.2, 0.8, ...] ← 几乎一样!
网格编码在两个房间的"角落"给出相似的向量 → 机器人不需要"记住每个房间的绝对坐标",而是通过网格相位的周期性模式泛化。"角落的行为像角落"成为一种可迁移的知识。
19.2.4 对现有架构的影响
| 原功能 | 旧实现 | v2.0 实现 |
|---|---|---|
| L2 位置查询 | 绝对坐标索引 OctoMap | GridMetric 编码 → 查哈希表 |
| L3 路由("当前在哪个房间") | L2 绝对坐标对比房间 polygon | GridMetric 编码 + 快速邻居查找 |
| 回环检测 | CLIP 全量匹配 | GridMetric 粗筛候选 + CLIP 精排 |
19.2.5 存储与计算开销
- 编码维度:4 modules × 16 dim = 64 维
- 编码速度:纯三角函数,< 1 μs
- 解码速度:候选网格点查找,< 10 μs
- 存储:64 float32 × 位置数,可忽略
19.3 [P0] DG 正交化 — 模式分离
19.3.1 问题
两个布局相似的酒店房间:
- 同样的床、同样的床头柜、同样的台灯
- iPhone RoomPlan 输出的 furniture label 完全一样
- CLIP embedding 余弦相似度 > 0.92
→ 重定位时 ZED 拍到的画面可能匹配到错误的房间。
19.3.2 设计
在大脑里,齿状回 (DG) 接收来自内嗅皮层的输入,但用极大量神经元 + 极稀疏活动的方式把相似输入映射为不重叠的表征。PRISM 2.0 模拟这个操作:
@dataclass
class DGOrthogonalizer:
"""
仿齿状回的模式分离。
不替代 CLIP——而是在 CLIP 的基础上叠加一层稀疏正交投影。
"""
input_dim: int = 512 # CLIP ViT-B/32 输出维度
hidden_dim: int = 4096 # 高维膨胀(模拟 DG 神经元数量 > 输入数)
sparsity: float = 0.02 # 只有 2% 的维度非零(模拟 DG 极稀疏活动)
threshold: float = 0.85 # 余弦相似度超过此值触发正交化
# 随机但固定的投影矩阵(不需要训练——类似 DG 的随机连接)
projection: np.ndarray # (4096, 512),初始化后不变
def separate(self, clip_emb: np.ndarray) -> np.ndarray:
"""
输入:CLIP embedding (512,)
输出:稀疏高维表征 (4096,),其中 ~80 个维度活跃
"""
# 1. 随机膨胀投影
h = self.projection @ clip_emb # (4096,)
# 2. 赢者通吃:只保留 top-k% 活跃 → 稀疏化
k = int(self.hidden_dim * self.sparsity)
threshold = np.partition(h, -k)[-k]
h[h < threshold] = 0
# 3. L2 归一化
return h / (np.linalg.norm(h) + 1e-8)
def should_create_new_node(
self,
new_emb: np.ndarray,
existing_embs: List[np.ndarray]
) -> bool:
"""
判断新观测是否应该创建新 L3/L4 节点(而非关联到已有节点)。
规则:
- 原始 CLIP 相似度 > 0.9 但 DG 编码后相似度 < 0.3 → 新建节点
- 原始 CLIP 相似度 < 0.5 → 肯定新建
- 原始 CLIP 相似度 > 0.9 且 DG 编码后相似度 > 0.6 → 关联到已有节点
"""
new_dg = self.separate(new_emb)
for exist_emb in existing_embs:
clip_sim = cosine_similarity(new_emb, exist_emb)
dg_sim = cosine_similarity(new_dg, self.separate(exist_emb))
if clip_sim > 0.85 and dg_sim > 0.5:
return False # 真正是同一个地方
return True # CLIP 说像但 DG 说不一样 → 新建
19.3.3 工程直觉
| 场景 | CLIP 相似度 | DG 编码后相似度 | 决策 |
|---|---|---|---|
| 同一房间,不同角度 | 0.88 | 0.72 | ✅ 关联到同一节点 |
| 不同房间,布局相似 | 0.91 | 0.28 | ✅ 创建新节点 |
| 不同房间,完全不同 | 0.45 | 0.05 | ✅ 创建新节点 |
| 同一房间,重新装修后 | 0.62 | 0.42 | ⚠️ 模糊——需要更多证据 |
核心机制:将 D 维稠密向量膨胀到 k*D 维稀疏空间 → 两个原本接近的点在高维稀疏空间中有很大概率正交化。
19.4 [P0] PredictiveLayer — 预测编码
19.4.1 问题
当前 PRISM 的所有写入都是被动的:ZED 产生一帧数据 → worker 处理 → 写入对应层。大脑不是这样工作的——它不断预测下一刻的感官输入,只有当预测失败时才更新记忆。
这导致了两个问题:
- 冗余写入——不变的场景(如空走廊)每秒 5 帧 TSDF 更新,99% 的写入完全重复
- 错过真正的变化——因为没有"期望",差异检测只是一个后验的几何 diff,缺少上下文
19.4.2 设计
@dataclass
class PredictiveLayer:
"""
预测编码层——位于 L1 与 L2/L4 之间。
每接收一帧新观测,先生成"该看到什么"的预测,
只有观测与预测的偏差超过阈值才触发写入和更新。
对应大脑中"自上而下的预测 + 自下而上的预测误差"机制。
"""
# 预测模型:给定当前位姿 + L2/L4 记忆 → 预测下一个观测
spatial_predictor: SpatialPredictor # 预测 L2 几何(深度/占据)
semantic_predictor: SemanticPredictor # 预测 L4 语义(该看到哪些物品)
# 阈值
geometric_threshold: float = 0.05 # TSDF 差异阈值 (m)
semantic_threshold: float = 0.30 # 语义置信度差异阈值
def process_frame(self, frame: PerceptualFrame, memory: SpatialMemory) -> PredictionReport:
"""
每一帧调用一次。返回是否需要写入 + 写入什么。
"""
# === 步骤 1:生成预测 ===
# 从 L2/L4 的当前状态,预测在 frame.pose 处应该看到什么
predicted_depth = self.spatial_predictor.predict(memory.l2, frame.pose)
predicted_objects = self.semantic_predictor.predict(memory.l4, frame.pose)
# === 步骤 2:计算预测误差 ===
geo_error = self._tsdf_diff(frame.depth, predicted_depth)
sem_error = self._semantic_diff(frame.detections, predicted_objects)
# === 步骤 3:根据误差决定行动 ===
if geo_error < self.geometric_threshold and sem_error < self.semantic_threshold:
# 预测准确 → 不写入任何东西,只更新"确认计数"
return PredictionReport(
action=Action.CONFIRM,
surprise=0.0,
message="Prediction confirmed"
)
# 有偏差 → 计算"意外程度"
surprise = self._compute_surprise(geo_error, sem_error)
if surprise > 0.7:
# 高意外 → 立即写入 + 标记为高显著性事件
return PredictionReport(
action=Action.WRITE_IMMEDIATE,
surprise=surprise,
geo_error=geo_error,
sem_error=sem_error,
message=f"High surprise ({surprise:.2f}): geometry or objects unexpected"
)
else:
# 中等意外 → 写入 delta/ 目录,等待 Consolidator 决定
return PredictionReport(
action=Action.WRITE_DELTA,
surprise=surprise,
geo_error=geo_error,
sem_error=sem_error,
message=f"Moderate surprise ({surprise:.2f}): queued for consolidation"
)
19.4.3 SpatialPredictor 的实现
@dataclass
class SpatialPredictor:
"""
给定当前位姿 + L2 几何记忆,预测应该看到的深度图。
实现:从 L2 的 TSDF/3DGS 做 ray-marching 或渲染。
"""
def predict(self, l2: L2Memory, pose: Pose) -> np.ndarray:
"""从当前位姿渲染期望深度图"""
# 用 3DGS (快速) 或 TSDF ray-march (精确) 渲染
if l2.gaussians_uri:
return self._render_3dgs(l2.gaussians_uri, pose)
else:
return self._raymarch_tsdf(l2.tsdf, pose)
19.4.4 SemanticPredictor 的实现
@dataclass
class SemanticPredictor:
"""
给定当前位姿 + L4 语义记忆,预测应该看到哪些物品。
"""
def predict(self, l4: L4Memory, pose: Pose) -> List[PredictedObject]:
"""
查询 L4:
- 在当前 FOV 内的所有已知物品
- 每个物品的期望 bbox (投影到像平面)
"""
in_fov = l4.query_fov(pose, hfov_deg=90, vfov_deg=60, max_depth_m=5.0)
return [
PredictedObject(
uid=node.uid,
label=node.label,
bbox_2d=self._project_bbox(node.bbox_3d, pose),
confidence=node.confidence
)
for node in in_fov
]
19.4.5 效果
| 场景 | 传统 PRISM 1.5 | PRISM 2.0 预测模式 |
|---|---|---|
| 空走廊巡逻 | 每秒 5 次 TSDF 写入 | 预测完全匹配 → 0 次写入,仅心跳确认 |
| 椅子被搬走 | 10 秒后差异检测发现 | 预测椅子的 bbox → 实际无椅子 → 立即 SURPRISE 触发 |
| 光照变化 | TSDF 出现噪声写入 | 几何预测基本一致 → 不触发误报 |
| 新物品出现 | 2 Hz VLM 检测到 | 预测物品列表无此物 → 标记新增 |
关键效果:写放大从 ~99%(几乎每帧都写)降到 ~5%(只在意外时写)。
19.5 [P1] CA3 双确认 — 模式完成
19.5.1 问题
当前重定位 (Pipeline B) 是单向的:CLIP 匹配 → 候选房间 → ICP 验证。缺少"我认出了这个房间,那么我应该看到床和台灯→实际去看一眼确认"的双向验证。
大脑的 CA3 是一个递归自联想网络:部分线索激活整个记忆模式,然后从上往下验证——"如果这是卧室 301,我应该能看到床的左上角……确实看到了,确认。"
19.5.2 设计
@dataclass
class CA3BidirectionalVerifier:
"""
双向模式完成验证器。
在粗匹配后、精配准前运行——对每个候选房间做"期望-观测"交叉验证。
"""
def verify(
self,
observation: PerceptualFrame, # ZED 当前帧
candidate_rooms: List[L3Node], # CLIP + GridMetric 粗筛的候选
l4_memory: L4Memory, # 全局语义记忆
) -> List[VerificationResult]:
results = []
for room in candidate_rooms:
# === 前向:当前观测 → 激活记忆 ===
# (CLIP 已经做了这步,我们继承其 score)
forward_score = room.clip_match_score
# === 反向:激活的记忆 → 期望观测 → 验证 ===
# 取该房间在 FOV 内应有的 top-5 家具
expected_furniture = l4_memory.query_room_furniture(room.uid, limit=5)
observed = observation.detections # YOLO-World 检出
# 验证:期望的家具中,有几个被实际检测到?
verified = 0
for furn in expected_furniture:
if any(det.label == furn.label and
self._iou(det.bbox, self._project_3d_to_2d(furn.bbox_3d, observation.pose)) > 0.3
for det in observed):
verified += 1
backward_score = verified / max(len(expected_furniture), 1)
# === 融合前向+反向得分 ===
combined_score = 0.4 * forward_score + 0.6 * backward_score
results.append(VerificationResult(
room_uid=room.uid,
forward_score=forward_score,
backward_score=backward_score,
combined_score=combined_score,
verified_items=verified,
total_items=len(expected_furniture)
))
# 返回按 combined_score 排序的结果
return sorted(results, key=lambda r: r.combined_score, reverse=True)
19.5.3 效果
场景:ZED 拍到床角,CLIP 返回 {room_301: 0.82, room_302: 0.79}
(两个卧室布局相似,CLIP 无法区分)
CA3 反向验证:
room_301 期望家具:[床, 台灯, 电视, 书桌, 衣柜]
实际检测到:[床 ✓, 电视 ✓, 书桌 ✓]
反向得分 = 3/5 = 0.60
room_302 期望家具:[床, 台灯, 沙发, 茶几, 衣柜]
实际检测到:[床 ✓]
反向得分 = 1/5 = 0.20
融合得分:
room_301 = 0.4 × 0.82 + 0.6 × 0.60 = 0.688 ← 胜出
room_302 = 0.4 × 0.79 + 0.6 × 0.20 = 0.436
→ 正确选择了 room_301,即使 CLIP 只差 0.03
19.6 [P1] ExplicitReplay — 海马重播巩固
19.6.1 问题
当前 Pipeline D(巩固)的逻辑是线性批处理:读 delta JSONL → 分类 → 应用。缺少两个关键操作:
- 时间压缩重播——白天的路径应该以 10-20× 加速重走一遍
- 关键帧精炼——不是所有帧都重播,而是挑选"信息量大"的帧(类似 SWR 选择性重播)
19.6.2 设计
@dataclass
class HippocampalReplay:
"""
仿海马 SWR 重播的巩固引擎。
在充电/空闲时运行,替代原 Pipeline D 的简单 apply 步骤。
"""
compression_ratio: float = 15.0 # 时间压缩倍数
keyframe_budget: int = 200 # 每次重播选取的关键帧数
num_replay_passes: int = 3 # 重播 3 遍(对应多轮 SWR)
def consolidate(
self,
stm_keyframes: List[PerceptualFrame], # 今天积累的所有关键帧
delta_events: List[DeltaEvent], # 差异事件列表
memory: SpatialMemory,
) -> ConsolidationReport:
"""
睡眠式巩固:重播 → 更新 → 验证 → 提交。
"""
# === 阶段 1:挑选重播关键帧 ===
# 不是均匀采样——高意外帧 + 差异事件帧优先
replay_frames = self._select_keyframes(
stm_keyframes, delta_events, self.keyframe_budget
)
# === 阶段 2:多轮压缩重播 ===
for pass_idx in range(self.num_replay_passes):
# 以 compression_ratio × 速度"重走"选中的路径
for frame in self._compressed_trajectory(replay_frames):
# 用当前已更新的 memory 状态重新做预测
report = predictive_layer.process_frame(frame, memory)
if report.surprise > 0.3:
# 重播中仍有意外 → 标记为 unresolved,下轮重点处理
memory.unresolved.append(frame.id)
# 更新 L2 TSDF / L4 confidence(慢速学习率,模拟皮层巩固)
memory.l2.update(frame, learning_rate=0.05) # 慢速
memory.l4.update_confidence(frame, increment=0.01)
# === 阶段 3:应用差异事件 ===
# 经过重播验证的差异才晋升
for event in delta_events:
if event.id not in memory.unresolved:
memory.apply_delta(event)
else:
memory.mark_for_next_session(event) # 留着下次再判断
# === 阶段 4:生成巩固报告 ===
return ConsolidationReport(
replayed_frames=len(replay_frames),
passes=self.num_replay_passes,
applied_deltas=len(delta_events) - len(memory.unresolved),
unresolved=len(memory.unresolved),
)
def _select_keyframes(
self,
all_frames: List[PerceptualFrame],
delta_events: List[DeltaEvent],
budget: int,
) -> List[PerceptualFrame]:
"""
智能关键帧选择策略(模拟 SWR 的选择性重播):
1. 差异事件前后 30 帧——高优先级("事件上下文")
2. 高意外帧(PredictiveLayer 记录的 surprise > 0.5)
3. 第一次进入某房间的帧("边界穿越")
4. 剩余预算用 FPS (Farthest Point Sampling) 覆盖轨迹多样性
"""
selected = []
used_ids = set()
# 策略 1:差异事件附近的帧
for event in delta_events:
nearby = [f for f in all_frames
if abs(f.ts - event.ts) < 30 and f.id not in used_ids]
selected.extend(nearby[:10]) # 每个事件最多 10 帧上下文
used_ids.update(f.id for f in nearby[:10])
# 策略 2:高意外帧
high_surprise = [f for f in all_frames
if f.surprise > 0.5 and f.id not in used_ids]
selected.extend(high_surprise[:budget // 4])
# 策略 3:边界穿越帧(GridMetric 编码突变 > 阈值)
...
# 策略 4:FPS 覆盖剩余预算
remaining_budget = budget - len(selected)
if remaining_budget > 0:
candidates = [f for f in all_frames if f.id not in used_ids]
selected.extend(self._farthest_point_sampling(candidates, remaining_budget))
return selected[:budget]
19.6.3 与 Pipeline D 原版的关系
| 步骤 | Pipeline D v1.5 | Pipeline D v2.0 (ExplicitReplay) |
|---|---|---|
| 触发 | 充电 10 分钟后 | 同 |
| 分类 | 基于规则的 delta 分类 | 同(不变) |
| 仲裁 | 冲突合并 | 同(不变) |
| 应用 | 直接 apply | 重播验证后再 apply ← 新增 |
| 几何更新 | incremental_update_tsdf | 重播过程中逐步更新(慢学习率) |
| 回滚 | snapshot + restore | 每轮重播有 checkpoint |
19.7 [P1] Reconsolidation — 再巩固协议
19.7.1 问题
当前 PRISM 对已存在记忆的更新方式是直接覆写:ZED 重新观测到物品 → 更新它的 pose 字段。但大脑不是这样——已巩固的记忆在被再次激活时会短暂回到不稳定状态,允许修改后再重新巩固。如果直接覆写:
- 一次 ZED 误检就能永久污染一个高 confidence 的记忆
- 无法区分"物品真的被移动了"和"这次看的角度不同"
19.7.2 设计
@dataclass
class ReconsolidationProtocol:
"""
再巩固协议——管理已有记忆的"解锁→更新→重新封印"生命周期。
"""
# 什么条件下触发"解锁"(记忆变得可修改)
@dataclass
class UnlockConditions:
prediction_error: bool = True # 预测编码层报告意外
novelty_in_context: bool = True # 熟悉房间中的新物品
temporal_gap: float = 3600.0 # 超过 1 小时没看到 → 自动解锁
contradicting_observations: int = 3 # 连续 3 次矛盾观测 → 解锁
def process_observation(
self,
observed_item: dict, # ZED/VLM 检出的物品
matched_memory: Optional[L4Node], # 匹配到的已有记忆(可能为 None)
salience: float, # 来自 SalienceGate 的显著性分数
) -> ReconsolidationAction:
"""
对每个检测到的物品调用一次。
"""
# === 情况 1:新物品 → 快速编码(海马体模式) ===
if matched_memory is None:
return ReconsolidationAction(
type=ActionType.ENCODE_NEW,
memory_state=MemoryState.UNSTABLE, # 初始不稳定
confidence=0.3, # 低置信度
unlocks_at=time.time() + 3600, # 1 小时内可能被巩固或遗忘
)
# === 情况 2:已知物品,预测一致 → 强化记忆 ===
if not self._should_unlock(matched_memory, observed_item, salience):
return ReconsolidationAction(
type=ActionType.REINFORCE,
memory_state=MemoryState.STABLE,
confidence_adjust=+0.02, # 小幅增加置信度
)
# === 情况 3:已知物品,预测不一致 → 解锁记忆 ===
return ReconsolidationAction(
type=ActionType.UNLOCK_AND_UPDATE,
memory_state=MemoryState.UNSTABLE, # 锁被打开
unlocked_fields=['pose', 'bbox_3d'], # 哪些字段可修改
confidence_adjust=-0.05, # 一致性下降
new_evidence=observed_item, # 新观测证据
unlocks_at=time.time() + 1800, # 30 分钟内必须重新巩固
message=f"Memory {matched_memory.uid} unlocked: observed at new pose"
)
def reconsolidate(
self,
memory: L4Node,
accumulated_evidence: List[dict], # 解锁期间收集的所有新观测
consolidator_approval: bool, # Consolidator 是否批准
) -> L4Node:
"""
解锁的记忆在 Consolidator 阶段被重新封印(或丢弃)。
"""
if not consolidator_approval:
# 不批准 → 记忆退回原状态
memory.state = MemoryState.STABLE
return memory
# 融合证据:加权平均新的 pose/bbox
memory.pose = self._weighted_average(
[memory.pose] + [e['pose'] for e in accumulated_evidence],
weights=[0.7] + [0.3 / len(accumulated_evidence)] * len(accumulated_evidence)
)
memory.bbox_3d = ... # 同理
memory.state = MemoryState.STABLE
memory.last_reconsolidated = time.time()
return memory
19.7.3 记忆状态机
┌──── 新观测 ────→ UNSTABLE (confidence=0.3)
│ │
│ ┌─────────┼─────────┐
│ │ 连续 N 次确认 │ 1 小时内无确认
│ ▼ ▼
│ STABLE FORGOTTEN
│ │
│ ┌─────────┼─────────┐
│ │ 预测误差 │ 超过 temporal_gap 没看到
│ ▼ ▼
│ UNSTABLE UNSTABLE
│ (unlocked) (unlocked)
│ │ │
│ └──── Consolidator ──┘
│ │
│ ┌─────────┼─────────┐
│ │ 批准 │ 驳回
│ ▼ ▼
│ STABLE STABLE
│ (updated) (rolled back)
└──────────────────────────┘
19.8 [P1] SalienceGate — 显著性门控
19.8.1 问题
当前 PRISM 对 YOLO-World 的每次 2 Hz 检测全量写入 L4。一个 8 小时的巡逻 session 可能产生 > 50,000 条检测,绝大多数是完全重复的(同一个台灯被检测到 5,000 次)。大脑不会这样——只有意外、重要、新奇的刺激才会被编码。
19.8.2 设计
@dataclass
class SalienceGate:
"""
显著性门控——决定一个观测是否值得写入长时记忆。
输出 0-1 的显著性分数,低于阈值的观测被丢弃。
"""
threshold: float = 0.2 # 低于此值的检测仅用于实时导航,不写入记忆
def score(self, detection: Detection, memory: SpatialMemory) -> float:
"""
综合多个线索计算显著性分数。
"""
# 因子 1:预测误差 (来自 PredictiveLayer)
prediction_surprise = detection.surprise # 0-1
# 因子 2:空间新奇性——这个位置/区域是新发现的吗?
spatial_novelty = 1.0 - memory.l2.is_known_region(detection.position)
# 因子 3:语义新奇性——这个物品之前没见过吗?
known_match = memory.l4.find_similar(detection)
semantic_novelty = 0.0 if known_match and known_match.confidence > 0.7 else 1.0
# 因子 4:时间衰减——距上次看到这个物品多久了?
if known_match:
hours_since_last = (time.time() - known_match.last_seen) / 3600
temporal_novelty = min(hours_since_last / 24.0, 1.0) # 24 小时达到 1.0
else:
temporal_novelty = 1.0
# 因子 5:目标相关性——和当前任务有关吗?
task_relevance = self._compute_task_relevance(detection)
# 加权融合
salience = (
0.35 * prediction_surprise +
0.20 * spatial_novelty +
0.20 * semantic_novelty +
0.10 * temporal_novelty +
0.15 * task_relevance
)
return min(salience, 1.0)
def should_write(self, salience: float) -> WriteDecision:
"""根据显著性决定写入策略"""
if salience < self.threshold:
return WriteDecision.SKIP # 不写入记忆
elif salience < 0.5:
return WriteDecision.STM_ONLY # 只写短期(delta/)
else:
return WriteDecision.FULL # 写短期 + 触发 Consolidator 关注
19.8.3 效果
| 检测 | 预测误差 | 空间新奇 | 语义新奇 | 时间衰减 | 显著性 | 决策 |
|---|---|---|---|---|---|---|
| 同一个台灯 (第 500 次) | 0.0 | 0.0 | 0.0 | 0.0 | 0.00 | SKIP |
| 新出现的盆栽 | 0.9 | 0.1 | 0.9 | 1.0 | 0.66 | FULL |
| 椅子位置变了 30 cm | 0.7 | 0.0 | 0.1 | 0.2 | 0.30 | STM_ONLY |
| 进入从未去过的走廊 | 0.3 | 0.9 | 0.8 | 1.0 | 0.57 | FULL |
| 按任务找遥控器时看到遥控器 | 0.1 | 0.0 | 0.0 | 0.3 | 0.52 | FULL |
写放大从 ~50,000 条/天 降到 ~500 条/天(1%),且保留了最重要的信息。
19.9 [P1] BoundaryDistance — 边界细胞特征
19.9.1 问题
当前 PRISM 中,位置由 (x, y, z) 绝对坐标 + room polygon 表示。但大脑的边界细胞编码的是"离最近墙的距离"+"离特定方向的墙的距离"——这比绝对坐标更鲁棒,因为墙是一个环境中最难改变的东西。
19.9.2 设计
@dataclass
class BoundaryDistance:
"""
仿边界细胞:为任意位置计算"相对于环境边界"的特征。
输入来自 iPhone RoomPlan 或 ZED 累积扫描提取的 wall_segments。
"""
walls: List[WallSegment] # 环境中的所有墙体段
def encode(self, position: np.ndarray) -> np.ndarray:
"""
输入:2D 位置 (x, y)
输出:边界距离特征向量
特征组成:
- ego-centric: 距最近墙的距离(标量)
- allocentric_N/S/E/W: 距最近 N/S/E/W 方向墙的距离(4 维)
- boundary_orientation: 最近墙的法向量方向编码(2 维)
总共 7 维
"""
# 独热方向:将 360° 分成 4 个象限
direction_bins = 4
# 1. ego-centric: 到最近墙的距离
min_dist = min(wall.distance_to(position) for wall in self.walls)
# 2. allocentric: 到 N/S/E/W 四个方向最近墙的距离
n_dist = min(w.distance_to(position) for w in self.walls
if w.normal_dot(position) > 0.7) # 朝北的墙
s_dist = min(w.distance_to(position) for w in self.walls
if w.normal_dot(position) < -0.7) # 朝南的墙
# ... E/W 同理
# 3. 最近墙的法向量方向
nearest = min(self.walls, key=lambda w: w.distance_to(position))
orientation_code = self._angle_encode(nearest.normal_angle)
return np.array([min_dist, n_dist, s_dist, e_dist, w_dist,
orientation_code[0], orientation_code[1]])
19.9.3 用途
- 位置细胞的初始化脚手架——在没有边界距离特征的环境中,位置细胞无法稳定形成。PRISM 2.0 在首次进入新房间时,用边界距离特征初始化位置表征。
- 重定位的辅助信号——"我离北墙 1.5m、东墙 0.8m"是一个非常有辨别力的信号,特别在对称房间中。
19.10 [P2] SuccessorRepr — 继任表征
19.10.1 问题
当前路径规划用 A* 在 L3 图上搜索,每次规划都是 O(N log N)。内嗅皮层实际上编码了继任表征 (Successor Representation)——从每个状态出发,未来可能访问的所有状态的期望折现频率。有了 SR 后,"从 A 到 B 的最优路径"退化为一次向量内积。
19.10.2 设计
@dataclass
class SuccessorRepresentation:
"""
继任表征——为 L3 的每个节点存储一个 SR 向量,
向量中的每个元素表示从该节点出发到达对应节点的期望折现访问次数。
"""
gamma: float = 0.9 # 折现因子
sr_matrix: np.ndarray # (N_nodes × N_nodes),或低秩近似
def compute(self, l3: L3Memory) -> np.ndarray:
"""
从 L3 的图结构计算 SR 矩阵。
SR = (I - γ * T)^{-1},其中 T 是转移概率矩阵。
初始转移概率由边的 cost 决定:
P(i→j) ∝ exp(-cost(i, j) / temperature)
"""
n = len(l3.nodes)
T = np.zeros((n, n))
for edge in l3.edges:
src_idx = l3.node_index[edge.src]
dst_idx = l3.node_index[edge.dst]
T[src_idx, dst_idx] = np.exp(-edge.cost / self.temperature)
if edge.bidirectional:
T[dst_idx, src_idx] = np.exp(-edge.cost / self.temperature)
# 行归一化
T = T / T.sum(axis=1, keepdims=True)
# SR = (I - γT)^{-1}
self.sr_matrix = np.linalg.inv(np.eye(n) - self.gamma * T)
return self.sr_matrix
def plan_path(self, start_uid: str, goal_uid: str) -> List[str]:
"""
用 SR 做一步路径规划:
在每个节点选择使"到目标的 SR 值"最大的邻居。
"""
start_idx = l3.node_index[start_uid]
goal_idx = l3.node_index[goal_uid]
path = [start_uid]
current = start_uid
while current != goal_uid:
current_idx = l3.node_index[current]
# 选 SR(current→neighbor→goal) 最大的邻居
neighbors = [e.dst for e in l3.edges if e.src == current]
if not neighbors:
break
best = max(neighbors, key=lambda n:
self.sr_matrix[l3.node_index[n], goal_idx])
path.append(best)
current = best
return path
19.10.3 优势
- 规划时间:A* O(N log N) → SR O(1) per step(查表)
- 自然地处理"不要路过厨房"等软约束——只需把厨房对应的 SR 列置零
- 可以增量更新——机器人走通新边后,只需局部更新 SR
19.11 [P2] LandmarkRelative — 地标相对定位
19.11.1 问题
当前 PRISM 只用绝对位姿 (x, y, z, qx, qy, qz, qw)。在 VIO 漂移时(长走廊、弱纹理),绝对位姿会累积误差。大脑的物体向量细胞提供了另一种坐标系统:相对于地标的方向和距离。
19.11.2 设计
@dataclass
class LandmarkRelativePose:
"""
相对于已知地标的位姿表示。
作为全局位姿的补充——在 VIO 漂移时提供锚定信号。
"""
def compute(
self,
current_pose: Pose,
visible_landmarks: List[L4Node],
) -> Dict[str, LandmarkVector]:
"""
计算当前位置相对于每个可见地标的向量。
"""
vectors = {}
for landmark in visible_landmarks:
# 从地标指向机器人的向量
delta = current_pose.translation - landmark.pose.translation
distance = np.linalg.norm(delta)
direction = np.arctan2(delta[1], delta[0]) # 相对于世界坐标的方位角
# 同时存地标朝向的方位角(用于估计机器人面向)
relative_angle = direction - current_pose.yaw
vectors[landmark.uid] = LandmarkVector(
landmark_uid=landmark.uid,
distance=distance,
world_direction=direction,
ego_direction=relative_angle,
landmark_label=landmark.label,
)
return vectors
def estimate_pose(
self,
vectors: Dict[str, LandmarkVector],
landmark_poses: Dict[str, Pose],
) -> Pose:
"""
从多个地标向量反推全局位姿(三角定位)。
用于 VIO 跟丢后的恢复,或作为 VIO 漂移的修正信号。
"""
# 最少需要 2 个地标
if len(vectors) < 2:
raise ValueError("Need at least 2 landmarks for pose estimation")
# 加权三角定位(置信度高的地标权重大)
...
19.12 [P2] EpisodicBinding — Where/What 紧耦合
19.12.1 问题
当前 PRISM 中,物品的位置只是 L4Node 的一个 pose 字段——它与 L2 的几何没有深度绑定。当 L2 的 TSDF 更新时(如墙壁被修正),物品不会自动跟着修正。大脑中,海马体将"where"和"what"绑定为一个不可分割的 episodic memory。
19.12.2 设计
@dataclass
class EpisodicBinding:
"""
Where/What 紧耦合——将物品与其所在空间的几何上下文绑定。
当 L2 几何被修正时,所有绑定物品的位姿自动修正。
"""
def bind(self, item: L4Node, l2_geometry: L2Memory) -> BoundItem:
"""
绑定物品到 L2 几何——记录物品在 TSDF 中的锚定 voxel。
"""
# 找到物品 bbox 的中心在 TSDF 中对应的 voxel
anchor_voxel_idx = l2_geometry.tsdf.world_to_voxel(item.pose.translation)
# 记录"锚定上下文"——物品周围 10 cm 立方体内的体素特征
context_patch = l2_geometry.tsdf.extract_patch(anchor_voxel_idx, radius_voxels=5)
return BoundItem(
item=item,
anchor_voxel=anchor_voxel_idx,
context_signature=hash(context_patch), # 几何上下文指纹
bound_at=time.time(),
)
def relocalize_bound_item(
self,
bound: BoundItem,
l2_geometry: L2Memory,
) -> Pose:
"""
当 L2 几何被修正后,用绑定的上下文重新定位物品。
如果锚定 voxel 的上下文签名不匹配,搜索邻近区域找到最佳匹配。
"""
current_context = l2_geometry.tsdf.extract_patch(
bound.anchor_voxel, radius_voxels=5
)
if hash(current_context) == bound.context_signature:
# 上下文未变 → 原地不动
return bound.item.pose
else:
# 上下文变了(几何被更新)→ 搜索最佳匹配位置
best_voxel = l2_geometry.tsdf.search_patch(
bound.context_signature, search_radius=10
)
return l2_geometry.tsdf.voxel_to_world(best_voxel)
19.13 PRISM 2.0 完整架构图
┌─────────────────────────────────┐
│ SalienceGate │
│ (显著性门控:决定什么写入) │
└──────────┬──────────────────────┘
│
┌─────────┐ ┌──────────┐ ▼ ┌──────────┐ ┌──────────┐
│ L1 │────→│GridMetric│────→│ │Predictive│────→│ L2 │
│ 感知缓冲 │ │(网格细胞) │ │ │ Layer │ │ 度量记忆 │
│ 30 Hz │ │64 维编码 │ │ │(预测编码) │ │ TSDF/GS │
└─────────┘ └──────────┘ │ └─────┬─────┘ └────┬─────┘
│ │ │ │
│ │ ┌───────┴───────┐ │
│ ┌──────────┐ │ │ DG Orthogonalizer│ │
│ │ Boundary │ │ │ (模式分离) │ │
│ │ Distance │ │ └───────┬───────┘ │
│ │(边界细胞) │ │ │ │
│ └────┬─────┘ │ ▼ │
│ │ │ ┌───────────────┐ │
│ ▼ │ │ CA3 Verifier │ │
│ ┌──────────┐ │ │ (双向模式完成) │ │
└────→│ L3 │←───────┘ └───────┬───────┘ │
│ 拓扑记忆 │ │ │
│ + SR │ ▼ │
└────┬─────┘ ┌──────────┐ │
│ │ L4 │←─────────┘
│ │ 语义记忆 │
│ │ + Episodic│
│ │ Binding │
│ └────┬─────┘
│ │
▼ ▼
┌─────────────────────────────────────┐
│ Reconsolidation Protocol │
│ (解锁 → 更新 → 重新封印) │
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ ExplicitReplay Consolidator │
│ (海马重播:压缩轨迹 → 多轮验证) │
└─────────────────────────────────────┘
图例:
- 实线 = 数据流
- 粗体 = P0 新增模块
- 斜体 = P1 新增模块
- 细线 = P2 新增模块
19.14 更新的流水线
19.14.1 Pipeline A (iPhone 离线建图) — 新增步骤
原 A1-A8 之后新增 A9:
A9: 计算 GridMetric 初始化
- 从 RoomPlan wall segments 计算 BoundaryDistance 特征
- 初始化 GridMetric 层(用房间尺寸自适应选择网格间距)
- 为每个 L3 节点计算 SR 向量(初始从 RoomPlan 的 room 连接推导)
19.14.2 Pipeline B (重定位) — 变化
原 B1-B9 → v2.0:
B1: ZED 上电,VIO 启动
B2: GridMetric 粗定位(利用网格编码 + 上一帧位置消歧) ← 新增
B3: CLIP + DG 正交化 → 候选房间 ← 修改
B4: CA3 双向验证(期望家具 vs 实际检测) ← 新增
B5: BoundaryDistance 辅助验证 ← 新增
B6: TEASER++/ICP 精配准
B7: LandmarkRelative 锚定(如果精配准 fitness < 0.7) ← 新增
B8: 发布 TF
19.14.3 Pipeline C (在线感知) — 变化
C1: ZED → L1 环形缓冲 (不变)
C2: GridMetric 编码 (替代直接写 L2) ← 新增
C3: PredictiveLayer 预测 → 计算 surprise ← 新增
C4: SalienceGate 判定 → 决定写入级别 ← 新增
C5: (仅高显著性) TSDF 增量融合 ← 条件化
C6: (仅高显著性) VLM 检测 + DG 正交化 → L4 ← 条件化
C7: Reconsolidation 处理已知物品的更新 ← 新增
C8: 差异检测 + delta/ 写入 (不变,但触发条件改为预测误差 > 阈值)
19.14.4 Pipeline D (巩固) — 变化
原 D1-D8 → v2.0:
D1: 触发 (充电 > 10 分钟,不变)
D2: 分类 delta 事件 (不变)
D3: 选择重播关键帧 (智能采样策略) ← 新增
D4: ExplicitReplay: 压缩轨迹多轮重播 (3 passes) ← 新增
D5: 重播中验证预测——仍意外的标记 unresolved ← 新增
D6: 仲裁冲突 (不变)
D7: Reconsolidation: 处理所有 unlocked 的记忆 ← 新增
D8: apply_events (不变,但只应用到通过重播验证的事件)
D9: 更新 SR 矩阵(如有新边/修改 cost) ← 新增
D10: 增量 L2 更新 (不变)
D11: staging → sanity → commit (不变)
19.15 更新的路线图
19.15.1 对原有 8 周原型计划的影响
| 原有 Phase | 影响 | 调整 |
|---|---|---|
| Phase 0 (准备) | 无变化 | — |
| Phase 1 (单边管线) | Pipeline A 增加 GridMetric 初始化 + BoundaryDistance | W1 增加 1 天 |
| Phase 2 (重定位) | 增加 DG 正交化 + CA3 双确认 | W3-W4 增加 3 天 |
| Phase 3 (在线感知) | 增加 PredictiveLayer + SalienceGate | W5-W6 增加 5 天 |
| Phase 4 (巩固) | 增加 ExplicitReplay + Reconsolidation | W7 增加 2 天 |
| Phase 5 (demo) | 无变化 | — |
总计:原 8 周 → 约 10 周(+25%)
19.15.2 P0/P1/P2 的交付策略
| 阶段 | 范围 | 交付 |
|---|---|---|
| v2.0-alpha (10 周) | P0 全部 (GridMetric + DG + PredictiveLayer) | 单房间完整闭环 |
| v2.0-beta (14 周) | + P1 全部 | 多房间 + 长时运行 |
| v2.0 (18 周) | + P1 稳定 + 评测 | 完整 PRISM 2.0 |
| v2.1 (22 周) | + P2 全部 | SR + LandmarkRelative + EpisodicBinding |
19.16 风险与缓解
| 风险 | 概率 | 影响 | 缓解 |
|---|---|---|---|
| GridMetric 解码歧义导致路由错误 | 中 | 高 | 保留 L2 绝对坐标作为 fallback;GridMetric 作加权建议 |
| DG 高维投影导致内存爆炸 | 低 | 中 | 用 scipy.sparse 存储;或降维到 1024 |
| PredictiveLayer 渲染太慢 | 中 | 中 | 先用简化的 OctoMap ray-cast;3DGS 渲染仅在 Jetson 空闲时 |
| 重播时间太长,充电窗口不够 | 中 | 中 | 自适应 keyframe budget;允许 incremental 增量重播 |
| SalienceGate 阈值调不好 | 高 | 低 | 提供可视化调参工具;阈值持久化为可运行时修改的配置 |
| 整体复杂度增加,调试困难 | 高 | 高 | 每个模块可独立开关(feature flag);v1.5 兼容模式保底 |
19.17 从 v1.5 到 v2.0 的迁移
原则:渐进升级,不破坏 v1.5
# configs/prism_v2.yaml
version: "2.0"
brain_inspired_modules:
grid_metric:
enabled: true # P0 — 设为 false 回退到 v1.5 行为
num_modules: 4
spacings: [0.25, 0.5, 1.0, 2.0]
dg_orthogonalizer:
enabled: true # P0
hidden_dim: 4096
sparsity: 0.02
predictive_layer:
enabled: true # P0
geometric_threshold: 0.05
semantic_threshold: 0.30
ca3_verifier:
enabled: true # P1
backward_weight: 0.6
explicit_replay:
enabled: true # P1
compression_ratio: 15.0
num_passes: 3
reconsolidation:
enabled: true # P1
salience_gate:
enabled: true # P1
threshold: 0.2
boundary_distance:
enabled: true # P1
successor_repr:
enabled: false # P2 — 默认关闭
gamma: 0.9
landmark_relative:
enabled: false # P2
episodic_binding:
enabled: false # P2
每个模块都可以通过配置独立开关。关闭所有 brain_inspired_modules → 行为回退到 v1.5。
19.18 本章小结
| # | 模块 | 优先级 | 对应脑区 | 一句话 | 关键影响 |
|---|---|---|---|---|---|
| 1 | GridMetric | P0 | 内侧内嗅皮层 | 周期性度量编码,跨环境泛化 | L2 不再绑定绝对坐标 |
| 2 | DG Orthogonalizer | P0 | 齿状回 | 相似场景强制正交化 | 重定位消歧 |
| 3 | PredictiveLayer | P0 | 全皮层 | 预测→误差驱动更新 | 写放大从 99%→5% |
| 4 | CA3 Verifier | P1 | 海马 CA3 | 双向验证重定位候选 | 减少错误关联 |
| 5 | ExplicitReplay | P1 | 海马 SWR | 压缩轨迹多轮重播巩固 | 巩固不再是一次性 batch |
| 6 | Reconsolidation | P1 | 海马+皮层 | 解锁→更新→重新封印 | 记忆不会因单次误检被污染 |
| 7 | SalienceGate | P1 | 注意网络 | "不值得记的就别记" | 存储量降低 99% |
| 8 | BoundaryDistance | P1 | 下托/MEC | 以墙为骨架的定位特征 | 对称房间可区分 |
| 9 | SuccessorRepr | P2 | 内嗅皮层 | 预测性地图 | 路径规划 O(1) |
| 10 | LandmarkRelative | P2 | MEC | 地标相对定位 | VIO 漂移时锚定 |
| 11 | EpisodicBinding | P2 | 海马体 | Where/What 紧耦合 | 几何修正自动传播 |
读完本章你应能:
- ✅ 理解每个仿脑模块的神经科学来源和工程映射
- ✅ 理解 P0/P1/P2 的优先级和渐进交付策略
- ✅ 在 v1.5 代码基础上逐步开启 v2.0 模块
下一步:
research/human_spatial_memory.md— 神经科学机制详解- PRISM 2.0 原型实现(从 P0 模块开始)
章节版本:v1.0 估计阅读时间:35 分钟 关键收获:8 个仿脑模块的设计原理、伪代码、与现有 Pipeline 的集成方式