文本相似性的应用场景会有很多,在工业界我粗略遇到过:
- 热点做舆情识别监控的时候,需要对全网文章进行聚合,聚合过程中需要知道哪些文章是一致的
- 推荐做相似内容召回的时候,需要对文章内容进行匹配,匹配过程中需要知道哪些文章是一致的
- 特殊分类场景,比如fake news这种样本集合较小的场景下,相似匹配可以用来补充分类结果
- ...
说到文本相似性可以有很多种划分的方式,从文章的长短可以分别处理,从计算的方式可以分为深度学习和机器学习方式,从实现目的上可以分为去重和匹配...
以下面两个例子举例:
0 伟大祖国让我们过的非常幸福
1 伟大的祖国让我们过的非常幸福
2 伟大的祖国是我们过的非常幸福原因
3 中国人民现在的日子真的很幸福
Hash方法
我们可以说0和1是相似的,因为它们之间只差了一个“的”,通常通过SimiHash或者MiniHash我们就可以解决这类问题
分词、hash、加权、合并、降维,这个是它们在常规流程。总结一下就是,根据各种权重赋值方式(比如tfidf)给出每个词的权重,通过hash的方式给每个词一个01表征方式,通过权重和表征结果得到加权结果,再根据加权结果map到0或者1,得到一串签名结果Fingerprints,再通过分段比对或者其他什么方式进行两句话的比对,看下差异是否在可接受的范围内,得到两句话是否一致。
这种方式通常用作去重,比如如果我们有一份非常庞大的样本集合,要去做去重,大家通常的做法就是在写hive sql或者spark sql的时候distinct一下,在做后续的自然语言处理任务,通过这种方式可以去除掉一些真·近似的文本,加快速度且干掉一些异常点非常有效,最重要的是超级快。
词袋方法
1和2由于相差的字数比较多,可能不是以一种hash方式去做能识别多出的。但是细心的朋友回发现,它们之间重合率非常高,通过分词后的比对也是可以识别的。
这边点名一下常见的倒排索引、杰卡德相似、onehot的cosine、切词后的词交集个数,句法依存,编辑距离等等,都可以实现,而且其中不乏一些非常高效的解决方案,相似框架faiss,l2正则化后稀疏矩阵点积,tire树做文本内容的存储以匹配词交叉的情况等等。
这种方式通常是应用在业务场景中了,它们通常不会作为一个自然语言理解必备的preprocessing工作去做,而是解决一系列要求高时效或者要求高相似的文本判断的场景下。
匹配方法
刚才为说到要求高相似,很多朋友可能不理解什么叫做高相似,对比1和2,2和3就会发现,大家对相似的标准其实是不一致的,在某些场景,比如热点榜单生成,1,2,3就是一件事情,大家都不喜欢在自己的热榜排序结果中重复出现1,2,3中的任意2条;而在信息流推荐中,2,3或者,1,3就是两件不同的事情,可能它们的文章有不同的侧重点。
如果需要识别出1,2,3是同一件事情,可能词袋方法也不适用,因为它们没啥重复的词,词袋就失效了。
- LDA
- TWE
- WordEmbedding
- SentenceEmbedding
- BERT
- supervised method
以上6种方式是工业界最常用的6种思路,不乏会有一些奇思妙想没有列出来,但是各大厂主力的方法一定包含在其上。
LDA
LDA是一个nlp工程师必会的技能,这边不展开讲,这边主要讲它能带来什么。LDA这个方法对一段文本的表征,它优秀在于考虑了当前文本和全局之间的关系,我在做文本匹配的时候,我不简简单单的立足在这个两篇文本是否一致,而是把它放在了全部数据集中,拼的是文本1、文本2在全量数据集中到底是个什么地位,在全部的文档库中这两个文本讲的到底是不是一类事情。
LDA提供的是两篇文本的主题的分布,得到这个量化后的分布后如何去比呢?cosine?其实大家在高代学习过程中一定听过相对熵这个逻辑,从这个角度上来看,是不是text1对text2的相对熵和text2对text1的相对熵的加权平均很合适,因为它们都是衡量的两个分布上的一致性,而主题恰巧是text本文的分布。对,这个方法就是JS散度。另外一种方式是Hellinger Distance,它的中文名叫概率分布之间的距离--海林格距离,话不多说,同理易得。
这边补充一下,百度的Familia就集成这两种方式去算相似度,异常好用。
TWE
用过LDA的人应该知道:LDA 产生的主题往往被高频词占据,这种现象导致低频词在实际应用中的作用非常有限。
知道LDA原理的人应该知道:LDA通常假设同一个句子里的词产生自同一主题,对句子内的词进行了进一步的建模。
显而易见,这些都是不合理的。
Topical Word Embedding (TWE) 利用 LDA 训练获得的主题为词向量的训练提供补充信息,进而得到词和主题的向量表示。也就是说,我得到了主题向量,我也得到词向量,我结合两者一起来补充信息,这样我既照顾到了句的主题,也没有损失词的多样性信息。
如何去生成:
- 我们将每个主题视为一个伪词(pseudo word),分别学习主题向量和词向量。然后根据向量wiwi和zizi构建主题词向量⟨wi,zi⟩⟨wi,zi⟩
- 我们将每个单词词-主题对⟨wi,zi⟩⟨wi,zi⟩视为一个伪词(pseudo word),并直接学习到主题词向量
- 我们分别为每个词和每个主题保留不同的嵌入向量,通过连接相应的单词和主题向量来建立每个单词-主题对的向量
法1精度差但是好实现,法2训练时间长但是效果更可接受,法3给单词embedded学习过程中引入了主题向量的影响。我现在一般用3,但是我觉得2是也是可接受的。
WordEmbedding
这段比较无脑,是个nlp工程师都用过的方式。GloVe,FastText,W2V,DSSM生成词的向量化表征结果,然后通过SIF,加权,Attention,平均,求和,Pooling等各种花里胡哨的方法然后在计算Cosine相似度。
这个方法一定是baseline,效果非常可观,这边不多说。只是给大家分享一些我使用的心得
- 找个靠谱的词向量做Fine-tune,不要随机。这边我是用的集团AI提供的词向量,开源的有腾讯16G,搜狗的20G,科大讯飞的8G结果
- 数据越多,越好。但是要的是分布均匀的数据,玩命怼一个方向的数据对提升效果作用=0,我在finetune的时候是打散后的38个垂类的1亿2000万条资讯文章,而且这些文章已经做了fingerprint过滤的
- 不要剔除过多的词,不要只保留实体。一些连接词的学习会带来句子的连惯性上的帮助。假如你有一天需要做seq2seq的任务呢?
- 添加<CLS>可以帮你索定词位置,引入可以帮助你解决一些语句合理性判断的问题
- 把过短的内容删掉,1个字的词删掉,纯数字删掉,如果是资讯文章最后的版权声明删掉,小细节注意一下
word embedding会带来一种问题,苹果和香蕉相关,“我下午想吃香蕉”和“我下午想吃苹果”,word embedding认为是一件事,但是实际可能并不是。
SentenceEmbedding
Sentence Embedding认为既然一个词可以是一个向量,一句话也可以是。这边的论文一堆,方法也一堆:doc2vec,Infersent,Sentence2Vec,skip-thought,Quick-Thought,FastSent。
以上这些是我看过还没忘的,我觉得比较有价值的。它们基于不同的思想,也有不同的作用,我这边谈几个比较经典的。
- doc2vec是word2vec翻版,通常的做法是在句首加一个<CLS>,学习到的<CLS>的向量作为句向量。
- Skip-thought利用中心句预测context中的句子,用了一个encoder来压缩中心句子的信息,然后用两个decoder来产生context里的句子,一个decoder用于预测前一句,另外一个decoder用于预测后一句。
QT针对这个问题,对decoder部分做了大的调整,它直接把decoder拿掉,取而代之的是一个classifier,使得预测行为变成了分类行为。就是用向量点积来判断同样的encoder的向量的相似度。 - Sentence2Vec是Skip-thought加强版
- Infersent是有监督的方法,用的是相似内容对,构造了<u,v,|u-v|,u*v>进行Entailment、contradiction and neutral三分类的预测
- FastSent,用上下文的词来预测目标句子的词,然后把目标句的词的和作为目标句的向量
句向量来补充词向量衡量一个句子的相似度会有明显的提升,我日常做baseline就是wordembedding+sentenceembedding来算cosine的
Bert
原理就不讲了,一万个人写过bolg了,这边给大家整理了一份易错知识点Bert知识点。
主要注意的是:
- 原始的bertbase别用,效果差,建议在自己的数据集上微调
- 不要用CLS或者词向量拼接的方式,建议拿倒数第一层的output向量,你需要的只是句子综合信息
- 这个速度真的慢,考虑场景,收益没有想的那么大
supervised method
有监督的方式大概有两种思路:
- fasttext/infersent这种用有监督的方式生成词向量或者句向量方式,再计算句词向量之间的cosine相似度
- 直接求解(Universal Sentence Encoder/DSSM)
- 第一步选择监督训练数据,设计相应的包含句子编码器Encoder的模型框架
- 第二步选择(设计)具体的句子编码器,包括DAN、基于LSTM、基于CNN和Transformer等
正儿八经的人用infersent或者fasttext这种就行了,越复杂的设计带来的风险在工业界也是难以hold住的。
以上这些,匹配方法可以得到2和3这种语句的聚合,但是需要考虑使用场景和收益率。
总结
讲道理,上面这些方法可以覆盖日常工作中的绝大多数文本相似度计算的方法,剩下就是如何去融合成一个适合场景的解决方案了。代码按大家的需要后续给大家开源。