微信公众号:NLP从入门到放弃
本文涉及到的代码:https://github.com/DA-southampton/NLP_ability/blob/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/%E6%96%87%E6%9C%AC%E5%8C%B9%E9%85%8D_%E6%96%87%E6%9C%AC%E7%9B%B8%E4%BC%BC%E5%BA%A6/src/models.py
如果想要获取本文PDF版本,查看这个文章获取对应文章PDF:https://mp.weixin.qq.com/s?__biz=MzIyNTY1MDUwNQ==&mid=2247484174&idx=1&sn=da89d66cb09b39297144dd50cfe10820&chksm=e87d3128df0ab83eeaa5fa1c3bcf6ffb1bb3b46d1471f26e3cfb92c2a0b1e686876cc2f809ff&token=1539851539&lang=zh_CN#rd
先问一个核心问题,为啥需要文本相似度/文本匹配?换句话说,文本匹配的应用场景有哪些?
举个例子,比如说海量文本去重。
在一些社交媒体,某个话题之下,存在大量的营销号的文本,这些文本存在一个特点,内容大同小异,都在说同一个事情。
我在对这些文本进行处理的时候,本质上只需要处理其中的一条就可以,这样可以极大提高我的处理速度。
那么问题来了,我如何判定句子a和句子b/c/d/e等等是在说同一个事情?
再举个例子,老生常谈,搜索场景,你在某度搜索“深度学习如何入门?”,某度如何返回和你这个问题最接近的问题/文章/博客等等内容网页?
这些本质上都是在做文本相似度的判定或者说文本匹配,只是在不同场景下,有着不同办法不同的特色。
文本匹配,并不是简简单单的词汇层次的匹配:比如“深度学习如何入门”和“如何入门深度学习”。
还会有语义方面的匹配考虑,比如说“我想撒尿去哪里啊?”和“我想去卫生间”。这两句话在问答系统中,匹配到标准问题都是“卫生间在哪里”这个问题。
对文本匹配方法来说,我们一般可以使用两种:无监督和有监督。
首先我们来谈一下无监督的方法。
对于无监督的文本匹配,我们需要实时把握两个重点:文本表征和相似函数的度量。
文本表征指的是我们将文本表示为计算机可以处理的形式,更准确了来说是数字化文本。而这个数字化文本,必须能够表征文本信息,这样才说的通。
相似函数的度量就是你选择何种函数对文本相似度进行一个判定,比如欧氏距离,余弦距离,Jacard相似度,海明距离等等
我大概梳理了一下无监督的几种比较典型的方法,,如下所示:
TF-IDF/IDF+词向量比较简单,我就不多说了。我们先来看一下BM25。
对于BM25,有搜索Query q,分词之后单词 w,候选文档 d。
掌握BM25,核心要点有三个:分词之后w的权重,w和q的相似性,w和d的相似性。
对于搜索场景,显而易见的一个问题就是,我们的搜索query和候选文档的长度是不一样甚至差距很大,所以BM25在计算相似性的时候需要对文档长度做一定的处理。
话说回来,其次我们谈一谈WMD,项目中并没有用到 WMD 这个获取句子向量的方法,所以这里只是对它有一个简单的了解即可,在这里做一个记录,等以后需要用到的时候, 会在这里继续更新,深挖进入。文章末尾会列出相关参考资源,感兴趣的可以看一看。
WMD Word Mover's Distance的缩写,翻译为词移距离,WMD距离越大相似度越小,WMD距离越小文本相似度越大。
理解这个概念,分为两个步骤。
首先第一步,有两个文档A和B,A中的每个词遍历和B中每个词的距离,挑出A中每个词和B中每个词中最小的距离,最后相加,得到A到B的WMD。
这个时候,需要明白第一步是存在巨大问题的。什么问题呢?A,B,C三个文档。A全部词和音乐相关。B中一个词和音乐相关,C中一个词和音乐相关,其余词和音乐有点关系,而且定义B和C中和音乐相关的词是同一个词。
根据我们的直觉,A和B相关性肯定小于A和C的相关性,但是如果按照第一步的算法去做,会得到相关性相等的结果,这是不对的。
这是因为A中的所有词匹配到的B中的那个和音乐相关的词(遍历取最小),A中所有的词匹配到C中和音乐相关的词(遍历取最小),B和C中和音乐相关的词一样,就导致相关性相等。
怎么解决这个问题,这就是第二步,让A中的所有词在匹配的时候,不是遍历取最小,而是让每个词匹配到C中的所有词,只不过匹配的权重不同。
这个权重从两个部分去看:一个是从A看,要符合分配出去的权重等于自身这个词在本文档的权重,一个是从B看,分配到B的某个词的权重要等于这个词在B文档的权重。
大概理解到这里。之后关于WMD加速(因为复杂度比较高)的内容就没有进一步的去了解
这个时候,我想到了一个比较有意思的点。我们知道有监督,比如说SiaGRU 这个方法,也是对句子进行编码,只不过这里的编码我们使用的是基于损失函数和标注语料训练出来的编码。
注意,这句话里我认为是有个重点的,就是句子编码是基于损失函数训练出来的。
换句话说,不同的损失函数我们训练出来的句子编码肯定是不一样的,效果也就有好有坏。
换句话说,我们使用不同的损失函数,比如调整正样本对应的损失权重和正常损失函数训练出来的文本编码肯定是不一样的。
为什么说这么呢?我们想一下,如果我使用SiaGRU训练出来的句子对的编码,我们直接使用cosine进行相似度的度量,效果会怎么样?
对于这个问题,大家可以自己实践一下。我想提到的一点就是,我们的句子对的编码并不是基于我们的cosine这种相似度量训练出来的,所以不存在完全匹配,所以效果肯定是比不上有监督的。
我们更近一步的想一个问题。通过实践,我们在使用bert做句子对的相似度的时候会发现一个问题,就是效果可能还没有使用word2vec的效果好。
我自己猜测这个原因可能就是因为cosine这个函数没有办法表征出bert训练的句子编码信息,因为bert训练过程复杂,不是consien这种简单函数就可以表达的。这是我自己的一个理解。
然后,我们详细聊一下SIF这个方法。
我先说一下这个方法最容易出问题的一个地方:数据预处理的时候不要去除停用词等高频词汇
掌握SIF,就抓住两个核心要点:
我们先说核心要点1。其实词向量加权求和这个方法并不是特别的陌生,比如等权求和,IDF权重求和,TF-IDF权重求和等等,这些我们平时都会用到。至于用哪一个就看你的数据集上的效果了。
我们知道word2vec这种词向量是含有语义信息的(在我看来其实它本质上是一种位置信息,相同句子结构下不同词的词向量可能很相似)。所以等权求和或者加权求和更像是在做Pooling(最大池化,平均池化,加权池化)等等,就是从我们各个词语信息中提取中有用的部分。
因为我们是无监督,这个权重的设定我们没有办法像在CNN处理图片一样,把参数学出来,所以只能人工的去定这个参数。
说到这里,想要插一句话,上面说到因为无监督我们无法将权重参数学出来。换句话说,如果是有监督,这个参数是可以学出来的,那怎么学呢?
最简单的一个方法就是全连接,或者说逻辑回归啊,只要有数据,我们就可以使用机器学习,复杂就使用神经网络基于标注数据把这个参数学出来。
现在没有标注数据,这个参数我们需要人工去定。这个参数需要含有一定的意义,不是我们一拍脑袋就定个参数,它需要加权求和之后使句子编码含有的语义信息可以对下游任务有帮助或者说在下游任务中效果好才可以。
SIF这里使用的权重函数是这样的:a/a+p(w) p(w)代表的是词在句子中的频次或者频率。用大白话去描述这个公式就是词在语料中出现的越多,对应的权重越小。这么做就减少了高频词的作用。也是我们不需要在数据处理的时候去除高频词的原因。
想一下这个公式的作用,其实本质上和IDF是很类似的。它并没有涉及到IF这个概念,也就是词在本句话出现的情况。
我们再来看核心要点2。什么叫主成分?具体的原理不多说,PCA本质上是找到原始数据存在最大方差的那个方向,让原始数据的投影尽可能的分散,从而可以起到降维的作用。
核心要点2就是减去了矩阵中共有的一部分信息,比如都含有的高频词汇信息等等。比如原始句子是”我爱吃红烧肉“和”我超不爱吃红烧肉“。减去共有信息之后,剩下的词向量矩阵就回对不相似的信息比较敏感。
总体来说。SIF这个算法还是很不错,很适合做个基线初期上线的。
我主要是讲两个模型:SiamGRU和ESIM模型
主要是参考了论文:Siamese Recurrent Architectures for Learning Sentence Similarity.
从三个核心要点来掌握这个模型:
1.使用神经网络对句子进行编码;
2.两个句子共享一个神经网络;
3.对句子编码进行相似性的度量进行训练。
首先我们来看核心要点1。
对于这个我觉得应该这么去想。首先,我们考虑一个使用神经网络进行文本分类的场景。我们使用交叉熵损失函数,基于此,训练神经网络。
在这个过程中,神经网络可以认为依赖了两个东西,一个是标注数据, 比如属于娱乐领域或者音乐领域等等,我们通过更新神经网络的参数让我们的模型预测结果不停的逼近这个标注数据。
其次,我们使用损失函数来度量预测结果和真实数据的差距。损失函数在我看来更像是一种解题方法,
不同的解题方法可能对应不同的结果,有的解题方法得到的结果既高效又准确,有的方法就很差。
为什么讲上面这些话?神经网络在上面这个过程中究竟起到了什么作用?在上面这个例子中,神经网络就是一个载体,接受文本数据,输出一个文本编码,从而判定属于哪个类别。所以神经网络的输出是带有语义信息。
SiamGRU的损失函数应该是什么?数据格式是输入一个句子对,标注结果是两个句子是不是表达同一个含义。这个属于0/1的二分类问题,也就是损失函数和二分类问题的损失函数是一样的,基于此损失函数,我们对模型进行训练。
对于核心要点2,两个句子共享一个神经网络。最大的好处是可以减少参数量。对于这个,我们可以想一下,能不能不共享参数,而是每个句子对应一个自己的Bilstm/GRU等编码器。
当然可以,我们的神经网络只是对句子进行编码,每个句子对应自己的编码器也可以做到这样的效果,所以没问题。我们再想一下,有没有必要这么做?关于这一点,我并没有去具体的做实验。我只是说一下自己的理解。
我觉得是没有必要的。首先,如果每个句子都对应自己的编码器,我想到的一个问题是需不需要对训练数据进行一个翻转重新输入的问题。换句话说,作为输入,分别对应
那么我需不需要作为一次输入再训练一次增加泛化性,我认为这个步骤是需要,这样就增加了训练来量。
简单来说,我们的句子对的输入不应该对句子对的输入顺序敏感,所以共享编码反而很好的解决了这个问题。
最后,我们来看核心要点3,我觉得这里还是值得注意的。
这里我认为有两个细节点需要注意。首先,这里做了一个两个句子编码的交互。对的,SiaGRU在我看来是对两个句子编码进行了交互的,只不过是在后期做的交互。
为什么这么说呢?这里的相似性的度量使用的是曼哈顿距离,也就是对应坐标差值。注意,这里涉及到了对应坐标位置的操作,所以可以看做是一种交互,而且这种差值在模型训练过程中是会随着模型参数的变化而变化的。
其次一个细节点,还是曼哈顿距离,这里不是一个标量,也就是不是一个和。简单来说就是对应位置相减之后,每个位置对应一个神经元,然后直接接全连接层就可以。
如果你认为这里是一个标量,就是很大的问题,标量如何接全连接层最终做二分类呢?
理解ESIM模型最核心的要点就在于两个句子word层次的attentino就可以了。
区别于SiamGRU 在后期进行两个句子之间的交互,ESIM在模型中期进行了word层级的两个句子之间的交互。这种交互在我看来是一种attention。
最近在做多模态的东西,其中一部分的attention和这里的很类似,更准确的说attention形式很类似。
整个ESIM可以分为三个部分。
首先第一部分是对两个句子的初期编码,这里和SiamGRU没有什么区别,都是使用神经网络对句子进行信息的提取。
需要注意的一点是在这里,我们编码之后,比如说LSTM,我们是可以得到每一个时刻或者说每一个单词的输出的,这就为交互中用到的Q/K/V提供了基础。
这里,我们使用LSTM作为编码器,然后得到编码向量,得到的维度是这样的[batch_size,seq_len,embedding_dim]。
为了下面解释的更加的方便,我们举个简单的例子,句子a,长度为10,句子b,长度为20.
现在经过初期编码,句子a:[1,10,300],句子b: [1,20,300]
随后,我们的操作就是进行交互操作,为了简单,我们省略掉第一个维度。它的交互就是矩阵相乘,得到[10,20]一个矩阵。
这个矩阵需要根据针对a句子横向做概率归一化,针对b句子纵向做概率归一化。
上面这句话其实就是ESIM的核心要点。它是一个两个item之间互相做attention,简单称之为both attention。
这样针对句子a我们就获得了attention之后的语境向量,针对句子b我们也获得了attention之后的语境向量,然后各自在做差和点击,然后结果拼接。
最后我们用获得的向量拼接输入到另一个编码器,输出pool接全连接就可以了,这块没有什么好说的。
针对不同的业务场景挑选不同的匹配模型很考验一个工程师的能力,所以需要掌握每个模型的特点和优缺点。