语言模型能写诗、能翻译、能写代码,但你要是让它做几道时空关系推理题,比如“猫在箱子左边,箱子在球右边,那么猫在球哪边”,它十有八九会给出一个让人哭笑不得的答案。我最早意识到这个短板,是在一个模拟项目X里测试一款开源底座模型时——当时只是想看看它在常识推理上的底线在哪,结果连续三道涉及“左右传递”和“时间先后叠加”的题全答错了。这个现象让我花了不少时间专门去琢磨一件事:语言模型在时空关系推理中的表现提升,到底该从哪里下手。
这篇文章就是我那段经历的完整记录,包含问题根因分析、合成数据构造方法、微调与提示策略的配合、评测集设计,以及我踩过的一些坑。适合正在做大模型微调、指令数据构造,或者对推理能力增强感兴趣的人参考。
1. 先看清问题:时空关系推理为什么会让语言模型集体翻车
1.1 两个典型失败案例:连小学生都会的空间题,模型却答错
先说两个我实测过的案例,都是非常基础的关系推理,没有陷阱,没有歧义。
案例A是空间链式推理:题目是“猫在箱子左边,箱子在球右边,问:猫在球的哪一边?”正确答案是“猫在球的右边”。模型给出的答案是“左边”。问题就出在它把“箱子在球右边”直接等同为“球在箱子右边”,又用“猫在箱子左边”做了错误的方向合并,整个推理链条在第一步就跑偏了。
案例B是时间叠加计算:“会议下午2点开始,进行了一个半小时后休息20分钟,再继续1小时后结束,问:会议几点结束?”正确答案是下午4点50分。模型给出的答案是“4点40分”,把“休息20分钟”漏算在了结束时间之外,或者把“一个半小时”直接按照“1小时50分钟”处理了。这种错误在人类身上几乎不会发生,但在模型身上非常普遍。
这两个案例本质上反映的是同一个问题:模型在回答时空关系题时,更多是在做“词语共现”和“句式联想”,而不是在真正执行一套可验证的坐标或时间线运算。
1.2 把“关系”拆开看:空间、时间、综合场景是三种不同考验
我一开始以为时空关系推理是一块相对单一的能力,后来把任务拆开才发现,它至少包含三个难度递增的子类。我把它们整理成了下面这个表,方便在后续做数据构造和评测时对号入座。
| 子任务类型 | 典型问法 | 模型常见失败模式 | 需要具备的核心能力 |
|---|---|---|---|
| 单步空间关系 | A在B的左边,B在C的左边,问A在C哪边 | 方向反转,把“B在C左边”误推成“C在B左边” | 相对方向基准的建立与保持 |
| 多步空间链式推理 | A在B的左边,B在C的右边,C在D的前面,问A相对D的位置 | 链式传递出错,越到后面积累误差越大 | 多跳坐标变换与中间结果记忆 |
| 时间叠加与区间运算 | 事件从2点开始,持续80分钟,休息10分钟,再持续30分钟,问结束时间 | 分钟进位出错、漏算事件间隔、跨小时运算混乱 | 时间点与时间段的混合计算 |
| 时空综合场景 | 从A点出发向北走200米,再向东走100米,用时15分钟,问目前方位与到达时间 | 空间坐标与时间线各自正确但无法同步 | 把空间关系与时间进度耦合在一个统一场景里 |
从这个表里能看出,提升空间很大,但每一类需要的训练信号和数据形式其实差别很大。这也是为什么简单的“多喂一点语料”很难奏效,因为网上的自然语言语料里很少有成体系的、带唯一标注答案的时空推理题。
1.3 大模型翻车的三个根源:共现偏置、坐标缺失、序列离散化
为什么语言模型在自然语言理解上表现那么好,一到时空关系就掉链子?我总结了三个根源。
第一个是共现偏置。语言模型本质上是在学习词与词之间的条件和共现概率。“左边”“右边”这些词在训练语料里经常一起出现,模型学会了它们“相关”,却没学会它们在几何上“相反”。于是它看到“左边”时,没有足够的机制去抑制“右边”这个关联词浮出概率。
第二个是坐标缺失。自然语言里的空间描述都是相对的、依赖观察者视角的,没有一个文本自带的全局坐标系。同样的“在左边”,可能是紧挨着、隔一个物体、还是在很远的地方,模型无法从文本里还原出稳定的几何结构。时间也一样,语料里的“半小时后”没有统一的锚点。
第三个是序列离散化的天然缺陷。语言模型通过自回归逐个生成token,它没有一个中间机制去维护一个“当前坐标系”或“当前时间线”的状态寄存器。每一步推理的中间状态都必须想办法塞进文本里,如果推理步骤没显式写出来,它就丢掉了。
这三个根源决定了,单靠增大模型参数量并不能解决全部问题。提升的核心思路,必须是给模型提供足够一致、可验证、带结构化的时空推理训练信号,并且推理时引导它把中间状态显式算出来。
2. 数据侧提升:合成数据集是成本最低的突破口
2.1 公开语料为什么喂不饱时空推理
最开始我尝试直接从公开语料里捞时空关系推理样本,结果很不理想。表面上看,“在左边”“之前”“之后”这些词在网上出现的频率非常高,讨论区、新闻、小说里到处都是。但深层看,这些语料有两个致命问题。
第一是自相矛盾。不同的作者对同一个空间关系的描述方式千差万别,甚至同一个句子里前后方位根本不统一。模型读到大量互相矛盾的语料,学到的不是“一个确定的关系”,而是“关系本身是模糊的”。
第二是缺乏可验证标签。网上文本里极少有“A在B左边”后面紧跟着“正确答案是右边”这种监督信号。模型看不到关系的唯一真值,就无从建立稳定的映射。
试想一下,让一个学生学几何,但不给他做任何带标准答案的习题,只让他读大量描述方位的小说,他能学会几何推理吗?公开语料之于时空推理,就是这个效果。
2.2 构造合成数据:模板生成加程序校验,保证“三元一致”
既然公开语料不可靠,就得自己造数据。我的做法是合成数据生成,核心思想是程序先生成确定的结构,再转成自然语言。
对于空间数据,构造步骤是这样的:
- 在程序里定义一张坐标系,随机放置3到5个物体。物体名称从一个词表里随机抽,比如“猫、箱子、球、水杯、台灯”。
- 程序根据物体的真实坐标,计算出物体之间的“左、右、前、后、上、下”关系,以及“紧挨着、隔一个、在远处”这类的距离修饰。
- 用多套自然语言模板,把坐标关系和物体名渲染成问句和答句。问句可以是“A在B的哪边”或者“以C为参照,A和B的位置关系是什么”,答句把计算出来的结果填进去。
关键在于“三元一致”:坐标、关系表达式、自然语言文本三者的内容必须完全吻合。先定空间坐标,再从坐标推导关系,最后才转成文本。绝对不能先写一句自然语言,再把它翻译成关系表达式,那样会引入歧义。我见过不少失败的数据集,表面上长得很像训练题,内部关系却是乱的,模型越练越糊涂。
对于时间数据也类似:
- 先生成一条时间线,给每个事件分配开始时间点和持续时间。
- 程序计算事件之间的先后关系、间隔时长、总时长。
- 再把它转成“事件描述+问题+答案”的三元组。
时间数据比空间数据难一点的是,它的自然语言表达方式非常多,描述“下午2点开始”和“14:00开始”是等价的,模型如果只见过一种格式,很容易产生严重偏置。
2.3 平衡性与数量级:多大训练量算够,又不会淹没基础能力
合成数据并不是越多越好。我在做模拟项目X时用了大概5万条空间样本、2万条时间样本。这个数量我认为是个比较合适的起点。
如果再低,比如空间样本只有几千条,模型的句式覆盖不足,遇到没见过的问法依然不会。如果再往上堆,比如单一空间样本到几十万条,模型确实能把这类题做得很准,但基础能力会明显下滑,通用对话的流畅度、知识问答的准确性都会掉。平衡点大约在总训练量占全量指令数据的10%到20%之间。
这一条没有标准答案,不同底座模型的表现差异很大。我自己测试下来,7B量级的开源底座模型在这个比例附近效果最好,更大的模型基础能力强,比例可以稍微降低。
合成数据还有几个容易踩的坑。模板句式必须多样化,我用至少20套不同的问法来渲染同一个关系,避免模型背住模板。另外一定要加入干扰句和条件反转,比如“如果猫在箱子右边,而箱子在球左边,猫在球的哪边”,让模型学会读条件而不是套模板。我还加入了少量“信息不足”样本,例如没有给出参照对象的问题,正确答案是“无法判断”,这能有效减少模型强行作答的概率。
3. 训练与推理策略:让模型学会“脑内坐标系”
3.1 指令微调:把问题改造成显式推理任务
光有数据还不够,还要把数据放进合适的任务格式里。我发现,与其直接让模型输出一个答案,不如把训练目标从“给答案”改成“给出推理过程再给答案”。这样模型在微调阶段就学会了一个习惯:遇到时空关系题,先建立中间结构。
我用的数据格式大致是这样:
输入: 根据以下描述,回答问题。 “猫在箱子左边,箱子在球右边。” 问:猫在球的哪一边?请先写出推理步骤,再给出最终答案。 输出: 第一步:猫在箱子左边。 第二步:箱子在球右边,说明箱子在球的右侧。 第三步:猫在箱子的左侧,也就是在球右侧的更左侧。 所以答案是:猫在球的左边。注意,这里输出里的推理过程和答案是完全一致的。我在构造训练样本时,不是手写这些步骤,而是用程序从坐标关系里一步步推导出推理链,再把推理链渲染成文本。这样保证了模型学到的“思维链”是真实可验证的。
实践下来,指令微调后再做同样一套题,准确率上升非常明显。因为模型不再需要直接从“问题文本”飞跃到“答案文本”,而是被训练成先写出关系、再做合并、再给结论。
3.2 思维链提示词:用“自言自语”补偿缺失的坐标计算
推理时也要配合提示策略。我在微调后的模型上试过几种提示词,效果差异很大。最简单的“请回答”格式,模型偶尔还是会跳步;在提示里显式加上“请按步骤推理并写出每一步”,效果会稳很多。
一个具体例子:
请按以下步骤推理: 1. 找出描述中每一个物体的参照关系。 2. 把每个关系转成方向判断。 3. 逐步合并关系,求出最终目标物体的位置。 4. 写出最终答案。为什么这种提示有效?因为思维链本质上是在让模型把隐式状态外化成显式文本序列。它每写一步,就相当于往上下文里塞了一个新的“中间寄存器”,下一步生成时,最新的文本至少给了它一个离结果更近的锚点。这在很大程度上缓解了共现偏置的影响。
不过思维链也不是万能的,它对小模型效果可能很有限。我更建议的做法是:先做指令微调,让模型学会正确的推理链走向,推理时再用思维链提示来约束它的输出节奏。两者结合,效果远大于任何一个单独用。
3.3 边界词与模糊关系的处理技巧
时空关系里有一类特别麻烦的情况,就是“紧挨着”“在旁边”“在远处”这类带距离修饰的词。如果训练数据里只有“左边”“右边”这种绝对关系,模型一旦遇到“紧挨着左边”,就会把它和“左边”当成同一件事,导致边界判断错误。
我的处理方式是在合成数据生成时,对距离修饰做区间抽样。比如“紧挨着”对应坐标距离为1,“隔一个物体”对应距离为2,“在远处”对应距离大于5。这样模型能看到同一方向在不同距离修饰下的表达式变化,也更容易理解“紧挨着左边”并不是“左边远处”。
时间数据也有类似的边界问题:事件之间是“无间隔连续”还是“有间隔”,直接影响结束时间的计算。我把“紧接着”和“间隔了10分钟”区分开来生成,确保模型不会把间隔时间漏掉。
3.4 微调与提示词的配合:我实测下来的效果对比
为了说清楚“训练”和“推理策略”各自的贡献,我在模拟项目X里做了一组对照实验。评测集是一套我自建的时空推理题目,包含空间题、时间题和综合题,总数约1000道。结果大致如下:
| 实验设置 | 空间题准确率 | 时间题准确率 | 综合题准确率 |
|---|---|---|---|
| 底座模型,无微调,直接回答 | 52.4% | 48.1% | 35.7% |
| 底座模型,无微调,加思维链提示 | 61.3% | 55.2% | 42.8% |
| 底座模型,合成数据微调,直接回答 | 79.5% | 76.9% | 63.4% |
| 底座模型,合成数据微调,加思维链提示 | 88.2% | 84.6% | 72.5% |
从这组数据能明显看到:单靠提示词,提升幅度大约在8到10个百分点;单靠微调,提升幅度达到25到30个百分点;把两者组合起来,又是额外的8到10个百分点。这印证了一个观点:想提升语言模型的时空关系推理能力,训练数据是基础,推理策略是放大器,缺一不可。
4. 评测设计:没有一套“时空推理卷子”,提升就全是玄学
4.1 自建评测集的三层结构
做提升工作最忌讳的就是“感觉自己变强了”,但没有一套标准卷子。只有把评测集稳定下来,才能对每个改动做AB对比。
我搭的评测集分为三层。第一层是单步基础关系,覆盖最朴素的左右、前后、上下和先后顺序判断。第二层是链式多步推理,覆盖两步到三步的关系传递,比如“A在B的左边,B在C的右边,问A相对C的位置”。第三层是场景应用题,把空间和时间信息混在一个情境里,例如“某人从点A出发向北走150米,再向东走100米,用时12分钟,问最后位于出发点的哪个方向,以及总用时多少”。
每一层大概三分之一的题量。这个结构的价值在于,我可以随时知道模型到底是在哪个层级卡住的。是连单步关系都学不会,还是单步学会了但链式传递出错,处理方式完全不同。
4.2 评测时要同时看准确率和错误类型分布
只记录一个总准确率远远不够。我每次训练完一个版本,都会把错误样本按类型归类。主要分四类:
- 方向反转错:模型把“左边”或“右边”说反了,这种说明基础空间表征没建立好。
- 链式传递错:单步关系都正确,却无法把两步关系合并,这种说明多跳推理能力不够。
- 时间运算错:把分钟数加错、跨小时没有进位,这种说明时间计算的内隐能力不足。
- 读出条件错:题目里明明描述的是“猫在箱子右边”,模型回答时却当成“猫在箱子左边”,这种多半是注意力机制层面的问题,需要排查训练样本是否足够清晰可区分。
我自己的经验是:方向反转错和读出条件错,通常用增加高质量数据能解决;链式传递错需要调整推理步长的训练占比;时间运算错则建议在数据里专门加入大量“小时进位”的边界样本。
4.3 评测集设计里的两个暗坑
评测集本身也需要小心设计,不然会把评测结果带到沟里去。
第一个坑是题目歧义。有些空间描述天然有多解,比如“水杯在书本旁边”,“旁边”没有明确是左边还是右边,如果把这种题写进评测集,模型答什么在我这里都算错,但实际上它的回答未必不合理。解决办法是:评测集的每道题必须保证答案唯一,我处理时会给题目补足信息,比如“水杯在书本的左边”。
第二个坑是句式泄漏。如果你构造训练集和评测集时用了同一套模板,模型很可能靠背模板得分,而不是靠推理。为了避免这个问题,训练样本和评测样本的句式必须分开设计,我在评测集里刻意使用了训练集没有出现过的问法,这样才能测出真正的泛化能力。
5. 从数据构造到部署落地:完整复盘里最值得记的几笔
5.1 训练样本里必须包含“反例”和“矛盾样本”
早期我只生成“正向正确的样本”,模型做正题越来越准,但一旦遇到包含矛盾信息的题目,它还是容易强答。比如“猫在箱子左边,箱子在猫的左边,问猫和箱子的位置关系”,正确回答是“描述矛盾,无法判定”,模型却会给一个具体方位。
后来我在合成数据里专门加入了两个类别。第一类是条件反转样本,训练模型关注“如果”后面的条件变化。第二类是信息不足样本,训练模型在缺少参照对象或时间锚点时承认“无法判断”。加完这两种之后,模型在评测集上的得分又涨了一截,更关键的是,那种“一本正经胡说八道”的情况大幅减少了。
5.2 数据构造的顺序,比想象中更重要
我在最初几次实验里忽视了混合顺序的影响,直接把通用指令数据和合成时空数据混在一起喂给模型,结果模型的基础能力掉得比较厉害。后来测试了几种策略,比较稳妥的做法是先让模型充分学习通用指令数据,保持基础对话和通用能力,再用时空合成数据做后期增量训练。顺序安排比混合比例更容易被人忽略,但它对最终模型的影响是实打实的。
用一句话总结就是:通用能力决定模型的下限,时空推理数据决定模型的上限,但先保住下限,再谈上限。
5.3 排查“模型还是会答错”的标准化流程
如果你也遇到微调后模型依然答错的情况,建议按这个顺序排查,而不是盲目加数据。
- 第一步,确认评测题目本身没有歧义。把答错的题目拿给两个人分别读,如果他们给出的答案不一致,那这个错不算模型的问题,是评测题的问题。
- 第二步,把错误样本按句式聚类。如果错误集中在某一种句式,大概率是训练数据里这种句式的覆盖不足。我去看了一下,发现大量错误集中在“以X为参照物”这种句式上,因为生成模板里这种句式占比太少。
- 第三步,检查推理输出是否被截断。有些空间题需要写很长的推理链,如果输出长度限制太短,模型写到一半就会被强行截断,后面的结果无从谈起。
- 第四步,再看训练分布和评测分布的重合度。重合度过高说明模型在背题,重合度极低则可能说明评测难度超出了训练难度,需要调整数据。
我印象最深的一次排查,一批时间计算题总是错。一开始以为是分钟进位问题,后来把错误样本打印出来才发现,模型根本没有把“下午2点”解析成数字“14:00”,而是把它当成一个普通词贴进输出里。问题不在计算环节,而在时间表达解析环节。排查链路走一遍,你才能找到真正该补的那块短板。
我在做这个提升实验的过程中,最有感触的一点是:数据构造的“一致性”比“数量”重要得多。坐标、时间线、自然语言三者一旦不一致,模型练得越多,错得越顽固。如果你也想在语言模型的时空关系推理上做点提升,我建议从一开始就先把方向映射表和时间表达式固定下来,哪怕生成得慢一点,也要保证每一条样本在结构上是严格正确的。这个基础打好了,后面所有的微调、提示词、评测,才有意义。