知识库检索匹配的服务化实践
一、背景
知识库是企业经营过程中的面向客户和内部员工的知识沉淀文档库,里面包含各类教程、问答、案例等,知识库的检索匹配是自然语言处理(NLP)中一个重要的基础问题,本质是进行文本语义的相似度计算,也就是语义匹配,我们很多领域的任务都可以抽象为文本匹配检索任务,例如检索引擎、智能客服、知识检索、信息推荐等领域。
知识库检索匹配可以概述为:给定一个query和大量候选知识库的文档,从这些文档中找出与用户输入query最匹配的TopK个文档。
二、架构流程
2.1、整体架构
2.2、请求链路
三、算法模型
3.1、DSL改写
检索优化第一步:DSL改写,接手前业务方自己已经对检索结果做过优化,调整不同字段的匹配权重,这一方法的已经难以继续优化。从知识运营的角度出发,在用户检索时,将运营认为重要的文档推到前面,由于文档之间互相有链接引用,可以使用PageRank算法给每个文档计算重要分(PR值)。
PageRank的核心思想是,被引用次数越多的文档越重要。算法原理如下,假设只有四个网页ABCD,以AB间的箭头为例,代表可以从B网页跳转到A网页,对B即一次引用(链出),对A则一次被引用(链入)。L(B) 表示B网页的链出数量,PR(B)表示B网页的PageRank分数。
假设所有文档的初始PR值是0.25,这里L(B)=2,L(C)=1,L(D)=3,计算出PR(A)=0.458,接下来计算所有其他被引用(有链入)的文档PR值,PageRank是个迭代算法,反复计算以后所有的PR值会收敛,那就是最终每个文档的PR值,也是用来改写DSL的关键信息:
new score = old score * log(1+2*PageRank)
old score指原来不同字段加不同权重由ES算出来的BM25分数,PageRank缺失值使用1代替。
3.2、文本召回
文本召回是召回中最常用的一种策略,最常见的方式是通过对Query直接进行分词,然后将分词后的关键词到ES构建倒排索引,进行tf-idf等相似计算匹配索引召回,这种召回方式的优点是实现简单,不需要训练模型、低资源需求、检索速度快,然而它的缺点也很明显,文本是具有语义的、是有语法结构的,文本召回忽略了语句的语法结构,同时也无法解决一词多义和同义词的问题,对 query 进行语义层面相似的召回效果就比较一般,解决这个问题就要用到向量召回。
3.3、向量召回
向量召回的思想就是计算搜索词的向量和文档标题/相似问的向量的余弦相似度,返回相似度分数最高的TopK个文档,计算向量相似度的步骤放在Milvus进行,Milvus作为向量检索库,对计算过程有优化,性能更好。
向量计算模型结构:
文本转向量的算法模型由embedding、两层transformer和MLP组成,模型最后会对编码向量做L2归一化,采用典型的双塔模式,可以将左塔的搜索词和右塔的文档标题形成独立的子网络,左右塔的结构分离但编码器参数共享,双塔结构天然的可以用于召回,将这个模型部署到小盒子就可以在线计算搜索词的向量,将海量的知识库文档作为右塔离线训练成文本向量后刷入向量检索工具Milvus。
双塔召回模型的核心思想是将query/item嵌入到共享低维空间,然后通过向量距离来度量相关性。
向量召回流程:
工程实现部分的DP离线任务中实现了计算文档标题和相似问向量并将其导入Milvus的功能。由于Milvus对string类型属性信息存储检索不够友好,所以在DB阶段会请求mysql库表对召回结果进行扩展,匹配补全相关信息。
3.4、精排序
经过召回和粗排后,可以理解为将重要相关的文档排在了前面,但是距离用户真正的检索意图还有差距,可以使用用户的检索记录对结果再进行排序。
基于所有场景的用户检索点击数据,有点击行为就认为检索词和文档标题匹配(正样本),其他就认为没有那么匹配(负样本)
训练数据样例:
采用 in_batch 负采样就不需要提前构造负样本,模型的设计如下:
检索词与正负样本的相似度会进入InfoNCE(info Noise Contrastive Estimation,噪声对比估计)的函数计算损失,使用这个损失来更新模型参数。这个由很多负样本组成的双塔结构也称为对比学习,核心思想是去拉近相似的样本,推开不相似的样本,目标是要从样本中学习到一个好的语义表示空间。
精排模型与文本转向量的算法原理相同。
InfoNCE计算公式:(可以理解为带温度超参的CrossEntropy)
L_i=-log(\frac{exp(sim(z_i, z_i^+)/\tau)}{\sum_j{exp(sim(z_i, z_j)/\tau)}})
分子是正例对的相似度,分母是正例对+所有负例对的相似度,最小化infoNCE loss,就是最大化正例对的相似度,最小化负例对的相似度。
在计算损失时,label可以在batch内生成,检索词和文档的编码向量经过矩阵乘法可以得到一个相似度方阵,对角位置就是互相匹配的检索词和文档的分数,如果batch size=4,那每行对应的label就是 [0,1,2,3]。inbatch负采样损失计算示意图:
模型训练好以后,就得到文本的编码器,输入两个文本,就可以得到一个匹配的分数,将这个模型部署到小盒子,在需要排序时,输入候选的文档标题和检索词,按计算出来的分数从高到低排序,就完成了一次对检索结果的排序。
3.5、排序优化
上述向量召回介绍的在模型服务中计算两个文本相似度的方法,在只需要对20个文档(一页)排序时是没有问题的,但是每个文档还会有若干个相似问,只使用20个商品标题没法很好的代表整个文档,如果能使用每个文档的标题和全部相似问,那效果会更稳定一些,但这样的话,每次进入小盒子排序的文档数就不固定了,少则20,多则几百,上百的batch_size走小盒子很容易超时,就算切成小的batch,也会有一个失败全部失败的情况,而且总体上rt也没下降多少。
与向量召回时类似,模型也改为输入只有一个文本,输出这个文本的向量。每个文档的标题和全部相似问向量都与Query向量算相似度后计算均值,等价于先计算文档的标题和全部相似问的向量均值,再与Query向量计算相似度。基于此,排序任务也可以转换为向量召回任务。
在进入Milvus之前,会按照向量召回和ES召回的文档ID作为过滤条件,一次计算的RT就得以保证,可以支持的QPS也可以比基于小盒子的版本高:
此方法可以解决RT和QPS问题,但是也有局限,只能优化“每个文档的标题和全部相似问向量都与Query向量算相似度后计算均值”这个均值计算逻辑,其他的比如“取最大的相似度”就不能这么做了,而且Query与文档的交互太少,只在最后算相似度,可能不如多次交互的模型的效果好。
四、工程实现
当线上接受一条检索请求文本后,先调用在线推理-小盒子计算Query向量,然后去Milvus向量库中和知识库向量进行相似度计算,并返回距离最近的Top N个Item作为向量召回的结果。
4.1、离线训练(DP平台)
海量的知识语料库向量化计算在自研DP平台离线运行,使得全库文本匹配速度较快:
1)语料库预处理:包括语料库的文本清洗、文本筛选等预处理逻辑
2)语料库向量化:利用上述的向量计算模型进行向量化
3)导入Milvus库:将集合部署在Milvus集群,依次批量导入更新机器的集合保证线上可用
4.2、在线推理(Sunfish平台)
自研算法平台(Sunfish)对模型训练提供一站式闭环服务,支持分布式训练、GPU/CPU切换、模型版本管理、一键式运行和部署等功能,其中:
1)算法工程模块:一键运行训练、任务作业管理、模型输出
2)模型管理模块:实现训练任务、地址导入、本地上传等多途径模型来源选择
3)模型部署模块:简单配置、模型格式选择、线上资源配置等便捷署方式
{
"inputs":[
{
"name":"INPUT",
"shape":[
1,
1
],
"datatype":"BYTES",
"data":[
"满足条件满减送没有赠品"
]
}
],
"outputs":[
{
"name":"OUTPUT"
}
]
}
4.3、Milvus向量检索
Milvus 是一款开源的、针对海量特征向量的向量相似性检索(ANNS,Approximately nearest neighbor search)引擎,集成了 Faiss、Annoy 等广泛应用的向量索引,成本更低、性能更好、高度灵活、稳定可靠以及高速查询等特点,十亿向量检索仅毫秒响应。
1、Milvus向量索引列表如下:
简言之,每种索引都有自己的适用场景,如何选择合适的索引可以简单遵循如下原则:
1)当查询数据规模小,且需要 100%查询召回率时,用 FLAT;
2)当需要高性能查询,且要求召回率尽可能高时,用 IVF_FLAT;
3)当需要高性能查询,且磁盘、内存、显存资源有限时,用 IVFSQ8H;
4)当需要高性能查询,且磁盘、内存资源有限,且只有 CPU 资源时,用 IVFSQ8。
2、Milvus 目前支持的距离计算方式与数据格式、索引类型之间的兼容关系:
选择合适的距离计算方式比较向量间的距离,能很大程度地提高数据分类和聚类性能,主要采用内积 (IP)的计算方式,内积更适合计算向量的方向。
内积计算两条向量之间的夹角余弦,并返回相应的点积。内积距离的计算公式为:
假设有 A 和 B 两条向量,则 ||A|| 与 ||B|| 分别代表 A 和 B 归一化后的值。cosθ 代表 A 与 B 之间的余弦夹角。
在向量归一化之后,内积与余弦相似度等价。因此 Milvus 并没有单独提供余弦相似度作为向量距离计算方式。
4.4、AI模型接口服务
算法模型接口服务由ai-service和ai-app两个服务组成,ai-service负责调用算法模型在线推理、Milvus实时向量召回等接入库,ai-app负责业务逻辑的开发。
1、ai-service配置示例:
{
"model_name": "similarity_jira",
"model_source_type": "YZ_MODEL",
"model_version": 1,
"model_invoke_timeout": 3000,
"protocol": "kfserving",
"infer_type": "triton",
"feature_maps": [
{
"model_feature_key": "INPUT",
"data_type": "string",
"shape": "(-1,1)",
"default_value": "",
"feature_source": "PARAMS",
"source_key": "jira_text",
"is_required": 1
}
],
"param_mapping": {
"jira_text": "<objectList.jira_text>"
}
}
2、ai-app接口设计
实现业务逻辑开发测试后,发布上线即可提供前后端调用。
a、Maven示例:
<dependency>
<groupId>com.youzan</groupId>
<artifactId>ai-app-api</artifactId>
<version>1.0.13-RELEASE</version>
</dependency>
b、请求示例:
invoke com.youzan.ai.app.api.service.jira.Service.retrieve({"fromApp":"test","scene":"similarity_predict","Title":"满足条件没有赠品","Key":"XXX"})
c、返回示例:
{
"code":200,
"data":{
"Similaritys":[
{
"createdAt":1648137600000,
"score":0.9390,
"key":"XXX0123442334",
"title":"满足条件没有赠品"
},
{
"createdAt":1636214400000,
"score":0.9010,
"key":"XXX0123365819",
"title":"满足条件没有送赠品"
},
{
"createdAt":1653408000000,
"score":0.8735,
"key":"XXX0123482446",
"title":"订单满足条件没有送赠品"
},
{
"createdAt":1655308800000,
"score":0.8312,
"key":"XXX0123496337",
"title":"订单满足条件但是没有送赠品"
},
{
"createdAt":1659628800000,
"score":0.8028,
"key":"XXX0123527965",
"title":"订单满条件但是赠品没有送"
}
]
},
"success":true,
"message":"successful"
}
五、服务场景
5.1、官网帮助中心
5.2、相似商品推荐
六、总结
本文大致介绍了知识库检索匹配的算法和工程服务化实践过程,服务应用的业务场景比较广泛,并对类似场景的接入做了低代码封装和策略配置,便于快速轻量级的服务化落地。在知识库检索匹配服务化实践过程中,后续值得关注以下几点:
1)对于知识库中低频或缺失的新文档或新商品的Embedding学习还不够充分,可考虑利用图结算法结构,把更多query和其他属性的语义信息聚合,进而提升query和知识库文档的语义表征能力;
2)Embedding 的实现算法比较多,可考虑结合业务需要,将词嵌入模型模块内置化;
3)服务的性能和稳定性,确保服务在高准确率和高QPS下的响应性能依然是重中之重。
七、招聘号外
有赞数据中台团队,目前有数据平台研发以及数据开发岗位虚位以待,有需要的同学请关注官网。