ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型空间智能评测:鹈鹕骑自行车测试与SVG自动化实践

大模型空间智能评测:鹈鹕骑自行车测试与SVG自动化实践 1. 这道题到底在考什么第一次看到“让 AI 画一只鹈鹕骑自行车”这个需求我差点笑出声。这不明摆着整活吗但真把这句话丢给几个主流大模型跑了一圈之后我笑不出来了——能画对的没几个画得好的更是凤毛麟角。后来我把它当成一个固定的测试用例反复跑了上百次才慢慢琢磨明白这根本不是在测画图能力而是在给大模型做一场空间智商考试。为什么这么说你想想鹈鹕这种鸟嘴长、脖子长、身子大、腿短自行车呢两个轮子、一个车架、一个车把、一副脚踏。要让鹈鹕“骑”上去模型得同时处理好几层空间关系鹈鹕的屁股得落在车座上翅膀得够到车把脚蹼得踩在脚踏上长嘴还不能戳到前轮。这里面涉及部件识别、姿态推理、接触点判断、遮挡关系任何一环掉链子出来的图就是“鹈鹕飘在自行车旁边”或者“自行车长了个鸟头”。我拿这个题目测过不少模型包括一些号称多模态能力很强的。结果很有意思有的模型画出来的鹈鹕确实像鹈鹕自行车也确实像自行车但两者就是各玩各的完全没有“骑”这个动作。还有的模型把鹈鹕的嘴画成了自行车横梁或者把翅膀画成了车把。这些失败案例恰恰暴露了大模型在空间推理上的短板——它能识别物体但搞不定物体之间的相对位置和物理约束。所以这个测试用例的价值不在于它有多搞笑而在于它用极低的成本把大模型的空间智能问题给具象化了。你不需要搭复杂的评测环境不需要标注数据只要一句话就能看出一个模型在空间理解上到底有几斤几两。对于做AI测试开发、大模型评测、甚至AI Agent方向的人来说这是一个非常实用的“土办法”。2. 为什么偏偏是鹈鹕和自行车2.1 这个组合的刁钻之处你可能会问为什么不是“猫骑自行车”或者“狗骑自行车”猫和狗太常见了训练数据里估计有一堆模型可能靠记忆就能拼出来。鹈鹕就不一样了它的身体结构太特殊了巨大的喉囊、细长的脖子、短小的腿、宽大的翅膀。这些特征在训练数据里出现的频率远低于猫狗模型没法靠“背答案”蒙混过关必须真正理解空间关系才能画对。自行车也是个妙选。它的结构足够复杂有车架、车轮、车把、车座、脚踏、链条但又足够常见模型肯定认识。难点在于自行车是一个功能性物体它的各个部件有明确的交互逻辑手要握车把脚要踩脚踏屁股要坐车座。模型如果只是把鹈鹕和自行车两个图像“贴”在一起就会露馅。我试过把题目改成“鹈鹕骑滑板车”难度立刻下降——滑板车结构简单接触点少模型更容易蒙对。改成“鹈鹕骑独轮车”难度又上升了——独轮车对平衡要求更高模型更难推理出合理的姿态。所以“鹈鹕自行车”这个组合刚好卡在一个难度适中但区分度极高的位置上。2.2 从SVG到像素图的测试差异这里要区分一下测试场景。如果你让模型生成SVG代码那考的是它的结构化空间描述能力。SVG是矢量图每个元素的位置、大小、旋转角度都得用数值精确表达。模型得知道鹈鹕的翅膀应该放在哪个坐标自行车的车轮半径是多少两者怎么对齐。这种测试更硬核因为数值错了就是错了没有“大概像”的模糊空间。如果你让模型生成像素图比如通过扩散模型那考的是它的视觉空间推理能力。模型得在潜空间里把鹈鹕和自行车的特征融合起来生成一个合理的构图。这种测试更接近人类直觉但评估起来更主观需要人眼判断。我两种都试过。SVG测试更适合做自动化评测你可以写脚本解析生成的SVG检查关键元素的位置关系。像素图测试更适合做定性分析看看模型到底“理解”了多少。对于做AI测试开发的人来说SVG路线更值得投入因为它的可复现性和可量化性更好。2.3 为什么这个测试能火这个测试之所以在圈子里传开我觉得有几个原因。第一它门槛极低谁都能试不需要任何专业工具。第二它结果直观画得好不好一眼就能看出来不需要解释。第三它戳中了痛点大模型的空间智能确实是个短板但平时不容易暴露这个测试把它给揪出来了。还有一个原因就是它自带传播属性。“鹈鹕骑自行车”这个画面本身就够荒诞生成出来的失败案例更是笑料百出。大家在转发和讨论的过程中其实是在集体探索大模型的能力边界。这种自下而上的评测方式比官方跑分有意思多了。3. 拆解空间智商的几个维度3.1 部件识别与语义理解第一层考的是部件识别。模型得知道鹈鹕有哪些部件嘴、脖子、身子、翅膀、腿、脚蹼自行车有哪些部件车轮、车架、车把、车座、脚踏。这层看起来简单但实际测试中有些模型会把鹈鹕的嘴和自行车的车把搞混或者把翅膀和车架混在一起。这说明模型在细粒度语义区分上还有问题。我试过给模型一些提示比如“鹈鹕的嘴是长在头上的自行车的车把是金属的”帮助它区分。结果发现加了提示之后部件识别确实改善了但空间关系还是错。这说明部件识别和空间推理是两个相对独立的能力得分开训练和评测。3.2 姿态推理与关节约束第二层考的是姿态推理。鹈鹕要骑自行车它的身体得摆出一个合理的姿势脖子得往前伸翅膀得往下够腿得弯曲踩脚踏。这涉及到关节约束——鹈鹕的翅膀能转多少度腿能弯多少脖子能伸多长。模型如果不懂这些约束画出来的鹈鹕就是“橡皮鸟”翅膀能360度旋转腿能拉成面条。我观察到一个有趣的现象有些模型会把鹈鹕画成“站立”姿势只是把自行车放在它脚下。这说明模型没有理解“骑”这个动作的姿态要求只是把两个物体简单叠加。要解决这个问题模型需要学习动作语义知道“骑”意味着什么。3.3 接触点与物理合理性第三层考的是接触点判断。鹈鹕的屁股得接触车座翅膀得接触车把脚蹼得接触脚踏。这些接触点必须同时满足而且不能有穿模。我见过一些生成结果鹈鹕的翅膀穿过了车把或者脚蹼悬空在脚踏上方。这些都是接触点判断失败的表现。物理合理性还包括重心和平衡。鹈鹕骑自行车重心得落在两个轮子之间否则就会倒。有些模型画出来的鹈鹕身子完全在前轮上方一看就要翻车。这说明模型没有物理直觉不知道物体需要保持平衡。3.4 遮挡关系与深度排序第四层考的是遮挡关系。鹈鹕的腿应该在车架后面翅膀应该在车把前面身子应该挡住车座的一部分。这些遮挡关系决定了画面的深度感。如果遮挡错了画面就会显得扁平、混乱。我试过让模型生成SVG然后检查元素的z-order。结果发现很多模型生成的SVG里元素的顺序是随机的没有考虑遮挡。这说明模型在深度排序上还有很大的提升空间。4. 实测用Python和PowerShell跑一轮评测4.1 环境准备与工具选型要系统性地跑这个测试得先搭个环境。我用的方案是Python PowerShellPython负责调用模型API和处理结果PowerShell负责批量调度和日志记录。为什么选这个组合因为Python的生态最丰富处理SVG和图像的库很多PowerShell在Windows上原生支持调度脚本写起来方便而且可以轻松调用系统命令。Python环境建议用3.10以上版本安装几个关键库pip install requests pillow cairosvg lxml numpyrequests调用模型APIpillow处理像素图cairosvg把SVG转成PNG方便肉眼检查lxml解析SVG的XML结构numpy做数值计算比如检查元素位置PowerShell这边主要是写一个批量调度脚本循环调用Python脚本把每次生成的结果保存下来。如果你在Windows上跑记得先更新PowerShell到7.x版本老版本的兼容性有问题。注意如果你在PowerShell里遇到乱码大概率是编码问题。在脚本开头加上$OutputEncoding [System.Text.Encoding]::UTF8和[Console]::OutputEncoding [System.Text.Encoding]::UTF8就能解决。4.2 调用模型生成SVG的完整脚本下面是我实际用的Python脚本功能是调用模型生成SVG然后保存到本地import requests import json import os from datetime import datetime API_URL 你的模型API地址 API_KEY 你的API密钥 def generate_svg(prompt, modelyour-model): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: model, messages: [ {role: system, content: 你是一个SVG生成助手请根据用户描述生成完整的SVG代码。}, {role: user, content: prompt} ], temperature: 0.7 } response requests.post(API_URL, headersheaders, jsonpayload, timeout60) if response.status_code 200: return response.json()[choices][0][message][content] else: return None def save_svg(svg_content, filename): os.makedirs(output, exist_okTrue) filepath os.path.join(output, filename) with open(filepath, w, encodingutf-8) as f: f.write(svg_content) return filepath if __name__ __main__: prompt Generate an SVG of a pelican riding a bicycle. The pelican should be sitting on the seat, with its wings on the handlebars and its feet on the pedals. svg generate_svg(prompt) if svg: timestamp datetime.now().strftime(%Y%m%d_%H%M%S) filepath save_svg(svg, fpelican_bike_{timestamp}.svg) print(fSVG saved to {filepath}) else: print(Generation failed)这个脚本的关键点在于prompt的设计。我特意把“sitting on the seat”、“wings on the handlebars”、“feet on the pedals”这些空间关系写进去就是为了看模型能不能理解并执行。如果模型忽略了这些约束生成的SVG就会有问题。4.3 PowerShell批量调度与日志记录单次生成不够得批量跑才能看出规律。下面这个PowerShell脚本可以循环调用Python脚本并把每次的结果记录到日志里$OutputEncoding [System.Text.Encoding]::UTF8 [Console]::OutputEncoding [System.Text.Encoding]::UTF8 $logFile eval_log_$(Get-Date -Format yyyyMMdd_HHmmss).txt $iterations 20 for ($i 1; $i -le $iterations; $i) { Write-Host Running iteration $i... $result python generate_svg.py 21 $timestamp Get-Date -Format yyyy-MM-dd HH:mm:ss $timestamp | Iteration $i | $result | Out-File -FilePath $logFile -Append Start-Sleep -Seconds 2 } Write-Host Evaluation complete. Log saved to $logFile这个脚本会跑20次每次间隔2秒避免触发API限流。日志文件里记录了每次的运行结果方便后续分析。如果你要跑更多次可以把$iterations调大但记得加长间隔时间。实操心得批量跑的时候建议把temperature设成0.7左右。太低的话每次生成的结果都差不多看不出模型的多样性太高的话生成结果太随机难以分析规律。4.4 结果解析与评分脚本生成了一堆SVG之后得有个自动化的方式来评分。我写了一个简单的解析脚本检查几个关键的空间关系from lxml import etree import os def parse_svg(filepath): tree etree.parse(filepath) root tree.getroot() ns {svg: http://www.w3.org/2000/svg} elements {} for elem in root.iter(): tag etree.QName(elem).localname if tag in [circle, ellipse, rect, path, polygon]: elem_id elem.get(id, ) if elem_id: elements[elem_id] { tag: tag, x: elem.get(cx) or elem.get(x), y: elem.get(cy) or elem.get(y), r: elem.get(r), d: elem.get(d) } return elements def score_svg(elements): score 0 checks [] # 检查是否有车轮 wheels [k for k in elements if wheel in k.lower() or circle in k.lower()] if len(wheels) 2: score 20 checks.append(车轮数量正确) else: checks.append(车轮数量不足) # 检查是否有鹈鹕的身体 body [k for k in elements if body in k.lower() or pelican in k.lower()] if body: score 20 checks.append(鹈鹕身体存在) else: checks.append(缺少鹈鹕身体) # 检查是否有车把 handlebar [k for k in elements if handle in k.lower() or bar in k.lower()] if handlebar: score 20 checks.append(车把存在) else: checks.append(缺少车把) # 检查是否有车座 seat [k for k in elements if seat in k.lower() or saddle in k.lower()] if seat: score 20 checks.append(车座存在) else: checks.append(缺少车座) # 检查是否有脚踏 pedal [k for k in elements if pedal in k.lower()] if pedal: score 20 checks.append(脚踏存在) else: checks.append(缺少脚踏) return score, checks if __name__ __main__: output_dir output results [] for filename in os.listdir(output_dir): if filename.endswith(.svg): filepath os.path.join(output_dir, filename) try: elements parse_svg(filepath) score, checks score_svg(elements) results.append((filename, score, checks)) except Exception as e: results.append((filename, 0, [f解析失败: {str(e)}])) results.sort(keylambda x: x[1], reverseTrue) for filename, score, checks in results: print(f{filename}: {score}分) for check in checks: print(f - {check})这个评分脚本比较粗糙但能快速筛出明显有问题的生成结果。比如如果连车轮都没有那肯定是不合格的。如果车轮、身体、车把、车座、脚踏都齐了至少说明模型在部件识别上过关了。5. 常见翻车现场与排查思路5.1 鹈鹕和自行车各画各的这是最常见的翻车方式。模型生成的SVG里鹈鹕是一个独立的group自行车是另一个独立的group两者没有任何空间上的交互。你打开图一看鹈鹕飘在自行车旁边或者自行车悬在鹈鹕头顶。排查思路检查SVG里有没有对鹈鹕和自行车元素做坐标对齐。如果两者的坐标系是独立的那大概率就是各画各的。解决办法是在prompt里明确要求“鹈鹕的屁股坐标应该和车座坐标一致”或者让模型先生成自行车的坐标再基于自行车坐标生成鹈鹕。5.2 部件张冠李戴有的模型会把鹈鹕的嘴画成车把或者把翅膀画成车架。这种错误说明模型在语义绑定上出了问题它知道有这些部件但不知道哪个部件属于哪个物体。排查思路检查SVG里元素的命名和分组。如果鹈鹕的嘴被放在了自行车的group里那就是分组错了。解决办法是在prompt里强调“鹈鹕的嘴是鹈鹕的一部分不要和自行车的部件混淆”。5.3 接触点悬空或穿模鹈鹕的脚蹼悬在脚踏上方几厘米或者翅膀穿过了车把。这种错误说明模型没有接触点约束的概念。排查思路检查关键接触点的坐标是否一致。比如脚蹼的y坐标应该和脚踏的y坐标相同。如果差太多就是悬空如果脚蹼的x坐标在脚踏的x坐标范围内但y坐标重叠就是穿模。解决办法是在prompt里给出具体的坐标约束比如“脚蹼的底部y坐标等于脚踏的顶部y坐标”。5.4 姿态扭曲鹈鹕的脖子扭了180度或者翅膀反向折叠。这种错误说明模型没有关节约束的概念。排查思路检查鹈鹕各部件之间的相对角度。如果脖子和身体的夹角超过合理范围就是姿态扭曲。解决办法是在prompt里描述合理的姿态比如“鹈鹕的脖子向前伸展与身体呈45度角”。5.5 常见问题速查表问题现象可能原因排查方法解决思路鹈鹕和自行车分离坐标系独立检查两者坐标是否对齐prompt中明确坐标约束部件张冠李戴语义绑定错误检查元素分组强调部件归属接触点悬空缺少接触约束检查关键点坐标给出具体坐标等式姿态扭曲缺少关节约束检查部件夹角描述合理姿态角度遮挡错误z-order随机检查元素顺序指定遮挡关系比例失调缺少尺寸约束检查元素大小给出相对尺寸比例实操心得排查的时候建议先把SVG转成PNG肉眼看一下。有些问题光看代码看不出来但一看图就明白了。cairosvg这个库很好用一行命令就能转cairosvg input.svg -o output.png。6. 从测试到应用这个思路还能怎么用6.1 扩展到其他空间推理任务“鹈鹕骑自行车”只是一个切入点同样的思路可以扩展到很多空间推理任务。比如动物使用工具猴子用石头砸坚果大象用鼻子卷树枝人物交互两个人握手一个人递给另一个人东西复杂场景桌子上放着杯子杯子旁边有本书书下面压着纸这些任务的共同点是都需要模型理解物体之间的空间关系和交互逻辑。你可以把它们做成一个测试集系统性地评估模型的空间智能。6.2 用于AI Agent的规划能力测试如果你在做AI Agent这个测试还能用来评估规划能力。比如让Agent规划一个“画鹈鹕骑自行车”的任务看它能不能拆解成合理的步骤先画自行车再画鹈鹕然后调整鹈鹕的姿态最后检查接触点。如果Agent的规划步骤混乱那它的空间推理能力大概率也不行。我试过让一个Agent做这个任务它的规划是“先画鹈鹕再画自行车然后把自行车放在鹈鹕下面”。这个规划明显有问题因为“放在下面”没有考虑接触点。后来我调整了prompt要求它“先确定车座位置再确定鹈鹕屁股位置”它的规划就合理多了。6.3 结合SVG做可视化调试SVG的好处是可编辑、可调试。你可以把模型生成的SVG导入到矢量编辑工具里手动调整元素位置看看怎么改才能让画面合理。这个过程本身就是在逆向理解模型的空间推理逻辑。我经常这么做把生成的SVG打开把鹈鹕和自行车分开然后手动对齐接触点看看需要移动多少距离。这个距离往往就是模型空间推理的误差量。如果误差很大说明模型的空间感知很粗糙如果误差很小说明模型只是差一点点微调。6.4 作为大模型微调的评测指标如果你在做大模型微调这个测试可以作为一个轻量级的评测指标。你不需要标注大量数据只需要准备几十个类似“鹈鹕骑自行车”的prompt然后写个脚本自动评分。微调前后各跑一遍就能看出空间推理能力有没有提升。我试过用这个指标评估一个微调后的模型发现它在“部件识别”上提升了但在“接触点判断”上反而下降了。这说明微调数据里可能缺少接触点相关的样本。后来我补充了一些接触点标注数据再微调两个指标都上去了。7. 一些实操中的体会这个测试我断断续续跑了小半年积累了一些零散的经验这里一并分享一下。第一prompt的写法很关键。如果你只写“画一只鹈鹕骑自行车”模型大概率会自由发挥结果不可控。如果你把空间关系写清楚比如“鹈鹕的屁股坐在车座上翅膀握住车把脚蹼踩在脚踏上”模型的生成质量会明显提升。这说明模型不是不能理解空间关系而是需要明确的指令。第二不同模型的失败模式不一样。有的模型擅长画鹈鹕但自行车画得稀烂有的模型自行车画得很标准但鹈鹕像个气球。了解每个模型的强弱项有助于你针对性地设计prompt。比如对于鹈鹕画得好的模型你可以多给一些自行车的结构描述对于自行车画得好的模型你可以多给一些鹈鹕的姿态描述。第三SVG比像素图更适合做自动化评测。像素图的评估太主观而且需要大量人工。SVG是结构化的你可以写脚本自动检查元素的位置、大小、顺序。虽然SVG的生成难度更高但一旦跑通评测效率会高很多。第四不要指望一次就能画对。我跑了上百次真正完全正确的不到10%。大部分结果都是“差不多但有点问题”。这说明空间推理对大模型来说仍然是个难题。但换个角度想这也意味着这个领域还有很大的提升空间值得投入精力去研究。第五这个测试可以做得更细。比如你可以把“鹈鹕骑自行车”拆解成多个子任务先画自行车再画鹈鹕再调整姿态再检查接触点。每个子任务单独评测就能更精确地定位模型的短板。我试过这种拆解方式发现模型在“画自行车”这个子任务上表现最好在“调整姿态”上表现最差。第六别忘了保存每次的生成结果。我一开始没注意跑完就删了后来想对比不同版本的表现发现没数据了。现在我会把每次的SVG、PNG、日志都保存下来按时间戳命名。这样过一段时间回头看能清楚地看到模型的进步轨迹。第七可以结合其他评测维度。空间推理只是大模型能力的一个方面你还可以结合语义理解、指令遵循、创造力等维度一起评测。比如让模型画“鹈鹕骑自行车背景是夕阳下的海滩”就能同时测试空间推理和场景构建能力。第八这个测试很适合做教学案例。我带过几个新人让他们用这个题目练手效果很好。因为它足够简单谁都能上手又足够深刻能引出很多空间推理的问题。新人通过这个案例能快速理解大模型的能力边界和评测方法。第九不要被失败案例打击到。看到模型画出一堆乱七八糟的东西确实容易让人沮丧。但换个角度想这些失败案例恰恰是最有价值的数据。它们告诉你模型在哪里不行为什么不行怎么才能让它行。做AI测试开发最重要的能力就是从失败中找规律。第十保持好奇心。这个测试最开始只是我随手一试没想到后来成了我的一个固定评测项目。很多时候最有价值的发现就藏在那些看似“整活”的想法里。你永远不知道哪个小测试会变成大发现所以多试、多玩、多折腾总没错。最后再分享一个小技巧如果你想让模型生成更合理的SVG可以在prompt里加一句“请确保所有元素都在viewBox范围内且接触点坐标一致”。这句话能显著减少元素溢出和接触点悬空的问题。我试过加了这句话之后合格率大概能提升20%左右。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进