Расширение токена в сравнении с сопоставлением фраз и сопоставлением фраз с правителем сущностей в spaCy - PullRequest
2 голосов
/ 25 апреля 2019

Я пытаюсь найти лучший (быстрый) способ извлечения сущностей, например, месяц. Я предложил 5 различных подходов, используя spaCy.

Начальная настройка

Для каждого решения я начинаю с начальной настройки

import spacy.lang.en    
nlp = spacy.lang.en.English()
text = 'I am trying to extract January as efficient as possible. But what is the best solution?'

Решение: использование атрибутов расширения (ограничено совпадением с одним токеном)

import spacy.tokens
NORM_EXCEPTIONS = {
    'jan': 'MONTH', 'january': 'MONTH'
}
spacy.tokens.Token.set_extension('norm', getter=lambda t: NORM_EXCEPTIONS.get(t.text.lower(), t.norm_))
def time_this():
    doc = nlp(text)
    assert [t for t in doc if t._.norm == 'MONTH'] == [doc[5]]

%timeit time_this()

76,4 мкс ± 169 нс на цикл (среднее ± стандартное отклонение из 7 циклов, по 10000 циклов в каждом)

Решение: использование сопоставления фраз через линейку сущностей

import spacy.pipeline
ruler = spacy.pipeline.EntityRuler(nlp)
ruler.phrase_matcher = spacy.matcher.PhraseMatcher(nlp.vocab, attr="LOWER")
ruler.add_patterns([{'label': 'MONTH', 'pattern': 'jan'}, {'label': 'MONTH', 'pattern': 'january'}])
nlp.add_pipe(ruler)
def time_this():
    doc = nlp(text)
    assert [t for t in doc.ents] == [doc[5:6]]
%timeit time_this()

131 мкс ± 579 нс на цикл (среднее ± стандартное отклонение из 7 циклов, по 10000 циклов в каждом)

Решение: использование соответствия токенов через линейку сущностей

import spacy.pipeline
ruler = spacy.pipeline.EntityRuler(nlp)
ruler.add_patterns([{'label': 'MONTH', 'pattern': [{'lower': {'IN': ['jan', 'january']}}]}])
nlp.add_pipe(ruler)
def time_this():
    doc = nlp(text)
    assert [t for t in doc.ents] == [doc[5:6]]
%timeit time_this()

72,6 мкс ± 76,7 нс на цикл (среднее ± стандартное отклонение из 7 циклов, 10000 циклов каждый)

Решение: использование сопоставителя фраз напрямую

import spacy.matcher
phrase_matcher = spacy.matcher.PhraseMatcher(nlp.vocab, attr="LOWER")
phrase_matcher.add('MONTH', None, nlp('jan'), nlp('january'))
def time_this():
    doc = nlp(text)
    matches = [m for m in filter(lambda x: x[0] == doc.vocab.strings['MONTH'], phrase_matcher(doc))]
    assert [doc[m[1]:m[2]] for m in matches] == [doc[5:6]]
%timeit time_this()

115 мкс ± 537 нс на цикл (среднее ± стандартное отклонение из 7 циклов, по 10000 циклов каждый)

Решение: использование токен-сопоставителя напрямую

import spacy.matcher
matcher = spacy.matcher.Matcher(nlp.vocab)
matcher.add('MONTH', None, [{'lower': {'IN': ['jan', 'january']}}])
def time_this():
    doc = nlp(text)
    matches = [m for m in filter(lambda x: x[0] == doc.vocab.strings['MONTH'], matcher(doc))]
    assert [doc[m[1]:m[2]] for m in matches] == [doc[5:6]]
%timeit time_this()

55,5 мкс ± 459 нс на цикл (среднее ± стандартное отклонение из 7 циклов, по 10000 циклов в каждом)

Заключение

Пользовательские атрибуты с ограничением сопоставляются с одним токеном, и сопоставление токенов кажется более быстрым, поэтому это представляется предпочтительным. EntityRuler кажется самым медленным, что неудивительно, поскольку он меняет Doc.ents. Однако весьма удобно, что у вас есть совпадения в Doc.ents, так что вы можете рассмотреть этот метод еще.

Я был довольно удивлен, что сопоставитель токенов превосходит сопоставитель фраз. Я думал, что это будет наоборот:

Если вам нужно сопоставить большие списки терминологии, вы также можете использовать PhraseMatcher и создавать объекты Doc вместо шаблонов токенов, что в целом намного эффективнее

Вопрос

Я что-то упускаю здесь или могу доверять этому анализу в более широком масштабе?

...