Краткий ответ: не интерпретируйте его . Используйте это так: чем меньше расстояние, тем больше предложений. Для практически всех практических применений (например, KNN) этого достаточно.
Теперь длинный ответ: расстояние перемещения слов (читать статья ) определяется как средневзвешенное расстояние между наиболее подходящими парами "безостановочных" слов. Поэтому, если вы хотите нормализовать ее в (0, 1), вам нужно разделить эту наилучшую сумму на худшую.
Проблема в том, что в spacy
векторы слов не нормализованы (проверьте это, напечатав [sum(t.vector**2) for t in doc1]
). Поэтому максимальное расстояние между ними не ограничено. И если вы их нормализуете, то новое ОМУ не будет эквивалентно исходному ОМУ (то есть по-разному будет сортировать пары текстов). Следовательно, не существует очевидного способа нормализовать исходные расстояния между ОМУ и пространством, которые вы продемонстрировали.
Теперь давайте представим, что векторы слов нормализованы по единицам. Если это так, то максимальное расстояние между двумя словами - это диаметр единичной сферы (то есть 2). Максимальное средневзвешенное значение для многих 2 равно 2. Поэтому вам нужно разделить расстояние между текстами на 2, чтобы оно полностью нормализовалось.
Вы можете встроить нормализацию вектора слов в расчет ОМУ, унаследовав используемый класс:
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
Теперь вы можете быть уверены, что ваше расстояние правильно нормировано:
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")))
Теперь результат
0.469503253698349
0.0
0.12690649926662445
0.6037049889564514
0.7507566213607788
P.S. Вы можете видеть, что даже между двумя очень не связанными текстами это нормализованное расстояние намного меньше 1. Это потому, что в действительности векторы слов не охватывают всю единичную сферу - вместо этого большинство из них сгруппированы на нескольких «континентах» на ней. Следовательно, расстояние даже между очень разными текстами будет обычно меньше 1.