![]()
你有没有想过一件事:当你在应用商店里搜"记账",为什么弹出来的第一个结果往往是个理财类App,而不是那个真正叫"记账"的软件?
这背后藏着搜索系统一个几十年都没彻底解决的老问题。今天要聊的这篇论文,来自苹果公司的研究团队,他们琢磨出了一个挺巧妙的办法,让两个AI模型互相"打配合",一个专门理解用户在想什么,一个专门理解商品是什么,然后训练它们说同一种语言。
先搞懂:搜索到底难在哪
搜索系统的第一道关卡叫做检索。
检索:从海量商品或内容库里,先筛出一批可能相关的候选集,交给后面的排序系统精挑细选。
这一步一旦出错就没法挽回。你想想,如果一个真正相关的App在检索阶段就被漏掉了,那不管后面排序算法多牛,用户都看不到它。反过来,如果检索阶段塞进来太多垃圾候选,后面的系统压力也会暴涨。所以检索系统必须同时兼顾两件事:召回率要高(别漏),精确率也要高(别乱塞)。
这两者其实是天然矛盾的。你要是把网撒得越大,捞的鱼越多,召回率越高,但混进来的杂鱼也越多,精确率就下降了。这就是所谓的**精确率-召回率权衡**,几乎是所有检索系统绕不开的宿命。
传统做法主要有两条路子。一条是老牌的关键词匹配,最经典的代表是BM25算法。
BM25:一种基于关键词出现频率和重要性打分的经典检索算法,通过倒排索引快速匹配查询词和文档词。
倒排索引:一种数据结构,记录每个词出现在哪些文档里,就像书末尾的索引页,查一个词能立刻定位到相关页码,不用整本书翻一遍。
BM25这类方法简单快、可解释性强,广告系统里广泛使用(广告主直接竞价关键词),但它的死穴是没法理解语义。你搜"手机坏了修不了",它未必能联想到"维修服务"这个词,因为字面上根本不重合。
于是第二条路是**稠密检索**,把查询和商品都映射成一串数字向量,通过向量之间的相似度来判断是否相关。
稠密检索:把文本转换成固定长度的数值向量(embedding),通过计算向量间的余弦相似度等方式衡量语义相似性,代表方法有DPR、ANCE等。
稠密检索能捕捉语义关联,但代价是丢掉了关键词系统原本的可解释性和基础设施兼容性,你没法直接看懂一个向量为什么和另一个向量相似。
近几年又冒出了第三条路,叫生成式检索,直接用语言模型生成商品的"身份标识符"来完成匹配。但这类方法对标识符设计的依赖极强,一旦标识符设计得不好,扩展性和泛化能力都会打折扣。
问题来了:这几年大语言模型这么火,大家很自然地想,能不能用LLM来帮检索系统一把?
已经有不少工作在做这件事了,比如用LLM去扩写用户的查询词,或者用LLM生成一些辅助数据去训练检索模型,甚至用检索反馈去微调LLM。但这篇论文的作者们发现了一个共性问题:几乎所有这些方法都只在"查询这一侧"下功夫,商品那一侧的表示方式基本没变,还是靠一个独立的下游检索器去完成最终匹配。
这就好比你努力提升自己的沟通表达能力,却指望对方原封不动地保持老样子还能听懂你。系统里始终有一侧是"不进化"的,那匹配效果的天花板自然被这一侧焊死了。
于是研究者们提出了一个问题:能不能让查询和商品两边都用LLM去生成关键词,让两边同时进化,直接在生成出来的关键词空间里做匹配?
这就是这篇论文的核心思路,论文管这个框架叫CoGR(Co-evolving Generative Retriever,共同进化生成式检索器)。
CoGR:一个训练两个独立LLM分别为查询和商品生成关键词的检索框架,两边生成的关键词通过倒排索引直接匹配,兼容现有的关键词检索基础设施。
两个AI模型,一个学说"用户话",一个学说"商品话"
CoGR的基本设计其实挺直白。给定一个用户查询,用一个叫做**查询侧生成器**的LLM,生成一组关键词。给定一个商品,用**商品侧生成器**这个LLM,也生成一组关键词。然后系统看两边的关键词集合有没有重合,重合了就算匹配上,再用BM25给检索结果排个序。
这个设计本身不难理解,难的是怎么让这两套关键词"对得上话"。你想啊,如果查询侧LLM生成的关键词都是"省钱""划算"这种偏口语化的表达,而商品侧LLM生成的关键词都是"折扣""促销"这种偏商品属性的词,即便语义上很接近,字面上完全对不上,那关键词匹配这套机制就彻底失效了。
这就好比两个人明明想表达同一个意思,一个说方言,一个说普通话,内容一致,但字面上根本对不上号。如果不想办法让他们说统一的语言,再怎么聊都是鸡同鸭讲。
所以CoGR分成两个阶段来解决这个对齐问题。
第一阶段:监督微调打地基
第一阶段叫**监督微调**,简单说就是先给两个生成器一个"共同语言"的初始版本。
监督微调(SFT):用人工标注或构造好的数据对预训练模型进行有监督训练,让模型学会特定任务的输出模式。
具体怎么做的?论文里的算法思路是这样的:先用原始的基础LLM给每个商品生成一批关键词,得到商品侧的初始关键词集合。然后,对每个查询,把它所有相关商品的关键词都收集起来,取出现频率最高的一批词,作为这个查询的目标关键词。
这个设计的巧妙之处在于,它人为地在"相关的查询-商品对"之间制造了关键词重合。你查询的目标关键词本身就是从相关商品的关键词里筛出来的,两边天然就有交集。这就像是给两个刚认识、语言不通的人先塞一本共同的短语手册,让他们至少能用几个共通的词打个招呼,不至于完全零基础地互相摸索。
如果跳过这一步会怎样?论文的消融实验(后面会细说)给出了答案:直接从零开始做强化学习,效果明显更差。相当于让两个语言不通的人在完全没有共同词汇的情况下就开始谈判,能达成一致,但过程会曲折得多,效果也打折扣。
第二阶段:让两个AI相互切磋,共同进化
这是全文最有意思的部分,论文标题里"It Takes Two to Match"(一个巴掌拍不响)说的就是这个。
第二阶段用的是**强化学习**,具体算法是GRPO。
强化学习(RL):让模型通过与环境交互获得奖励信号,不断调整策略以最大化累积奖励的训练方法,不同于监督学习需要标准答案,而是通过"好不好"的反馈来学习。
GRPO:一种强化学习算法,全称是Group Relative Policy Optimization,通过给同一个输入采样多个候选输出,比较组内相对好坏来计算优势信号,从而更新模型。
训练方式是交替进行的:先固定商品侧的索引不动,只训练查询侧生成器;训练好之后,反过来固定查询侧索引,训练商品侧生成器;如此循环往复。
为什么要这样交替,而不是两边一起训练?
这里有个很现实的技术考量。如果两边同时训练、同时变化,那对每一侧来说,它面对的"环境"(也就是对方生成的关键词)是一直在晃动的靶子,你刚学会怎么打中一个位置,对方立刻又变了。这种情况下训练会很不稳定,很难收敛。
论文的做法是每次只让一侧变化,另一侧冻结,相当于给训练中的那一方一个固定的靶子去瞄准。等这一轮训练完,再把刚训练好的一方"冻住",换另一方去适应它。这就像是两位棋手轮流下棋而不是同时移动棋子,如果两人同时乱动棋盘,谁都摸不清对方到底想干嘛,棋局根本没法进行。只有一方走一步、稳定下来,另一方才能针对性地应对。这种交替更新的策略,本质上是用"轮流"换来了"稳定"。
那么,两边各自的奖励信号是怎么设计的?这才是整篇论文最精细的地方。
查询侧的奖励:直接看检索效果好不好
对查询侧来说,奖励信号很直接,用**F1分数**。
F1分数:综合考虑精确率(检索结果里有多少是真正相关的)和召回率(所有相关结果里有多少被检索到了)的调和平均数,是一个平衡两者的常用指标。
对每个查询,生成一组关键词,去匹配商品索引,看检索出来的商品集合里,有多少真的是相关商品(这是精确率),又覆盖了多少应该被检索到的相关商品(这是召回率),最后用F1把这两者揉到一起,作为奖励信号喂给GRPO去优化查询侧的模型。
论文里还提了一句很实在的话:在实际的商业系统里,精确率关系到用户体验(别让用户看到一堆不相关的垃圾),召回率关系到潜在收入(别把能赚钱的相关商品漏掉)。F1分数把这两者都照顾到了,是个务实的选择。
为了防止模型钻空子,生成一大堆关键词硬撑召回率,论文还设了一个关键词数量上限,超过这个上限直接给零分,这算是一个简单粗暴但有效的约束。
商品侧的奖励:这个词到底有没有用,要"扣掉"别人的功劳
如果说查询侧的奖励设计还算直观,商品侧的奖励设计就要绕个弯子了,这也是全文技术含量最高的部分。
问题出在哪?你没法直接套用查询侧那套逻辑给商品打分。为什么?因为商品的关键词好不好,不能光看它自己"检索到了多少相关查询",这个角度是错的,检索系统真正的目标是"查询能不能找到相关商品",而不是反过来。
论文管这个叫**反事实边际奖励**,我觉得这是整篇论文设计最精妙的地方。
反事实边际奖励:通过对比"替换某个商品的关键词前后",整个系统检索质量发生的变化,来衡量这组新关键词到底带来了多少净贡献,而不是孤立地评价这组关键词本身好不好。
具体怎么操作?论文的思路是这样的:先固定查询侧的索引,把当前商品侧的关键词状态当作"参考基准"。然后,只把某一个商品的关键词换成新采样出来的候选关键词集合,其他所有商品的关键词都原封不动。这时候重新跑一遍所有查询的检索,计算出一个新的总体F1分数,把这个新分数减去替换前的参考分数,差值就是这组新关键词的奖励。
打个比方,这就像评价一个足球运动员在某场比赛里踢得好不好,不能光看他自己进了几个球、抢了多少次断,更靠谱的方法是看"如果把他换下场,全队的比分会有什么变化"。如果换下他之后球队输得更惨,说明他确实起了作用;如果没什么影响,说明他的贡献被高估了。这种"控制变量法"式的评估,剥离了其他因素的干扰,精准定位到这一个商品的关键词到底值不值。
如果不这样做,直接让商品自己去优化"检索到尽可能多相关查询"会怎样?论文里专门设计了一个对照实验叫"Transposed F1"(转置F1),就是把商品当成查询、反过来做检索匹配,测这种对称设计的效果。结果这个变体在Internal数据集上的F1只有0.3743,而完整版CoGR是0.3963,差了两个百分点。这说明商品侧不能简单地"照葫芦画瓢"套用查询侧的逻辑,反事实边际奖励这种更精细的设计确实是必要的。
这里还有个工程上的巧思值得一提。按理说,每换一次某个商品的关键词就要把所有查询重新跑一遍检索,这个计算量大得吓人。论文的解决办法是,只关注这次替换实际影响到的那一小撮查询,因为一个商品的关键词变了,只有那些原本能检索到它、或者现在新检索到它的查询,F1分数才会变,其余绝大多数查询根本不受影响,直接用缓存好的历史统计量就能算出结果,不用重新跑一遍全量检索。这个效率优化虽然是个技术细节,但恰恰是让这套复杂奖励机制能在实际系统里跑起来的关键。
十个对手,两个数据集,CoGR全赢了
光有设计思路不够,得看效果。
论文选了两个数据集来验证:一个是苹果内部的APP应用商店搜索数据,训练集1.35万条查询,验证集1500条,商品库有3.96万个应用,平均每个查询大概对应1000个相关应用;另一个是公开的WANDS商品搜索数据集(来自家居电商Wayfair),规模小一些,训练集430条查询,验证集50条,商品库4.3万个产品,平均每个查询约200个相关商品。
对比的基线方法覆盖了三大流派,一共10种代表性方法:
稀疏检索阵营里有经典的BM25和学习式稀疏检索方法SPLADE-v2;稠密检索阵营里有DPR、ANCE,以及用更大规模模型Qwen3-Embedding-4B做的零样本和微调版本;生成式检索阵营里有DSI、DSI-QG、RIPOR,还有一个跟CoGR思路比较接近的DeepRetrieval(它只训练查询侧的语言模型去改写查询,不动商品侧)。
结果如下(用全量检索的F1分数衡量,这是最核心的综合指标):
在Internal数据集上,表现最强的基线是ANCE-Qwen4B,F1是0.3575。CoGR 4B(完整双侧优化版本)达到了0.3963,相比最强基线提升了10.9%。
在WANDS数据集上,最强基线依然是ANCE-Qwen4B,F1是0.5012。CoGR 4B达到了0.6819,提升幅度高达36.1%。
这里有个特别值得琢磨的对比。论文专门做了一个"冻结商品侧"的CoGR变体(表格里标注为CoGR\*),也就是只训练查询侧、商品侧参数完全不动。这个变体在Internal上的F1是0.2617,在WANDS上是0.4662,虽然比大部分传统基线要强,但比完整版CoGR差了不少(Internal差了约0.135,WANDS差了约0.216)。这个数据非常直接地证明了论文的核心论点:只优化一侧是不够的,两边必须一起进化。
如果你只训练查询侧会怎样?答案已经摆在这里了:性能损失能达到20个百分点以上(以WANDS为例,从0.6819掉到0.4662,降幅超过30%)。这就好比谈恋爱只有一方在努力学习理解对方、调整自己的表达方式,另一方原地踏步,关系或许会有改善,但天花板很低,因为沟通的鸿沟只填上了一半。
训练过程也不是拍脑袋定的一两轮就完事,论文进行了5轮查询侧和商品侧的交替训练。有意思的是,从训练曲线看,第一轮的提升最大(从初始的约0.16跳到约0.28),后面几轮的提升幅度逐渐变小、趋于平稳,最终稳定在0.40左右。这说明这套"交替共同进化"的机制不仅有效,而且是稳定收敛的,不会训练着训着就跑飞了。
关键词也在悄悄"进化",越来越精准、越来越像
除了整体性能,论文还专门分析了训练过程中关键词本身发生了什么变化,这部分挺有意思,能帮我们理解模型到底学到了什么。
第一个发现是关键词变得越来越具体。训练前,很多关键词是"mobile"(移动的)、"fun"(有趣的)这种放之四海而皆准的泛泛之词,几乎不带信息量。训练之后,这些笼统的词被淘汰,取而代之的是"cash management"(现金管理)、"survival horror"(生存恐怖)这类更精准描述内容的多词短语。用数据说话:单个词的关键词占比从37%降到了13%,三个词及以上的长短语占比从12%涨到了31%。
这个变化其实符合直觉。想想看,如果你在应用商店搜"好玩的游戏",这个词太宽泛了,几乎所有游戏App都能匹配上,检索出来的候选集又大又不精准。但如果关键词是"roguelike survival horror"(肉鸽生存恐怖),能匹配上的商品范围一下子就收窄了,精确率自然上升。强化学习用F1作为奖励,本质上就是在惩罚这种"广撒网但不精准"的行为,逼着模型往更具体、更有区分度的词汇上靠拢。
第二个发现更有意思:查询侧和商品侧的词汇量在训练过程中逐渐趋同。刚开始的时候,商品侧词汇量很大(因为商品描述本身信息量丰富),查询侧词汇量很小(用户的搜索词通常很短)。但经过几轮训练之后,两边的独特关键词数量逐渐接近,论文里给出的最终数字是商品侧约8.2万个,查询侧约8.7万个,几乎收敛到同一个量级。
这个现象恰好印证了前面提到的"共同语言"这个核心思路:两个原本表达习惯完全不同的系统,经过反复的博弈式训练,居然真的在词汇丰富度上趋同了。这不是设计者提前规定好的,而是训练过程自然涌现出来的结果,挺让人意外的。
多喂点信息,效果还能更上一层楼
论文还做了一个挺实用的补充实验:如果给两个生成器多一点上下文信息,效果会不会更好?
对商品侧,默认输入包含应用的标题和描述。如果把描述去掉,只留标题,F1从0.3963掉到0.3759,说明描述里那些标题没法覆盖的细节信息,确实在帮检索出力。
对查询侧,如果在提示词里加入现有搜索系统返回的搜索结果作为额外参考,F1能从0.3963提升到0.4379。论文举了几个具体例子说明这个提升从哪来:面对拼写错误、指代不清、涉及具体实体名称、或者非英语的查询(比如中文查询"解压软件"),额外的搜索结果能帮生成器先把查询的真实意图搞清楚,再去生成关键词,准确率自然更高。
这个结果不算特别意外,但很有实际意义:它说明CoGR这套框架不是封闭的,可以很自然地接入更多外部信息源来进一步提升效果,工程落地的灵活性还是有的。
这项工作站在了哪些人的肩膀上,又往前迈了一步
检索这个领域的技术演进路径其实挺清晰的。稀疏检索的BM25之后,出现了SPLADE这类学习式稀疏方法,试图用神经网络学习词项权重而不是死板的统计公式。稠密检索这条线上,DPR开创了双塔编码器的范式,随后ANCE用更巧妙的负样本挖掘策略进一步提升效果,GTR则把编码器规模做大来提升泛化能力。
生成式检索这条线更年轻一些,DSI最早提出用序列到序列模型直接生成文档的"语义ID",后续的RIPOR把检索导向的量化表示引入进来构建标识符。另外还有一条支线是用词汇本身作为标识符(比如GENRE直接用文档标题、SEAL用文档子串),这条路径和CoGR的关键词生成思路在精神上更接近。
而利用大语言模型做检索反馈优化这个方向,DeepRetrieval是一个关键的参照系,它训练一个LLM去改写查询、直接用检索指标作为奖励信号,这个思路和CoGR的查询侧强化学习几乎是一致的。但DeepRetrieval止步于查询侧,商品那端还是老一套的检索器。CoGR往前走的这一步,就是把商品侧也纳入了同一套优化闭环,让两边不再是"一个进化、一个静止"的不对称关系。
论文里还提了一嘴,这个思路和最近一些"自我进化"的LLM系统研究(比如R-Zero、G-Zero这类工作)在精神上是相通的:多个角色互相博弈、共同进步,而不是单方面被动接受监督信号。这算是给CoGR找到了一个更大的技术脉络归属。
写在后面
读完这篇论文,最让我意外的一点其实是那个"反事实边际奖励"的设计。一开始我以为商品侧的奖励会直接照搬查询侧的逻辑,毕竟对称设计听起来很优雅。但论文用实验证明,这种看似合理的对称设计(Transposed F1)反而效果更差。这说明检索系统里查询和商品的角色并不是完全对等的:查询是"发起方",商品是"被检索方",评价一个商品关键词好不好,天然应该放到整个检索系统的因果链条里去看,而不是孤立地评价它自己"检索"能力强不强。这个细节让我重新想了一下,很多看似对称的问题,背后可能藏着不对称的因果结构,直接照搬对称设计未必是最优解。
另一个值得琢磨的地方是,论文选的两个数据集都强调"平均每个查询对应几百到上千个相关商品",这和很多学术界常用的检索评测集(往往一个查询只对应几个相关文档)差异很大。这个选择其实暗含了研究者的一个判断:真实的商业检索场景里,相关性远比学术数据集里假设的要"稠密"得多,这也是为什么F1这种精确率召回率平衡的指标,比单纯的Top-K命中率更贴近业务实际需求。
论文最后也留了一个开放的口子:现在的排序阶段还是用BM25,如果换成更强的排序模型,这套框架的潜力还有多大空间没被挖出来?这个问题我倒是挺想知道答案的。
Q&A
Q1:CoGR是什么?
A:CoGR是苹果研究团队提出的一种检索框架,训练两个独立的大语言模型分别为用户查询和商品生成关键词,两边生成的关键词通过倒排索引直接匹配完成检索,同时兼容现有的关键词检索基础设施。
Q2:CoGR相比传统检索方法效果提升有多少?
A:在苹果内部APP应用商店数据集上,CoGR比最强基线方法F1分数提升了10.9%;在公开的WANDS商品搜索数据集上,提升幅度达到36.1%,两个数据集上都超过了稀疏、稠密和生成式检索的十种代表性对比方法。
Q3:为什么CoGR要让查询侧和商品侧交替训练而不是同时训练?
A:因为如果两侧同时变化,每一方面对的匹配对象都是一直在晃动的目标,训练会很不稳定。CoGR采用交替更新策略,每次只训练一侧、冻结另一侧作为固定环境,这样能让训练过程更稳定地收敛。

