在信息爆炸的时代,获取有价值的内容离不开检索算法,传统的检索算法主要是通过计算文本检索词和文档相关性排序获得结果,而BERT等预训练模型横空出世之后,向量检索也成了一种新方法。
文本检索
传统文本检索方法最出名的是BM25算法,这也是众多检索引擎(Lucene,Elastic Search,Xapian)的核心算法,它们应对查询语句的方式主要是切词等一系列处理方法。
BM25的公式如下所示: $$ ∑_i^nW(q_i)Score(q_i,D)Score(q_i,Q) $$ 其中:
- $q_i$ 表示查询语句中的第 i 个单词,D 表示查询的文档,Q 表示整个查询语句;
- $W(q_i)$ 表示单词权重,也就是TF-IDF算法里面的IDF:$ln(1+[(N-df_i+0.5)/(df_i+0.5)])$ ,其中N表示索引全部文档数,$df_i$ 表示包含检索词文档的个数。当这个词在文档中出现频率越高,该词对于最终分数的贡献就越小;
- $Score(q_i,D)$ 表示单词与文档的相关性:$[(k_1+1)tf_{td}]/[k_1(1-b+b*(L_d/L{ave}))+tf_{td}]$ ,其中 $k_1$ 是非负超参数,来标准化文章的词频范围,默认为1.2;b 是范围0-1的超参数,来决定文档长度包含信息量的范围,默认为0.75; $tf_{td}$ 是单词t在文档d中的词频,可以认为出现次数越多,分数越高;$L_d$ 是文档d的长度,$L{ave}$ 是所有文档的平均长度,可以认为同样的频率下,长文档带来的增益比短文档小得多;
- $Score(q_i,Q)$ 表示单词与查询语句的相关性,当查询的语句较长的时候这一项比较重要:$[(k_3+1)tf_{tq}]/[k_3+tf_{tq}]$ ,其中 $k_3$ 是非负超参数,来调节query的词频范围,$tf_{tq}$ 是单词t在查询语句q中的词频。
在实际使用中,由于知识库的文档质量问题,很可能导致 IDF 指标无法正确衡量指定词汇的权重,特别是某些重点词在知识库频繁出现,甚至可能和停用词频率接近。为了应对这个问题,可以试图引入自定义词频,或者录入部分外部文本来平衡知识库。
除此之外,这类算法的关键在于精确匹配能力,所以一词多义问题很难解决,通过自定义停用词表、同义词表可以获得一定程度的泛化性能。而难点在于这是采用真正的“文本”匹配,所以如果存在语义上的一致而文本表达方面不一致的情况,即使使用同义词改写也不会有很好的命中效果;同时因为不考虑语序问题,所以很可能出现语义飘移问题,即命中词一致,顺序不一致导致语义不一致。
向量检索
向量检索并不像文本检索那样将检索语句拆成一个个词去进行查找匹配,它们的主要手段是通过一些模型获得句子的向量表征,并通过一些向量检索引擎去获取文本库中较为接近的句向量。
句子嵌入本质上是对文本序列中信息的压缩,而压缩本质上是有损的。这意味着句子嵌入是粒度较低级别的表示形式。模型的瓶颈并不完全在于单个向量的表示能力不足,编码器的能力也会在很大程度上影响模型的泛化能力。
由此可知,向量检索具有良好的语义相似性,无需100%的命中关键词也可以匹配文档,但下列问题仍需要重视:深度模型的选择、知识库的构建以及检索算法的选择,下面将分别进行阐述。
模型选择
模型要考量的因素很多:
- 输入序列长度:关系到输入限制,超长文本可能截断导致语义不完整;
- 向量维度问题:关系到向量数据库性能,在文本量较多,且效果差距不大的情况下,高维向量会给向量数据库的检索和部署带来很大压力;
- 语义表征问题:未经垂直领域 Fine-tune 的模型通常效果不会太理想。
另外,很明显的一个问题,由于目前训练算法导致精确匹配能力不足,针对专有名词的精准召回效果不佳,对于N vs 1,1 vs N 的语义理解情况支持不佳。
目前最常用的句子表征模型训练方法是对比学习,训练样本由三部分组成:[ query,query+,query- ]。一般采用 infoNCE loss 作为优化目标: $$ L_i = −log(e^{S(z_i,z_i^+)/τ}/∑_{j=0}^K e^{S(z_i,z_j)/τ}) $$ 这个 loss 看起来很像套了 log 的 Softmax。其中,S 函数两个向量的余弦相似度, $τ$ 是控制难样本关注度的温度系数,趋向于 0 时更关注难负样本。
下面两个是比较典型的通用句子向量和它们采用的训练方法:
-
M3E:in-batch负采样对比学习 + Instruction Tuning 指令微调
-
BGE:RetroMAE预训练(Encoder+简单结构Decoder) + 负采样和难负样例挖掘 + Instruction Tuning 指令微调
知识库构建
知识库构建的最大问题是文档的切分,如何保证切分合理,并且维持语义完整性。这个问题需要结合实际业务进行考量:
- 对于 FAQ 任务来说,按照 QA 对进行拆分是比较好的,向量检索很容易可以使用 Query-Question 召回,但是当用户输入的问题不一定和 FAQ 的 Question 的提问角度一致时,可能Query-Documentary召回会更好一点,二者之间的协同是一个难题;
- 对于长文本来说,可以借鉴Langchain的简单粗暴切分法,就是按照 ‘\n’ 进行段落拆分,超长的话直接切断,这样可能会造成一些关键信息的拆离,针对特殊结构文档可以考虑使用更细节的拆分方式。
向量检索算法
在常用的向量检索引擎中,索引类型分为如下几类:
-
FLAT:最基础的索引结构,一定可以找到全局最优解,适用于数据量较小的情况。
-
IVF(Inverted File):倒排快速索引,基于决策树的索引,将数据分为多个子空间存储索引信息,增量更新不可以。在向量中如何使用倒排呢?可以拿出每个聚类中心下的向量ID,每个中心ID后面挂上一堆非中心向量,每次查询向量的时候找到最近的几个中心ID,分别搜索这几个中心下的非中心向量。通过减小搜索范围,提升搜索效率。
PQ:乘积量化,将一个向量的维度切成x段,每段分别进行检索,每段向量的检索结果取交集后得出最后的TopK。因此速度很快,而且占用内存较小,召回率也相对较高。
-
HNSW:图检索方案,把库中的结点随机插入图中,每次插入结点的时候都找图中和被插入结点最近的M个每个结点连边,构建索引非常慢,内存占用较大;
-
Annoy:树检索方案,空间划分过程可以看作聚类数为2的KMeans过程,收敛后在产生的两个聚类中心连线之间建立一条垂线,把数据空间划分为两部分。
计量类型有如下几种:余弦、内积、L2、Jaccacd。归一化后计算的欧式距离是关于余弦相似的单调函数,可以认为归一化后,余弦相似与欧式距离效果是一致的(欧式距离越小等价于余弦相似度越大)。
二者对比和融合
向量匹配和BM25的对比试验,相关论文显示:
- BM25精确匹配能力强,但是在应对多义词、OOV、变体词方面存在不足;
- 向量匹配具有BM25不具备的软匹配能力,但粒度问题难以评估、训练相似性与业务相似性的差距、重复语句处理、OOV问题、长尾数据均衡、精确匹配的任务仍需要研究;
- 二者对于头部文档相关性的定义有着很大的不同,向量匹配会高估不相关文档,而BM25会低估语义相关文档。
向量模型召回结果的 Rerank
前面提到了两种召回,如何结合这两路不在一个体系的结果是一个问题。如果是大模型场景,可以借助大模型的信息抽取能力,简单采用 TopK 去重做倒数加权的方式。而对于非大模型场景,一般考虑训练一个排序模型来进行相关性打分。而排序模型的训练又是另一个重要领域了,这里暂时先挖坑。