心法利器
本栏目主要和大家一起讨论近期自己学习的心得和体会,与大家一起成长。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。
2022年新一版的文章合集已经发布,累计已经60w字了,获取方式看这里:CS的陋室60w字原创算法经验分享-2022版。
往期回顾
向量表征召回之前有聊过(心法利器[16] | 向量表征和向量召回),作为检索的一种比较新潮的方式,现在也成为了很多人尝试一大选项,效果上,也有很大的提升,然而好像很多人的尝试看来效果也不是那么好,这里就聊聊,向量表征召回和字面召回的区别与关系,并且给出一些选型的建议。另外,这点也和上期:心法利器[61] | 规则到模型的过渡和升级策略,有很大的关联性,大家可以一起食用。
先说字面召回,字面召回是一种比较经典的检索召回模式,即根据用户query的字面内容进行直接的召回检索,例如“深圳的天气”就会召回一些和“天气”或者是“深圳”相关的内容,召回的方式和字面直接相关,title基本会带有和query相同的词汇。当然了,如何召回,内部的粗排是怎么做的,基本原理就是常言的倒排索引了,具体的话,可以参考《信息检索导论》这本书,里面会有详细的解释。
字面召回其实是旧时代的产物,早期的搜索一般都是通过字面召回来实现的,经典的ElasticSearch就是一个非常简单快速的字面检索工具,甚至还带有一些数字等的召回功能,拥有比较完善的搜索体系。
然后来聊聊他的优缺点吧,首先是优点:
表征和入库的性能极高,不需要进行复杂的向量表征推理,直接入库就能构造索引。
在线召回的性能也很高,同样是省去了表征query的时间。
可解释性和可控性强。
下限会比较高,因为召回的内容基本都会和query存在字面交集。
在粗排的相似度上,本身可以是无监督的,不需要标注样本进行训练。
但是也有缺点:
泛化能力弱,无关词、词典都可能会影响召回质量。
相似度本身可优化的程度低,天花板比较明显。
从上面的分析,其实字面的召回非常适合早期的baseline版本,因为需要的资源少,甚至都不需要训练,所以非常适合。
向量召回,就是把query和物料都表征成向量的模式,在检索的过程是直接用这个向量来进行召回,即在库里面找到与query向量最接近的那个向量。
那么,也是来聊聊优缺点吧,首先是优点:
泛化能力极强,同时也赋予了很高的上限。
对句子的变化敏感性较低,鲁棒性比较强。
缺点的话有这些:
依赖质量足够的数据进行训练。
要提升上限需要有一些性能上的代价,因为向量表征本身需要一些深度学习的推理。
有些领域可能并不需要很高的泛化能力,例如一些专有名词,擅自泛化可能得到的还是不好的效果。
其实可以看到,向量召回似乎更适合在后期承担锦上添花的功能,能为整个检索系统提上线,但是因为这些缺点,可能需要一些谨慎性。
项目早期的目标一般是先快速构造一个baseline,完成整个检索系统的基本功能,算法效果上能达到一个可用的水平,例如准确率80%左右吧,因为早期除了算法本身的工作,还有大量基础工作需要完成,例如数据构造、推理、检索等工作,需要花费大量时间,而且业务开展好坏的前提是现有开展,所以对于我们而言,可用比用得好的优先级更高,此时,直接追求更高上限的意义不是很大,而是应该保证下限,下限保证不住的话项目上不了线,项目周期很容易崩,例如直接上预训练模型,效果调优、性能优化、工程架构之类的都有不小的时间压力,前期更多倾向于可控、可用。
从前面的讨论可以看到,字面召回很适合前期搜索项目初步建立的阶段,因为前期很难弄到比较好的训练数据,训练的方式可能不行,而且字面匹配本身背后是要求字面相似的,这就能很大程度保证下限,最相似的几个基本大差不大,相似度稍微远的部分也能保证有一定的相似性,在用户体验上不至于很差。
可能会有人说,开放域的预训练模型可以提供无监督的方案,而且已经有比较不错甚至不输有监督的效果,例如simcse等,甚至更进一步,即使没有标注数据,纯语料也能做一些领域内的进一步学习,然而值得注意的是,领域内的相似度可能会和开放域的相似度概念存在不同,如果没有直接用没有进行过有监督的训练,可能会有不小的问题,而相比之下,字面召回和字面的相似度可能对这块的控制能更加稳定一些,甚至能自定义一些单向的相似度(A能映射B,但是B不能映射到A,例如集合的概念就是如此),字面相似度是更控制的。
所以综合来说,前期不太建议用向量化召回,可以直接考虑字面召回,结合字面相似度(例如cqr、ctr:心法利器[18] | cqr&ctr:文本匹配的破城长矛)来进行计算,完成快速的baseline的搭建,后续再来迭代,这样能保证先让程序跑起来,有个基本的基线,数据可以借助这种字面召回来收集(甚至包括hard case的挖掘),这个方案在一些小调整之后,能很快达到上线标准。
在项目有一定的baseline以后,要开始进行效果的调优了,但是这个效果调优,并非只有换模型(扎个心,想想自己是不是除了换模型就没有别的方法了),有的时候优化和清洗数据,调整规则,甚至是在相似度上调调关键词的权重就已经能有不小的提升,因为我们在项目早期追求更多的是可用,而不是更优,所以我们在这个阶段是希望能够尽量探索到现有方案的天花板,毕竟在现有版本进行小幅度调整的成本更低,而且,现阶段其实还有很多工作没有完成的很好,例如数据的质量,尤其是训练集和测试集,这些提升不仅对现有的方案有用,对未来我们上模型或者切换更大更好地模型都有好处,毕竟数据是限制模型上限的瓶颈,在较差的方案下清理这些问题,对未来有很大好处。
有了这种考量,其实我们虽然可以适当尝试模型,但是重点其实还是优化字面相似度和字面召回,召回质量本质上依赖的是3点,索引数据质量,相似度计算,以及预处理,对相似度计算,如果是类似BM25的方案,那其实空间不是很大,如果是cqr、ctr之类的可以自定义的方案,则可以考虑做一些微调,例如词权重的引入等,而对索引数据质量,更多是清洗数据,清除可能存在的错误数据,也看好数据库的覆盖情况看有没有机会补充下(例如用在线随机样本分析索引覆盖情况,也就是看用户希望知道的内容在库里面有没有),至于query预处理,则是要看是否要做一些丢词或者关键词抽取来辅助召回,提升召回质量。
所以,总结下来,此阶段的优化更多关注现有方案的进一步优化,尽可能探到现有框架的天花板。
当我们发现现有方案的基础上难以提升的时候,就是我们应该做方案大版本升级的时候,对前面以字面检索为主的方案,这才是我们可以开始正式聚焦并关注模型提升的时候。此时,我们就可以开始进行一些简单的向量召回方案了。
开始我们需要先构造好基于模型的离在线推理pipeline,离线有训练和效果评估,可能还包括模型的打包和部署,物料层面还有预测和索引构造存储,在线则是query的向量推理和检索。
这里想展开说的是2点,第一个是更新的时机,另一个是这次更新后字面和向量召回在线的关系。
首先是为什么这会才是更新的时机:
现有方案已经接触到天花板,要提升只能从根本上更新方案。
之前的数据和基础工作都不成熟,直接上模型的工作量很大,会很大程度影响项目周期,此时在线pipeline基本完成后,对排期的要求就没有那么紧了。
然后是,这次更新后字面和向量召回的关系。如果这是第一次向量召回方案的上线,那此时,并不是下掉字面召回的时机,此时可以考虑两者同时在线,因为向量召回很难完全覆盖字面相似度召回的内容,当且仅当向量召回能基本覆盖字面召回的内容才可以考虑这点,此时可以看做两者是两路召回,最终做个精排就好了。
后续则要根据项目的形态来决定最终形态如何,如果产品内用户对字面的要求更高,其实字面召回仍旧要考虑保留,例如音乐之类的,用户对字面内容更加敏感,除非向量能做到字面的事(但如果为了解决字面的问题来调整向量召回,在线可能零提升)。
另一方面,模型上的优化空间肯定更大的,在数据准备妥当的前提下,小模型到大模型,大模型到simcse之类的对比学习方案,或者是使用类似amsoftmax之类的损失方案,都会有不同程度的提升,模型的泛化能力和鲁棒性都会提升。
字面召回顾名思义,其实会更加倾向于字面内容的匹配,召回的结果在绝大部分的情况下,会真的和query很接近,这也决定了他高下限的性质,所以非常适合前期baseline的构造,而在后期,则同样有兜底,处理长尾的作用,甚至在一些场景下,能一直用到关服。
而向量召回,需要不少的基础性工作,搭建起来成本较高,而且效果调优也有很大的不稳定性,所以不太适合前期使用,但是他所代表的其实是上限,因为他有很强的泛化能力,对于用户输入的口语化等问题,都很需要模型的能力。
所以,这也就导致了,字面作为下限类能力,放在项目里能很好地保证系统的下限,前期是核心,后期也能一定程度上兜底,而向量召回,表征能力本身来源于底层的模型,因为模型它本身是有很强的泛化能力的,但是这种泛化其实并不好控制,而且构造pipeline的成本较高,因此前期直接上肯定不合适,而更加合适的是在基础工作逐渐完善后,提上限使用。
文章写完后,感觉这篇文章似乎就已经不只适用于字面和向量召回的权衡了,更像是模型和规则之间的权衡吧,感觉思考下来确实是有收获。另外,这篇文章本来我写的目标是为了下一篇文章做铺垫,涉及向量召回和语义表征这块的,大家可以期待下。