PGT:用程序化几何图元治好多模态大模型的"空间失明症"
一句话总结
PGT(Procedurally Generated Tasks)通过在真实图像上叠加无语义偏见的几何图元,生成可靠的细粒度监督信号,让多模态大模型(MLLM)学会真正的视觉定位——而不是用语言先验猜测空间关系。
为什么这个问题重要?
在机器人抓取、AR 场景理解、自动驾驶感知等任务中,系统需要回答的不是”图片里有什么”,而是”A 在 B 的哪一侧”“哪个物体离相机更近”。这类细粒度空间推理正是现有 MLLM 的软肋。
一个典型的失败案例:
图像:桌上放着一个杯子(左)和一个苹果(右)
问题:苹果在杯子的哪一侧?
模型回答:左侧 ← 错误,但符合"苹果通常在桌子左边"的语言分布
模型不是在看图,而是在查语言统计字典。在真实部署环境中,这种”假理解”会造成灾难性后果。
背景:MLLM 的空间推理困境
语义先验 vs. 视觉定位
MLLM 在预训练时见过数十亿张图文对,学到了大量共现统计:
- “冰箱通常在厨房里” → 如果问”冰箱在哪”,模型倾向于回答厨房相关答案
- “汽车通常在道路上” → 深度估计时倾向于把汽车放在中远景
这种语义先验(Semantic Prior) 在粗粒度理解上帮助很大,但会系统性地干扰精细的几何推理。
论文通过一个关键实验证实了这一点:将标准 MLLM 在纯几何任务(无语义内容)上测试,发现失败率远高于有语义上下文的版本。结论是:大量空间推理错误来自监督信号不足,而非架构或分辨率的根本限制。
3D 空间理解的三类子任务
| 任务类型 | 典型问题 | 难点 |
|---|---|---|
| 关系理解 | A 在 B 的左/右/上/下? | 需要真实图像坐标理解 |
| 数量理解 | 图中有几个红色标记? | 需要抗遮挡、抗干扰计数 |
| 深度/3D 理解 | A、B 哪个离相机更近? | 需要透视规律理解 |
PGT 核心思想
直觉解释
PGT 的核心思路非常优雅:把答案直接画在图里。
原始图像(一张厨房照片)
↓
叠加几何图元(在随机位置放两个彩色圆点,标记为 A/B)
↓
生成 QA 对("A 在 B 的哪侧?" → 根据坐标自动生成正确答案)
↓
模型训练(无法依赖语义先验,必须看坐标才能答对)
关键设计:几何图元(圆点、箭头、十字标记)本身没有语义含义,出现在任何场景中都说得通。这彻底切断了模型依赖语义偏见作弊的路径。
同时,这个框架也是一个诊断工具:如果模型在 PGT 任务上失败,失败原因一定是视觉定位能力不足(而非语言理解问题)——因为题目没有可供利用的语义捷径。
数学细节
设图像为 $I$,叠加几何图元后得到 $I’ = \text{Overlay}(I, P)$,其中 $P$ 是图元集合。
对于位置关系任务,标签由几何约束直接确定:
\[y_{rel} = \text{sign}(x_A - x_B) \quad \text{(左右关系)}\] \[y_{depth} = \text{sign}(y_A - y_B) \quad \text{(透视深度,图像纵坐标越大 = 近)}\]模型的训练目标是标准的条件语言建模损失:
\[\mathcal{L}_{PGT} = -\log P(y \mid I', q; \theta)\]- $q$:问题文本
- $y$:由几何坐标自动生成的确定性答案
- $\theta$:模型参数
最终的指令微调混合了原始数据和 PGT 数据:
\[\mathcal{L} = \mathcal{L}_{orig} + \lambda \mathcal{L}_{PGT}\]在列表中引用时注意:答案 $y$ 对图元 $P$ 和位置 $\mid P \mid$ 存在确定映射,不依赖于背景图像 $I$ 的语义内容。
Pipeline 概览
真实图像集合 (COCO / LLaVA-Instruct)
│
▼
┌──────────────────┐
│ PGT 生成器 │
│ - 随机采样图元类型 │
│ - 随机放置位置 │
│ - 生成 QA 对 │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 增强数据集 │
│ 原始QA + PGT QA │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ 指令微调 MLLM │
│ (LLaVA / InternVL│
│ / Qwen-VL...) │
└────────┬─────────┘
│
▼
空间感知增强的 MLLM
(What'sUp +20%, CV-Bench +13.3%)
实现
环境配置
pip install opencv-python pillow transformers datasets
# 下载 LLaVA-v1.5-Instruct 数据集用于增强(约 665K 条)
# 实际 PGT 生成在原图基础上在线进行,无需额外存储
PGT 数据生成器
import cv2
import numpy as np
import random
from dataclasses import dataclass, field
from typing import Tuple
@dataclass
class PGTSample:
image: np.ndarray
question: str
answer: str
task_type: str # "relational" | "counting" | "depth"
class PGTGenerator:
"""程序化任务生成器:在图像上叠加无语义图元,生成确定性 QA 对"""
COLORS = {"red": (0, 0, 255), "blue": (255, 0, 0), "green": (0, 200, 0)}
def generate(self, image: np.ndarray) -> PGTSample:
task = random.choice(["relational", "counting", "depth"])
if task == "relational":
return self._relational(image)
elif task == "counting":
return self._counting(image)
else:
return self._depth(image)
def _relational(self, img: np.ndarray) -> PGTSample:
h, w = img.shape[:2]
vis = img.copy()
# 确保 A、B 水平位置有明确差异
x_a = random.randint(w // 8, w // 2 - 20)
x_b = random.randint(w // 2 + 20, 7 * w // 8)
y_a = random.randint(h // 4, 3 * h // 4)
y_b = random.randint(h // 4, 3 * h // 4)
cv2.circle(vis, (x_a, y_a), 12, self.COLORS["red"], -1)
cv2.circle(vis, (x_b, y_b), 12, self.COLORS["blue"], -1)
cv2.putText(vis, "A", (x_a + 14, y_a + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, self.COLORS["red"], 2)
cv2.putText(vis, "B", (x_b + 14, y_b + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, self.COLORS["blue"], 2)
# 答案由坐标决定,零歧义
answer = "A" if x_a < x_b else "B"
return PGTSample(vis, "红色标记A和蓝色标记B,哪个在图中更靠左侧?", answer, "relational")
def _counting(self, img: np.ndarray) -> PGTSample:
h, w = img.shape[:2]
n = random.randint(2, 8)
vis = img.copy()
for _ in range(n):
x, y = random.randint(15, w - 15), random.randint(15, h - 15)
cv2.drawMarker(vis, (x, y), (0, 255, 255), cv2.MARKER_CROSS, 18, 2)
return PGTSample(vis, "图中有多少个黄色十字标记?", str(n), "counting")
def _depth(self, img: np.ndarray) -> PGTSample:
h, w = img.shape[:2]
vis = img.copy()
# 透视规律:图像底部 ≈ 近,顶部 ≈ 远
y_near = random.randint(2 * h // 3, h - 20)
y_far = random.randint(20, h // 3)
x_near = random.randint(w // 4, 3 * w // 4)
x_far = random.randint(w // 4, 3 * w // 4)
cv2.circle(vis, (x_near, y_near), 12, self.COLORS["green"], -1)
cv2.circle(vis, (x_far, y_far), 12, (255, 0, 255), -1) # 紫色
cv2.putText(vis, "A", (x_near + 14, y_near), cv2.FONT_HERSHEY_SIMPLEX, 0.7, self.COLORS["green"], 2)
cv2.putText(vis, "B", (x_far + 14, y_far), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 255), 2)
return PGTSample(vis, "标记A和标记B,基于透视关系哪个距离相机更近?", "A更近", "depth")
数据集增强集成
from torch.utils.data import Dataset
class PGTAugmentedDataset(Dataset):
"""将 PGT 样本混入原始指令微调数据集"""
def __init__(self, original_dataset, pgt_ratio=0.3):
self.original = original_dataset
self.generator = PGTGenerator()
self.pgt_ratio = pgt_ratio
def __getitem__(self, idx):
item = self.original[idx]
# 以 pgt_ratio 的概率替换为 PGT 增强版本
if random.random() < self.pgt_ratio:
image_np = np.array(item["image"])
sample = self.generator.generate(image_np)
return {
"image": sample.image,
"conversations": [
{"role": "user", "content": sample.question},
{"role": "assistant","content": sample.answer},
],
"task_type": sample.task_type,
}
return item
def __len__(self):
return len(self.original)
3D 可视化:诊断模型的空间推理能力
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
def visualize_failure_analysis(model, generator, test_images, n=12):
"""统计模型在不同 PGT 任务类型上的错误分布"""
results = {"relational": [], "counting": [], "depth": []}
for img in test_images[:n]:
for task_type in results:
sample = getattr(generator, f"_{task_type}")(np.array(img))
pred = model.predict(sample.image, sample.question)
correct = (pred.strip() == sample.answer.strip())
results[task_type].append(correct)
# ... (绘图代码省略)
acc = {k: np.mean(v) for k, v in results.items()}
# 若 relational 准确率低,说明 2D 定位能力弱
# 若 depth 准确率低,说明透视/3D 理解缺失
return acc
实验
数据集说明
| 数据集 | 用途 | 规模 | 获取难度 |
|---|---|---|---|
| LLaVA-v1.5-Instruct | 指令微调基础数据 | 665K 条 | 公开,HuggingFace 直接下载 |
| What’sUp | 空间关系评测 | 约 4K 图像 | 公开 |
| CV-Bench | 综合视觉推理评测 | 包含 2D/3D 子任务 | 公开 |
What’sUp 专门测试”上/下/左/右”关系理解,是目前最严苛的 MLLM 空间推理基准之一。
定量结果
| 方法 | What’sUp ↑ | CV-Bench-2D ↑ | CV-Bench-3D ↑ |
|---|---|---|---|
| LLaVA-v1.5 (原始) | ~38% | ~45% | ~40% |
| LLaVA-v1.5 + PGT | ~58% | ~58% | 提升显著 |
| SOTA MLLM (原始) | ~65% | ~70% | - |
| SOTA MLLM + PGT | ~70% | ~78% | - |
注:具体数字参见论文,此处为基于论文描述的近似值(+20% / +13.3% 为论文报告的提升幅度)。
核心结论:数据增强而非架构改动,实现了超出预期的提升幅度。
工程实践
实际部署考虑
- 训练开销:PGT 数据在线生成,CPU 端实时合成,无额外 GPU 开销;指令微调约需 1-2 个 A100 GPU·天
- 推理开销:零增量,PGT 只影响训练阶段,推理时完全使用标准模型
- 数据比例:实验表明 PGT 数据占比 20-40% 时效果最佳,过多会损害通用能力
数据采集建议
图元叠加时有几个容易忽略的细节:
# 坑1:图元颜色与背景冲突 → 用高对比度颜色 + 描边
cv2.circle(vis, pos, 12, color, -1)
cv2.circle(vis, pos, 12, (255,255,255), 1) # 白色描边,增强可见性
# 坑2:标签遮挡图元本身 → 偏移标签位置
label_pos = (pos[0] + 14, pos[1] + 5) # 避免与圆点重叠
# 坑3:深度任务中图元位置太规律 → 加随机扰动避免模型学位置捷径
y_near += random.randint(-30, 30)
常见坑
-
PGT 答案漏精确匹配 → 规范化输出字符串,用
strip().lower()后比较;或改为多选题格式((A) 左边 (B) 右边)减少自由文本歧义 -
图元放在纯色背景上效果差 → 在场景丰富的图像上叠加效果更好;可预过滤低方差(纯色/模糊)图像
-
计数任务中图元重叠 → 生成时检测最小距离约束,避免两个标记过于靠近
什么时候用 / 不用?
| 适用场景 | 不适用场景 |
|---|---|
| 需要细粒度空间推理的 MLLM 微调 | 只需要物体识别的任务 |
| 机器人指令跟随(”拿左边的那个”) | 纯文本任务 |
| AR/VR 场景理解 | 高速推理且不能微调的生产系统 |
| 诊断模型空间推理能力的研究 | 数据量极少无法微调的场景 |
与其他方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原始指令微调 | 通用能力强 | 空间推理靠先验猜测 | 通用对话 |
| 人工标注空间数据 | 准确性高 | 成本极高,难以规模化 | 高精度垂直任务 |
| 数据增强(翻转/裁剪) | 实现简单 | 未解决语义先验问题 | 一般增强 |
| PGT(本文) | 零标注成本,可扩展 | 几何场景与真实场景存在分布差异 | 空间理解增强 |
| NeRF/3DGS 渲染数据 | 高质量 3D 监督 | 数据准备成本高,依赖场景重建 | 3D 感知专项任务 |
我的观点
PGT 最让我觉得有价值的不是那 +20% 的数字,而是它提供了一种思维范式:把答案的生成逻辑从”语义分布”转移到”几何约束”。这种想法在 3D 视觉的其他领域也有对应:
- SLAM 的闭环检测:不依赖外观特征,而是用几何一致性验证
- 深度估计:用多视图几何约束代替单张图语义线索
- 6DoF 位姿估计:用坐标系几何替代物体外观匹配
离实际应用有多远? 比较近。该方法不需要新的模型架构,只是数据增强,可以直接插入现有的 MLLM 微调流程。对于机器人领域的应用开发者,这是一个低成本改善空间推理能力的实用工具。
值得关注的开放问题:
- PGT 生成的几何场景和真实遮挡/透视的分布差异还有多大?
- 能否把 PGT 扩展到视频帧序列,处理动态场景的时序空间推理?
- 和显式 3D 表示(深度图、点云)结合,是否能进一步消除先验依赖?
论文链接:https://arxiv.org/abs/2605.23883
Comments