SpaCy 的 Word Mover 距离的非标准化结果

Lui*_*uez 1 python nlp spacy

我正在尝试将 Fast Word Mover 的距离库与 SpaCy 一起使用,对于GitHub中的相同示例

import spacy
import wmd
nlp = spacy.load('en_core_web_md')
nlp.add_pipe(wmd.WMD.SpacySimilarityHook(nlp), last=True)

doc1 = nlp("Politician speaks to the media in Illinois.")
doc2 = nlp("The president greets the press in Chicago.")
print(doc1.similarity(doc2))
Run Code Online (Sandbox Code Playgroud)

结果是:

6.070106029510498
Run Code Online (Sandbox Code Playgroud)

我不知道如何解释它,因为距离通常是标准化的(0 到 1)。在自述文件中,此结果不可用,因此我不确定我的结果是否错误或此测量的比例不同。

Dav*_*ale 5

简短的回答:不要解释它。就像这样使用它:距离越小,句子越相似。对于几乎所有实际应用(例如 KNN)来说,这已经足够了。

现在长答案:单词移动距离(阅读论文)被定义为“不间断”单词的最佳匹配对之间距离的加权平均值。因此,如果你想将其归一化为 (0, 1),你需要将这个最佳总和除以最坏情况。

问题是spacy词向量没有标准化(通过打印检查[sum(t.vector**2) for t in doc1])。因此,它们之间的最大距离是无限的。如果你对它们进行标准化,新的 WMD 将不等同于原始的 WMD(即,它将以不同的方式对文本对进行排序)。因此,没有明显的方法可以标准化您演示的原始 spacy-WMD 距离。

现在让我们假设词向量是单位标准化的。如果是这样,那么两个单词之间的最大距离就是单位球体的直径(即2)。而很多2的最大加权平均值仍然是2。所以你需要将文本之间的距离除以2以使其完全归一化。

您可以通过继承您使用的类将词向量归一化构建到 WMD 计算中:

import wmd
import numpy
import libwmdrelax

class NormalizedWMDHook(wmd.WMD.SpacySimilarityHook):
    def compute_similarity(self, doc1, doc2):
        """
        Calculates the similarity between two spaCy documents. Extracts the
        nBOW from them and evaluates the WMD.

        :return: The calculated similarity.
        :rtype: float.
        """
        doc1 = self._convert_document(doc1)
        doc2 = self._convert_document(doc2)
        vocabulary = {
            w: i for i, w in enumerate(sorted(set(doc1).union(doc2)))}
        w1 = self._generate_weights(doc1, vocabulary)
        w2 = self._generate_weights(doc2, vocabulary)
        evec = numpy.zeros((len(vocabulary), self.nlp.vocab.vectors_length),
                           dtype=numpy.float32)
        for w, i in vocabulary.items():
            v = self.nlp.vocab[w].vector                                      # MODIFIED
            evec[i] = v / (sum(v**2)**0.5)                                    # MODIFIED
        evec_sqr = (evec * evec).sum(axis=1)
        dists = evec_sqr - 2 * evec.dot(evec.T) + evec_sqr[:, numpy.newaxis]
        dists[dists < 0] = 0
        dists = numpy.sqrt(dists)
        return libwmdrelax.emd(w1, w2, dists) / 2                             # MODIFIED
Run Code Online (Sandbox Code Playgroud)

现在您可以确定您的距离已正确标准化:

import spacy
nlp = spacy.load('en_core_web_md')
nlp.add_pipe(NormalizedWMDHook(nlp), last=True)
doc1 = nlp("Politician speaks to the media in Illinois.")
doc2 = nlp("The president greets the press in Chicago.")
print(doc1.similarity(doc2))
print(doc1.similarity(doc1))
print(doc1.similarity(nlp("President speaks to the media in Illinois.")))
print(doc1.similarity(nlp("some irrelevant bullshit")))
print(doc1.similarity(nlp("JDL")))
Run Code Online (Sandbox Code Playgroud)

现在的结果是

0.469503253698349
0.0
0.12690649926662445
0.6037049889564514
0.7507566213607788
Run Code Online (Sandbox Code Playgroud)

PS 你可以看到,即使在两个非常不相关的文本之间,这个归一化距离也远小于 1。这是因为实际上词向量并没有覆盖整个单位球体 - 相反,它们中的大多数都聚集在其上的几个“大陆”上。因此,即使非常不同的文本之间的距离通常也会小于 1。