SpaCy: установить информацию об объекте для токена, который включен более чем в один диапазон.

Я пытаюсь использовать SpaCy для распознавания контекста сущности в мире онтологий. Я новичок в использовании SpaCy и просто играю для начала.

Я использую ENVO Ontology в качестве списка «шаблонов» для создания словаря для распознавания сущностей. Проще говоря, данные представляют собой идентификатор (CURIE) и имя объекта, которому они соответствуют, вместе с его категорией.

Снимок экрана с моими образцами данных:

Ниже приведен рабочий процесс моего исходного кода:

  • Создание шаблонов и терминов
      
    # Set terms and patterns
    terms = {}
    patterns = []
    for curie, name, category in envoTerms.to_records(index=False):
        if name is not None:
            terms[name.lower()] = {'id': curie, 'category': category}
            patterns.append(nlp(name))

  • Настроить собственный конвейер
      
    @Language.component('envo_extractor')
    def envo_extractor(doc):
        
        matches = matcher(doc)
        spans = [Span(doc, start, end, label = 'ENVO') for matchId, start, end in matches]
        doc.ents = spans
        
        for i, span in enumerate(spans):
            span._.set("has_envo_ids", True)
            for token in span:
                token._.set("is_envo_term", True)
                token._.set("envo_id", terms[span.text.lower()]["id"])
                token._.set("category", terms[span.text.lower()]["category"])
        
        return doc
    
    # Setter function for doc level
    def has_envo_ids(self, tokens):
        return any([t._.get("is_envo_term") for t in tokens])

##EDIT: #################################################################
    def resolve_substrings(matcher, doc, i, matches):
        # Get the current match and create tuple of entity label, start and end.
        # Append entity to the doc's entity. (Don't overwrite doc.ents!)
        match_id, start, end = matches[i]
        entity = Span(doc, start, end, label="ENVO")
        doc.ents += (entity,)
        print(entity.text)
#########################################################################
  • Реализуйте настраиваемый конвейер
      
    nlp = spacy.load("en_core_web_sm")
    matcher = PhraseMatcher(nlp.vocab)
    #### EDIT: Added 'on_match' rule ################################
    matcher.add("ENVO", None, *patterns, on_match=resolve_substrings)
    nlp.add_pipe('envo_extractor', after='ner')

и конвейер выглядит так

      
    [('tok2vec', <spacy.pipeline.tok2vec.Tok2Vec at 0x7fac00c03bd0>),
     ('tagger', <spacy.pipeline.tagger.Tagger at 0x7fac0303fcc0>),
     ('parser', <spacy.pipeline.dep_parser.DependencyParser at 0x7fac02fe7460>),
     ('ner', <spacy.pipeline.ner.EntityRecognizer at 0x7fac02f234c0>),
     ('envo_extractor', <function __main__.envo_extractor(doc)>),
     ('attribute_ruler',
      <spacy.pipeline.attributeruler.AttributeRuler at 0x7fac0304a940>),
     ('lemmatizer',
      <spacy.lang.en.lemmatizer.EnglishLemmatizer at 0x7fac03068c40>)]

  • Установить расширения
      
    # Set extensions to tokens, spans and docs
    Token.set_extension('is_envo_term', default=False, force=True)
    Token.set_extension("envo_id", default=False, force=True)
    Token.set_extension("category", default=False, force=True)
    Doc.set_extension("has_envo_ids", getter=has_envo_ids, force=True)
    Doc.set_extension("envo_ids", default=[], force=True)
    Span.set_extension("has_envo_ids", getter=has_envo_ids, force=True)

Теперь, когда я запускаю текст «культура ткани», он выдает ошибку:

      
    nlp('tissue culture')

      
    ValueError: [E1010] Unable to set entity information for token 0 which is included in more than one span in entities, blocked, missing or outside.

Я знаю, почему произошла ошибка. Это потому, что в базе данных ENVO есть 2 записи для фразы «культура ткани», как показано ниже:

В идеале я бы ожидал, что соответствующий CURIE будет помечен в зависимости от фразы, присутствующей в тексте. Как мне исправить эту ошибку?

Информация о моем спа-центре:

      
    ============================== Info about spaCy ==============================
    
    spaCy version    3.0.5                         
    Location         *irrelevant*
    Platform         macOS-10.15.7-x86_64-i386-64bit
    Python version   3.9.2                         
    Pipelines        en_core_web_sm (3.0.0)   

  

3 ответа

В наши дни это может быть немного поздно, но, немного дополняя ответ Sofie VL , и для всех, кто может быть все еще заинтересован в этом, то, что я (еще один новичок spaCy, смеется) сделал, чтобы избавиться от перекрывающихся промежутков, идет как следует:

      import spacy
from spacy.util import filter_spans

# [Code to obtain 'entity']...
# 'entity' should be a list, i.e.:
# entity = ["Carolina", "North Carolina"]

pat_orig = len(entity)
filtered = filter_spans(ents) # THIS DOES THE TRICK
pat_filt =len(filtered)
doc.ents = filtered

print("\nCONVERSION REPORT:")
print("Original number of patterns:", pat_orig)
print("Number of patterns after overlapping removal:", pat_filt)

Важно отметить, что на данный момент я использую самую последнюю версию spaCy, v3.1.1. Кроме того, он будет работать только в том случае, если вы действительно не против удаления перекрывающихся промежутков, но если вы это сделаете, то вы, возможно, захотите на взглянутьэтот поток . Подробнее о filter_spans здесь .

С наилучшими пожеланиями.

С spacy v3, вы можете использовать для хранения сущностей, которые могут перекрываться. Эта функция не поддерживается.

Итак, у вас есть два варианта:

  • Реализуйте обратный вызов, который отфильтрует результаты сопоставления перед использованием результата для установки. Беглый взгляд на ваш код (и последующие правки) не думаю, что resolve_substringsна самом деле разрешает конфликты? В идеале on_match функция должна проверить, есть ли конфликты с существующими энтами, и решить, какие из них оставить.
  • Использовать doc.spans вместо doc.ents если это работает для вашего варианта использования.

Вместо загрузки модели nlp = spacy.load("en_core_web_sm"), попробуйте просто инициализировать объект nlp, используя nlp = English()и проверьте. Надеюсь, это сработает.

По сути, заменить

      nlp = spacy.load("en_core_web_sm")

с участием

      nlp = English()
Другие вопросы по тегам