3.1.1. BM25是否该淘汰了?搜索和问答傻傻分不清楚?本篇带你刨析文本匹配技术,解开这些谜底——基于检索的智能问答(IRQA)和搜索引擎背后的故事
[TOC]
3.1.1.1. 搜索和问答的异同
- 基于检索的问答系统(IRQA)是指提前预设问答对,通过匹配问题返回预设答案的技术。
- 智能问答技术大致分为IRQA、KBQA、MRC,在之后的文章中会逐步介绍。
- 搜索引擎与问答系统中的语义匹配大同小异。
- 【问答大多长文本,搜索大多短文本?】——其实并非都是如此,实际场景是用户短文本长文本都有,无论是问答还是搜索两种都得做支撑,具体还是看场景。
- 【问答只是语义匹配,搜索有些会有个性化?】——其实也并非都是如此,有些场景的问答需要根据用户历史的喜好返回不同的内容,如“XX,放首歌给我”,随机放可不会满足用户需求。当然搜索的排序建模是从根上个性化的,问答这种场景的个性化往往是命中这个意图之后,调用另一套推荐系统。
- 要非得找个区别,那就问答只返回一个,搜索返回多个把!(笑),但实际上,当不能很好的匹配问题库时,会返回一些相关的推荐问题,这也不是返回一个了吧。
所以从表象上,搜索和问答三方面的特征各自都是具有的;但从内核上搜索的目的更多偏向点击率提高而非真的从语义上最相似。
接下来将延续以往的风格【能用俩字就不用仨字】阐述文本匹配技术
3.1.1.2. 关键词召回
为什么都到了预训练大模型时代,还需要关键词召回?David Rau and Jaap Kamps做了相关实验,实验表明:

对于高度相关的文档有17%(CE@100: 12% + CE@500: 5%)的文档,被排在后面,而BM25排在top10,相关的也有类似的结论。
有一些高度相关的文档两种方法都排在很后面
神经网络软匹配能力虽强,但query term这种精确匹配能力却远远比不上BM25。David Rau and Jaap Kamps通过把文档中的非query term用[mask]掩码进行实验得到此结论。
TREC 2020 Deep Learning Track数据集实验

保持原样和非query term用[mask]掩码对比

3.1.1.2.1. BM25
关键词目前最常用的是BM25,BM25由三部分组成,【词权重】、【词与文档相关性】、【词与query相关性】 词权重取$IDF(q_i)$ ,$N$为文档总数,$n(q_i)$ 为出现$q_i$的文档数,$|D|$为文档长度,$avgdl$为文档平均长度,$f(q_i,d)$为在文档中的词频,$f(q_i,Q)$为在文档中的词频,$k_1,k_3,b$为调节参数。
由公式,当b等于0时,词与文档匹配分数与文档长度无关;当$k_1$等于0时,词与文档相关性与文档中词频无关;当$k_3$等于0时,词与query相关性与query中词频无关无关。其中词与query相关性$k_3$等于0(蓝色),$k_3$等于2(红色)如下图所示:

根据业务的不同,调节相应参数,同时利用$bool$查询综合多种查询策略,例如:match、match_phrase、match_phrase_prefix、term等以适应业务
3.1.1.2.2. ES拓展近义词
config目录下创建analysis文件夹创建自己的同义词文件synonym.txt
A,B=>C D,ED,E出现D和E都会同时检索D、E;出现A,B都会替换为C。
重启ES
创建索引库
{ "settings": { "index": { "analysis": { "analyzer": { "my_analyzer": { "tokenizer": "smartcn", "filter": ["my_synonym"] } }, "filter": { "my_synonym": { "type": "synonym", "synonyms_path": "analysis/synonym.txt" } } } } }, "mappings": { "properties": { "title": { "type": "text", "analyzer": "my_analyzer" } } } }
3.1.1.3. 语义召回
关于如何得到句子向量,笔者曾经用很长的篇幅阐述了句子表征的来龙去脉【???????】,这里就不再赘述了。语义召回可以有多路,例如一路simcse,一路主题模型
3.1.1.3.1. 特征式
由于召回是面向所有数据集的,交互式计算相似度复杂度高,合适的方案是用特征式之后使用faiss/annoy/milvus进行向量检索。
所以语义召回层更合适的模型如bert-whitening等由原始bert衍生的以及simcse等对比学习系列模型。具体的还是看这篇文章【???????】。
3.1.1.3.2. 交互式
如果想使用交互式,也是有方案可寻的。使用矩阵分解的方式,分解交互式打分函数 (q:query k:document $q \in \mathcal {Q} \quad k\in \mathcal {K}$)
即 ($F\in \mathbb{R} ^{|Q|×|U|}、G\in \mathbb{R} ^{|U|×|V|}、H \in \mathbb{R} ^{|V|×|K|}$)
将$u$视为$\mathcal K$的一部分(即某些列,选取一些有代表性的文档);将$v$视为$\mathcal Q$的一部分(即某些行,选取一些有代表性的query),于是:
$F∩H$是指$F$某些行$H$某些列交集的元素拼成的矩阵,即$G=( F\cap H ) ^ { \dagger }$
所以可以提前计算G和H,查询时,只需要计算q与u的相似度($|U|$次计算),再与GH相乘即可。选取有代表性的文档和query可使用聚类的方式。
3.1.1.4. 排序
由于搜索和问答的数据来源以及对个性化的要求不同,排序的建模有所不同。
3.1.1.4.1. 问答
合并之前的关键词和语义召回数,问答利用用户的配置的问答对语料进行有监督训练。一个更精确的交互式的模型更符合这一层的设置,例如可以选直接用bert [CLS]位置作为输出,输入使用【句子1+\
class SimModel(torch.nn.Module):
def __init__(self,model_name='bert-base-chinese',freeze_bert=False):
super(SimModel,self).__init__()
self.bert = BertModel.from_pretrained(model_name)
if freeze_bert:
for p in self.bert.parameters():
p.requires_grad=False
self.linear = torch.nn.Linear(768,1)
def forward(self,input_ids,attention_mask):
outputs = self.bert(input_ids, attention_mask=attention_mask)
sentences_embeddings=outputs.pooler_output.squeeze(1)
out=self.linear(sentences_embeddings).squeeze(1)
return out
3.1.1.4.2. 搜索
搜索场景非常宽泛,不同场景有不同的策略,但更多的场景是为了提升点击率,更多的倾向于推荐系统,而非纯NLP。
- 搜索的训练数据主要来源于埋点,天然具有个性化。数据量超大时,需要分成两层粗排、精排。
- 搜索的召回中一般也会加入一些个性化召回如FM万能油。
- 粗排可以是精排的蒸馏,也可以直接训练一个轻量级的排序模型如LR省心小宝贝。
- 粗排的作用此时相当于对数据进行了分层,之后对每一层进行精排。
3.1.1.5. 引用
- 信息检索导论
- How Different are Pre-trained Transformers for Text Ranking?
- https://en.wikipedia.org/wiki/Okapi_BM25
- 句子表征 前世今生
- https://spaces.ac.cn/archives/9336
