Spacy Извлечение именованных отношений сущностей из обученной модели - PullRequest
1 голос
/ 10 марта 2020

Как использовать Spacy для создания нового имени субъекта «дела» - в контексте количества случаев инфекционного заболевания, а затем извлечь зависимости между этим и кардинальным числом случаев.

Например, в следующем тексте «Из них 879 случаев с 4 смертельными исходами были зарегистрированы за период с 9 октября по 5 ноября 1995 года». Мы бы хотели извлечь "879" и "кейсы"

В соответствии с кодом для "Обучения дополнительного типа сущности" на странице документации примера Spacy:

https://spacy.io/usage/examples#information - экстракция

Я использовал существующую модель en_core_web_sm engli sh с предварительной подготовкой, чтобы успешно обучить добавочную сущность, называемую "CASES":

from __future__ import unicode_literals, print_function

import plac
import random
from pathlib import Path
import spacy
from spacy.util import minibatch, compounding

LABEL = "CASES"

TRAIN_DATA = results_ent2[0:400]

def main(model="en_core_web_sm", new_model_name="cases", output_dir='data3', n_iter=30):
    random.seed(0)
    if model is not None:
        nlp = spacy.load(model)  # load existing spaCy model
        print("Loaded model '%s'" % model)
    else:
        nlp = spacy.blank("en")  # create blank Language class
        print("Created blank 'en' model")
    # Add entity recognizer to model if it's not in the pipeline
    # nlp.create_pipe works for built-ins that are registered with spaCy
    if "ner" not in nlp.pipe_names:
        ner = nlp.create_pipe("ner")
        nlp.add_pipe(ner)
    # otherwise, get it, so we can add labels to it
    else:
        ner = nlp.get_pipe("ner")

    ner.add_label(LABEL)  # add new entity label to entity recognizer
    # Adding extraneous labels shouldn't mess anything up
    if model is None:
        optimizer = nlp.begin_training()
    else:
        optimizer = nlp.resume_training()
    move_names = list(ner.move_names)
    # get names of other pipes to disable them during training
    pipe_exceptions = ["ner", "trf_wordpiecer", "trf_tok2vec"]
    other_pipes = [pipe for pipe in nlp.pipe_names if pipe not in pipe_exceptions]
    with nlp.disable_pipes(*other_pipes):  # only train NER
        sizes = compounding(1.0, 4.0, 1.001)
        # batch up the examples using spaCy's minibatch
        for itn in range(n_iter):
            random.shuffle(TRAIN_DATA)
            batches = minibatch(TRAIN_DATA, size=sizes)
            losses = {}
            for batch in batches:
                texts, annotations = zip(*batch)
                nlp.update(texts, annotations, sgd=optimizer, drop=0.35, losses=losses)
            print("Losses", losses)

    # test the trained model   

    test_text = "There were 100 confirmed cases?"
    doc = nlp(test_text)
    print("Entities in '%s'" % test_text)F
    for ent in doc.ents:
        print(ent.label_, ent.text)

    # save model to output directory
    if output_dir is not None:
        output_dir = Path(output_dir)
        if not output_dir.exists():
            output_dir.mkdir()
        nlp.meta["name"] = new_model_name  # rename model
        nlp.to_disk(output_dir)
        print("Saved model to", output_dir)

        # test the saved model
        print("Loading from", output_dir)
        nlp2 = spacy.load(output_dir)
        # Check the classes have loaded back consistently
        assert nlp2.get_pipe("ner").move_names == move_names
        doc2 = nlp2(test_text)
        for ent in doc2.ents:
            print(ent.label_, ent.text)

main()

Проверка вывода:

test_text = 'Of these, 879 cases with 4 deaths were reported for the period 9 October to 5 November 1995. John was infected. It cost $500'
doc = nlp(test_text)
print("Entities in '%s'" % test_text)
for ent in doc.ents:
    print(ent.label_, ent.text)

мы получаем результаты

Entities in 'Of these, 879 cases with 4 deaths were reported for the period 9 October to 5 November 1995. John was infected. It cost $500'
CARDINAL 879
CASES cases
CARDINAL 4
CARDINAL 9
CARDINAL 5
CARDINAL $500

Модель была сохранена и может правильно идентифицировать СЛУЧАИ из приведенного выше текста.

Моя цель состоит в том, чтобы извлечь число случаи данного заболевания / вируса из новостной статьи, а затем и число смертей.

Теперь я использую эту недавно созданную модель, пытаясь найти зависимости между CASES и CARDINAL:

Снова, используя пример Spacy

https://spacy.io/usage/examples#new -entity-type

'Обучающий анализатор зависимости spaCy'

import plac
import spacy


TEXTS = [
    "Net income was $9.4 million compared to the prior year of $2.7 million. I have 100,000 cases",
    "Revenue exceeded twelve billion dollars, with a loss of $1b.",
    "Of these, 879 cases with 4 deaths were reported for the period 9 October to 5 November 1995. John was infected. It cost $500"
]


def main(model="data3"):
    nlp = spacy.load(model)
    print("Loaded model '%s'" % model)
    print("Processing %d texts" % len(TEXTS))

    for text in TEXTS:
        doc = nlp(text)
        relations = extract_currency_relations(doc)
        for r1, r2 in relations:
            print("{:<10}\t{}\t{}".format(r1.text, r2.ent_type_, r2.text))


def filter_spans(spans):
    # Filter a sequence of spans so they don't contain overlaps
    # For spaCy 2.1.4+: this function is available as spacy.util.filter_spans()
    get_sort_key = lambda span: (span.end - span.start, -span.start)
    sorted_spans = sorted(spans, key=get_sort_key, reverse=True)
    result = []
    seen_tokens = set()
    for span in sorted_spans:
        # Check for end - 1 here because boundaries are inclusive
        if span.start not in seen_tokens and span.end - 1 not in seen_tokens:
            result.append(span)
        seen_tokens.update(range(span.start, span.end))
    result = sorted(result, key=lambda span: span.start)
    return result


def extract_currency_relations(doc):
    # Merge entities and noun chunks into one token
    spans = list(doc.ents) + list(doc.noun_chunks)
    spans = filter_spans(spans)
    with doc.retokenize() as retokenizer:
        for span in spans:
            retokenizer.merge(span)

    relations = []
    for money in filter(lambda w: w.ent_type_ == "MONEY", doc):
        if money.dep_ in ("attr", "dobj"):
            subject = [w for w in money.head.lefts if w.dep_ == "nsubj"]
            if subject:
                subject = subject[0]
                relations.append((subject, money))
        elif money.dep_ == "pobj" and money.head.dep_ == "prep":
            relations.append((money.head.head, money))
    return relations


main()

Вывод выглядит следующим образом: не обнаружение зависимости. Это как если бы модель потеряла эту способность, сохранив при этом способность обнаруживать именованные объекты. Или, может быть, какая-то настройка была отключена?

Loaded model 'data3'
Processing 3 texts

Если я использовал оригинальную предварительно обученную модель en_core_web_sm, результат будет:

Processing 3 texts
Net income  MONEY   $9.4 million
the prior year  MONEY   $2.7 million
Revenue     MONEY   twelve billion dollars
a loss      MONEY   1b

То же самое, что и вывод для модели на странице примера Spacy.

Кто-нибудь знает, что произошло и почему моя новая модель, которая использовала трансферное обучение на оригинальном Spacy 'en_core_web_sm', теперь не может найти зависимости в этом примере?

РЕДАКТИРОВАТЬ:

Если я использую обновленную обученную модель, она может обнаруживать новые "случаи" сущности и кардинальное "100 000", однако теряет способность определять деньги и дату.

Когда я обучил модель, я обучил ее тысячам предложений, используя саму базовую модель en_core_web_sm, чтобы обнаружить все объекты и пометить их так, чтобы модель не «забыла» старые объекты.

enter image description here

...