1、检索式对话
1)q-q匹配:召回候选答案
倒排索引+TFIDF/BM25
tfidf https://zhuanlan.zhihu.com/p/113017752
BM25 https://zhuanlan.zhihu.com/p/113224707
2)q-a匹配:深度文本匹配模型
Utterance-Response Matching
①Multi-view(EMNLP2016 百度自然语言处理部)
从单轮q-r的匹配扩展到多轮呢?最简单的想法就是直接把多轮对话首尾连接变成一个长长的单轮
- 首先将各轮的对话连接起来(连接处插入"__SOS__"的token),然后这里用RNN系网络取最后时刻隐态的方法分别得到query和response的向量表示,
- 得到匹配分值,得到匹配概率。本质上就是一个基于表示的文本匹配模型,所以完全可以用更复杂的表示方法和匹配函数
对应这种将长长的word embedding sequence,
- 不仅要在这个word-level上进行匹配,而且还要在一个更高的level上进行匹配,
- 这个level称为utterance-level(即把对话中的每条文本(utterance)看作word)。
上图的绿色->黄色->红色的部分,
- 首先得到对话的每条文本(utterance)的向量表示(这里用的14年Kim提出的那个经典CNN),这样历史的多轮对话就变成了一个utterance embedding sequence。
- 之后再通过一层Gated RNN(GRU、LSTM等)把无用的utterances中的噪声滤掉,进而取最后一个时刻的隐状态得到整个多轮对话(context)的context embedding啦。
- 拿到context embedding后,就可以跟之前word-level中的做法一样,得到对话与candidate response的匹配概率啦。
- 最后,将word-level得到的匹配概率与utterance-level得到的匹配概率加起来就是最终的结果。
可以看到utterance-level确实是明显比word-level work的,而且集成一下提升效果更显著。
②SMN(ACL2017 MSRA 吴俣 )
MatchPyramid(AAAI2016中科院计算所)
- 使用传统的attention来计算出两个文本的word-level对齐矩阵/相似度矩阵后,
- 将该矩阵看成一个图像,然后使用图像分类模型(如CNN)来得到更高level的相似度特征表示(比如phrase level, segment level等),
- 进而最终得到全局的相似度匹配特征。这也是最早的几个交互式文本匹配模型之一。
SMN
给定一个candidate response,在生成word-level的每个utterance的向量表示的时候,
- 首先计算出历史上每个utterance跟该response的对齐矩阵,
- 然后对每个对齐矩阵,均使用上面这种图像分类的思想生成high-level表征文本对相似度的特征向量作为该utterance的向量表示(utterance embedding)。
之后就是使用前面Multi-view中的做法,
- 从这个utterance embedding sequence中得到整个对话的context embedding,
- 最后将该context embedding和之前的word-level下得到的context embedding与response的向量去计算相似度了。
在计算对齐矩阵和得到context embedding的时候,用了更复杂一些的方法。如图
在计算对齐矩阵的时候,
- 作者不仅用了原始的word embedding,
- 而且同时用了RNN系模型对文本encoding之后的隐状态(即编码过上下文信息的word embedding,可以看作phrase-level的"word embedding"了),这样就生成了两份对齐矩阵,
- 然后这样将两份对齐矩阵作为两个channel丢进“图像分类模型”,从而保证了即使图像分类模型很浅,也能抽取出比较high-level的特征,得到高质量的utterance embedding。
从实验效果来看,SMN相比较之前的Multi-view有很大的提升,这也说明了:
- 在q-r匹配上,基于交互的模型相比基于表示的模型有更大的优势
- 对文本进行多粒度表示是很有必要的。
以往的想法是,无论怎样都要先encode成一条向量才可以开始算相似度。
将特征矩阵做矩阵乘法得到相似度矩阵(对于segment还是用一个可训练矩阵来乘)
- 沿用Multi-View思想,原embedding矩阵当作word-level feature,通过GRU后的作为segment-level feature.
- 相对于用多层CNN提取的方法,先提取高层特征,再进行CNN操作能确保网络能学习到高层的信息,而不必依赖CNN的玄学
- 把提取出来的相似度矩阵看做是一张图,用CNN+maxpooling来进行提取。
1、文本相似度
①编辑距离
https://blog.csdn.net/wxgxgp/article/details/104184418
import Levenshtein https://blog.csdn.net/IOT_victor/article/details/106670275
②simhash
https://blog.csdn.net/wxgxgp/article/details/104106867
LSH https://blog.csdn.net/IOT_victor/article/details/104044453
局部敏感hash(LSH)实现方式有多种,常用的就是simhash。
- 基于simhash和汉明距离的文本相似性计算适用于长文本,对短文本的识别是很不准确的。
- 另外,由于任何hash函数都不可避免的存在冲突,所以,也会出现完全不相干的两段文本计算的hash值相同,导致汉明距离很小而误判为相似。
计算出simhash值后,再计算hash值的汉明距离,即可得到文本的相似性。
汉明距离
- 两个长度相同的字符串对应位字符不同的个数
- 这里的长度相同并不是指输入的字符串长度相同,而是指经过计算后得到的hash字符的长度(一般固定为64位)。
汉明距离计算文本相似性主要6个步骤为:分词、hash、加权、合并、降维、计算汉明距离,前5个步骤本质上是simhash算法的流程,思路很简单易懂。
-
分词:分词工具有很多,例如Ansj、JieBa、HanLP等等
-
hash(64位)
使用MurmurHash3.hash64(byte[] data)
来得到词语的hash值。需要注意的是它返回的是Long类型,如果转换为二进制表示后不足64位,需要在补齐64位(高位补0) -
加权:词频是衡量一个词语在句子中的常用方法之一,除此之外还有TF-IDF、词语的情感色彩值等等,本文使用词频作为词语的权重。
-
合并:对每个词语计算到的加权后的hash值按位求和
-
降维:对求和后的数据进行降维,即对于每一位,大于0的位变为1,小于0的位变为0,得到一个二进制数(或者字符串),即为最终的simhash值。
-
计算汉明距离
对以上步骤得到的两个simhash值计算其汉明距离,即统计两个64位的二进制数中对应位不同的个数(异或后1的个数),最终得到汉明距离。一般根据经验值,汉明距离小于等于3的即可认为相似。
汉明距离为一个整数,似乎不能很直观的反应两个文本的相似度(0 ~ 1),所以这里通过实验的方法找了条类似正态分布的函数来将汉明距离转化为一个0~1之间的数来表示相似度
③余弦相似度
https://blog.csdn.net/wxgxgp/article/details/104146034
④jaccard
(jaccard系数值越大,相似度越高) https://blog.csdn.net/wxgxgp/article/details/104235559
分词:A = [今天,天气,真好],B = [今天,天气,不错]
2、搜索流程
上游的query预处理、纠错拓展、意图识别,到下游的召回、精排
- 预处理和纠错,涉及到文本生成、文本共现挖掘等方面的技术。
- 意图识别,最直接的使用就是文本分类。
- 召回阶段,需要根据term weighting进行重要性算分,可供粗排使用。
- 精排部分,需要计算query和doc的匹配度,那就是文本匹配、语义相似度方面的内容了
对于原始query,需要处理:
- 预处理。繁体简体,大写小写,标点符号,数字等等
- 改写。招商银行和招行。
- 意图分类。对应是确定哪个库哪个表。
- 实体识别、term weighting等。对应的是哪个字段。
NLU,如文本分类、实体识别、文本匹配之类
NLG,即文本生成但是在查询拓展、纠错等方面
①数据预处理模块
用户query进来,一般要做如下处理:
- 大小写转化。
- 删除标点符号(当然有的分析会保留标点,但是建议在这个场景下还是去掉更好)。
- 繁体转简体。
- 数字处理,转中文,转阿拉伯数字,甚至罗马字等吗,部分情况要删除数字。
- 长度截断。(总不能让用户输入一本西游记,然后你还在整吧?)
理解模块
query理解其实是一个非常重要的模块,好的query理解能为后续的工作提供很大的支持。这部分往往不直接应对下游,而是按需使用,有点像辅助吧。
- 分词。基操。
- 关键词抽取。会涉及丢词、排序等功能。
- 命名实体识别。一方面协助用户识别,另一方面可能会涉及数据库查询匹配的内容。在垂搜中比较常见,大搜也有但是相比之下没那么精准和常见。
- 紧密度分析,避免由于切词出现错误导致词义偏移的问题,这个其实并不少见,尤其是在人名上,这个是别的精准度会很低,近期的如“武大靖”,会被分为“武大 靖”,“曾舜晞”直接被分为了3个字,挺头疼的。
- 非法信息过滤。例如黄色、暴力血腥、反动等。
改写模块
改写模块其实非常关键,这是连接用户query和数据库底层数据的桥梁,数据库的存储有特定的形式,但是用户不会按照你的底层数据结构去写,例如,用户不见得会输入和平精英,而是吃鸡,数据库里可不见得会存吃鸡对吧,所以这块很重要。
- 同义词改写。上面的吃鸡就要改写为和平精英,这个需要通过同义词挖掘等方式去构造词典实现。
- 拼音改写。数据库是罗密欧与朱丽叶,但是用户输入的是罗密欧与朱莉业,拼音改写其实颇为常见,用户经常由于找不到需要的结果或者不知道应该需要哪个,于是直接输入后开始搜索。
- 前缀补全。非常常见,用户输入射雕,射雕英雄传就要出了,这个一般的方法也是构造词典,另外有一个很重要的需要了解的就是前缀树,这个能让你查询的时间非常低的水平(只和query长度本身有关)。
- 丢词和留词。结合上述关键词提取和命名实体识别完成,有些不必要的词汇需要被删除,例如“李小璐到底怎么了”,整个句子只有李小璐是关键词,其他词如果也通过and逻辑召回,就没有信息召回了,这时候其实可以直接删除或者将降级到or逻辑。留词和丢词相反,丢词如果是做减法,留词就是做加法。
- 近义词召回。这个召回不是从数据库中召回,而是召回近义词,具体的方法是通过embedding方法转化词汇,然后通过ball tree、simhash的方式召回与之意思相近的词汇,该模式虽然比较激进,但是能一定程度增加召回,有一定效果。
- 意图识别。与其说是意图,我更喜欢理解为这是直接针对底层数据结构产生的需要解决的问题,query这是一条,但是数据库有好几个,我们要去哪个数据库搜,这是需要识别的,而这个数据库的设计往往和品类、意图有关,找酒店、找景点都是不同的,所以此时就要进行意图识别,一般地方法是抽象为文本分类,但是很多时候语义本身是无法体现出真实意图的,例如少年的你,语义上其实很难分析出,有时候更复杂的会夹带一些实体识别、词性、词典之类的信息。
③召回模块
结合命名实体识别、改写结果,然后开始召回,模式比较多,包括但不限于下面形式:
- Elastic Search,著名的搜索平台ES,有些时候甚至简单的搜索平台直接用它管整个搜索引擎。但是要精做,就只能把它当做底层的数据库和搜索平台。
- 关系数据库。MySQL之类的,但是在我的理解速度和并发性都不是特别好。
- Redis。KV形式的数据库,速度很快,但是Key匹配必须是精确匹配,这需要改写模块具有很强的能力。
说白了,就是把用上面流程处理过的query放到数据库里面查,这个其实就是召回。
④排序模块
内容是召回回来了,其实怎么展现给用户呢?这里是需要深度挖掘用户的实际需求的,很多时候甚至需要做个性化,但是更多时候是我们能够得到的只有短短的一句用户query,那么,我们就要好好利用好这个query,来做好排序,让用户喜欢的(当然,还有广告商喜欢的哈哈哈)东西放在前面,实际上就是一个学习排序的问题了,那么在特征层面,可以考虑如下的信息:
- query层面:
- 本身的embedding,后续迭代可以走elmo后逐渐形成的pre-train的模式。
- 词性、实体、词权重、offset等序列标注得到的结果。
- document层面。
- 如果文档本身有文本,最好是标题类的,也把embeding引入,和query层面那种一样。如果只有query文本和document文本,其实就是个文本相似度模型了对吧。
- 综合层面:
- query和document的ctr、cqr、bm25,句向量余弦相似度等匹配信息。
- 其他层面:
- 意图识别结果。
模型上,DSSM系列似乎是比较流行的方法,但是提取一些特定的特征,有的时候简单的LR、XGBOOST就能一定程度把问题解决好了。
而在排序模块中,还会涉及一些硬规则等。
搜索引导模块
query suggestion是一个上述搜索过程非常类似的模块,只不过处理的结果大部分是放在离线,在线是指一个查字典的过程,那么离线部分,其实是做了一整个搜索过程的:预处理、query理解、改写、召回、排序。
- 预处理:和上面的预处理类似,不赘述。
- query理解,和上述内容类似,但是有的时候会简化,直接进入改写模块。
- 改写模块,会进行容忍度更高的前缀匹配,去找回一些用户可能会喜欢的内容,这时候往往还会带上统计特征,从整体层面上看用户喜欢的内容,毕竟不同时间用户喜欢的可能不同,例如对热点新闻的偏好。
- 召回和排序模块。召回模块不是召回数据了,而是召回相似的doc信息,也可以是相似的历史用户query,尤其是后者,如果是后者,则要确认历史用户query是有结果的。
其他辅助模块
显然,整个搜索系统远远不止这些内容,在算法视角下,其实还需要很多辅助模块协助我们进行算法开发。
- 日志模块。无论是线上的运行日志,还是线下的模型实验和离线模型运行,都需要日志协助,对于线上运行和离线模型运行而言,出现错误可以方便追溯,找到ERROR出现的时间和具体问题,而线下的模型试验能方便我们计算运行时间、找到bug,而非每次训练模型的时候功亏一篑才来找问题。
- 实验模块。由于算法策略存在很多不确定性,无论是算法内存占用和时间,还是算法实际结果,因此需要利用AB实验的方式来验证,快速进行分析,对有问题的算法及时下线检查原因。
- 数据分析和报表模块。结合实验模块,需要对日常用户数据进行分析,这个比较好理解,不赘述。
- 特征生产模块。特征生产涉及两块,一个是线上的实时计算,对于一些实时数据,是需要即时生成的,例如微博热搜里面就需要一些诸如5分钟搜索量之类的特征,这个只能从实时计算模块中获取;另一个是离线模块,为了进行离线模型训练,需要定时将生肉(原始数据)转化为特征,方便进行下一步计算,如果这个能变成日常任务,那开发人员就不需要临时造轮子执行,还要长时间等待。
- 定时任务模块。很多任务其实是定时开始的,报表生成、特征生产,甚至是一些实时任务(说白了就是短时间内的计算),因此有定时任务模块去管理这些定时任务,将会非常方便。
- 模型管理模块。首先一个项目中可能存在大量模型,然后因为各个模型训练需要花费大量资源,还要结合AB试验,另外还有模型的更新和保存机制(防止模型加载失败导致的无模型可用),因此需要构建模型模块统一管理模型,这个不是每个部门都有,或者说每个模型都是各自管理,但是有一个比较好管理模型平台是非常高效的。
- 数据平台。额,简单地说就是写SQL的地方,但是也有类似ETL之类的内容,和特征生产模块很接近。