Сопоставление вектора слова с наиболее похожим/ближайшим словом с использованием spaCy

Я использую spaCy как часть решения для моделирования тем, и у меня возникла ситуация, когда мне нужно сопоставить вектор производного слова с «ближайшим» или «наиболее похожим» словом в словаре векторов слов.

Я вижу, что в gensim есть функция (WordEmbeddingsKeyedVectors.similar_by_vector) для вычисления этого, но мне интересно, есть ли что-то подобное в spaCy для сопоставления вектора со словом в его словаре (nlp.vocab)?


person Eric Broda    schedule 15.02.2019    source источник


Ответы (4)


После небольшого эксперимента я нашел функцию scikit (cdist в scikit.spatial.distance), которая находит «близкий» вектор в векторном пространстве к входному вектору.

# Imports
from scipy.spatial import distance
import spaCy

# Load the spacy vocabulary
nlp = spacy.load("en_core_web_lg")

# Format the input vector for use in the distance function
# In this case we will artificially create a word vector from a real word ("frog")
# but any derived word vector could be used
input_word = "frog"
p = np.array([nlp.vocab[input_word].vector])

# Format the vocabulary for use in the distance function
ids = [x for x in nlp.vocab.vectors.keys()]
vectors = [nlp.vocab.vectors[x] for x in ids]
vectors = np.array(vectors)

# *** Find the closest word below ***
closest_index = distance.cdist(p, vectors).argmin()
word_id = ids[closest_index]
output_word = nlp.vocab[word_id].text
# output_word is identical, or very close, to the input word
person Eric Broda    schedule 16.02.2019

Слово предостережения по поводу этого ответа. Традиционно сходство Word (в gensim, spacy и nltk) использует косинусное сходство, в то время как по умолчанию cdist scipy использует евклидово расстояние. Вы можете получить косинус расстояния, который не совпадает со сходством, но они связаны. Чтобы дублировать вычисления gensim, измените вызов cdist на следующее:

distance.cdist(p, vectors, metric='cosine').argmin()

Однако вы также должны отметить, что scipy измеряет косинусное расстояние, которое «назад» от косинусного подобия, где «косинусное расстояние» = 1 - cos x (x - угол между векторами), поэтому, чтобы сопоставить / дублировать числа gensim, вы должны вычесть ваш ответ от одного (и, конечно же, возьмите аргумент МАКС - подобные векторы ближе к 1). Это очень тонкая разница, но она может вызвать большую путаницу.

Подобные векторы должны иметь большое (около 1) сходство, а расстояние небольшое (близкое к нулю).

Косинусное сходство может быть отрицательным (это означает, что векторы имеют противоположные направления), но их РАССТОЯНИЕ будет положительным (как и должно быть расстояние).

источник: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html

https://tedboy.github.io/nlps/generated/generated/gensim.models.Word2Vec.n_similarity.html#gensim.models.Word2Vec.n_similarity

также сделать подобие в пространстве следующим образом:

import spacy
nlp = spacy.load("en_core_web_md")
x = nlp("man")
y = nlp("king")
print(x.similarity(y))
print(x.similarity(x))
person RDS    schedule 04.11.2019
comment
x.similarity было достаточно быстро, чтобы я перебрал все слова в словаре для небольшого количества случаев. - person z0r; 08.05.2020

Да, у spacy есть метод API для этого, как у KeyedVectors.similar_by_vector:

import numpy as np
import spacy

nlp = spacy.load("en_core_web_lg")

your_word = "king"

ms = nlp.vocab.vectors.most_similar(
    np.asarray([nlp.vocab.vectors[nlp.vocab.strings[your_word]]]), n=10)
words = [nlp.vocab.strings[w] for w in ms[0][0]]
distances = ms[2]
print(words)
['King', 'KIng', 'king', 'KING', 'kings', 'KINGS', 'Kings', 'PRINCE', 'Prince', 'prince']

(слова не нормализованы должным образом в sm_core_web_lg, но вы можете поиграть с другими моделями и получить более репрезентативный результат).

person Amir    schedule 12.10.2020

Это пример поиска сходства с векторами признаков размерности 300 (1,2 КБ для 32-битных чисел с плавающей запятой). ).

Вы можете хранить векторы слов в геометрической структуре данных, sklearn.neighbors .BallTree, чтобы значительно ускорить поиск, избегая потерь высокой размерности, связанных с деревьями k-d (нет ускорения, когда размерность превышает ~100). Их можно легко мариновать и распаковывать и хранить в памяти, если вам нужно избежать загрузки spaCy. Подробности реализации смотрите ниже. Демо, источник.


Другие ответы с линейным поиском работают (и я просто хотел бы отметить, что будьте осторожны при использовании сходства косинусов, если какой-либо из ваших векторов равен нулю), но будут медленными для больших словарей. Библиотека en_core_web_lg компании spaCy содержит около 680 тыс. слов с векторами слов. Поскольку каждое слово обычно занимает несколько байтов, это может привести к использованию памяти в несколько ГБ.

Мы можем сделать наш поиск нечувствительным к регистру и удалить нечастые слова, используя таблицу частотности слов (начиная с версии 3.0, spaCy имеет встроенную таблицу, но теперь вам нужно загружать их отдельно), чтобы сократить словарный запас до ~ 100 тыс. слов. Однако поиск по-прежнему линейный и может занять пару секунд, что может быть неприемлемо.

Существуют библиотеки для быстрого поиска сходства, однако они могут быть довольно громоздкими и сложными в установке, и предназначены для векторов функций порядка МБ или ГБ с ускорением графического процессора и остальными.

Мы также можем не захотеть всегда загружать весь словарь spaCy при каждом запуске приложения, поэтому мы собираем/распаковываем словарь по мере необходимости.

import spacy, numpy, pickle
import sklearn.neighbors as nbs

#load spaCy
nlp=spacy.load("en_core_web_lg")

#load lexeme probability table
lookups = load_lookups("en", ["lexeme_prob"])
nlp.vocab.lookups.add_table("lexeme_prob", lookups.get_table("lexeme_prob"))

#get lowercase words above frequency threshold with vectors, min_prob=-20
words = [word for word in nlp.vocab.strings if nlp.vocab.has_vector(word) and word.islower() and nlp.vocab[word].prob >= -18]
wordvecs = numpy.array([nlp.vocab.get_vector(word) for word in words])  #get wordvectors
tree = nbs.BallTree(wordvecs)  #create the balltree
dict = dict(zip(words,wordvecs))  #create word:vector dict

После обрезки словаря мы можем выбрать слова, dict и balltree и загрузить их, когда они нам понадобятся, без повторной загрузки spaCy:

#pickle/unpickle the balltree if you don't want to load spaCy
with open('balltree.pkl', 'wb') as f:
        pickle.dump(tree,f,protocol=pickle.HIGHEST_PROTOCOL)
#...
#load wordvector balltree from pickle file
with open('./balltree.pkl','rb') as f:
    tree = pickle.load(f)

Получив слово, получите его вектор слова, найдите в дереве индекс ближайшего слова, затем найдите это слово в словаре:

#get wordvector and lookup nearest words
def nearest_words(word):
    #get vectors for all words
        try:
            vec = to_vec[word]
        #if word is not in vocab, set to zero vector
        except KeyError:
            vec = numpy.zeros(300)

    #perform nearest neighbor search of wordvector vocabulary
    dist, ind = tree.query([vec],10)

    #lookup nearest words using indices from tree
    near_words = [vocab[i] for i in ind[0]]

    return near_words
person Jackson Walters    schedule 07.02.2021