Выполните пошаговое руководство, в котором мы связываем пользовательские модели распознавания именованных объектов (NER) и извлечения отношений (RE) вместе, чтобы легко извлекать именованные объекты и отношения из текста.

Этот пост призван завершить серию других постов:

Модели распознавания именованных объектов
Создание пользовательских моделей распознавания именованных объектов
Создание пользовательских моделей распознавания именованных объектов — преобразователи

Модели извлечения отношений
Создание пользовательских моделей извлечения отношений — Часть 1
Создание пользовательских моделей извлечения отношений — Часть 2

Весь процесс управлялся через командную строку с помощью библиотеки extr-ds (Github Repository).

pip install extr-ds

Распознавание именованных объектов

Извлечение именованных объектов довольно просто. Мы загружаем текст в классификатор и фильтруем по показателю достоверности. Мы конвертируем ответ JSON в Entity .

from extr import Entity, Location
from extr.entities import AbstractEntityExtractor
from transformers import pipeline

class TransformerEntityExtractor(AbstractEntityExtractor):
    def __init__(self, model_output_checkpoint: str, threshold: float=.5) -> None:
        self._classifier = pipeline(
            'ner', 
            model=model_output_checkpoint,
            aggregation_strategy='simple'
        )
        self._threshold = threshold

    def get_entities(self, text: str) -> List[Entity]:
        valid_observations = filter(
            lambda r: r['score'] >= self._threshold,
            self._classifier(text)
        )

        entities = []
        for i, response in enumerate(valid_observations):
            location = Location(start=response['start'], end=response['end'])
            entities.append(
                Entity(
                    identifier=i + 1,
                    label=response['entity_group'],
                    text=location.extract(text),
                    location=location
                )
            )

        return entities

Классификатор отношений

Классификатор ожидает, что наш текст будет аннотирован, e1 -> <e1:TEAM></...>, e2 -> <e2:QUANTITY></..> . Мы можем взять извлеченные объекты и построить этот аннотированный текст для каждой комбинации пар e1, e2, что делает для нас BaseRelationLabeler. Отсюда нам просто нужно передать этот текст в наш классификатор и отфильтровать по показателю достоверности.

Этот метод вернет все ярлыки, включая NO_RELATION .

from extr import Entity, Relation
from extr_ds.models import RelationLabel
from extr_ds.labelers.relation import BaseRelationLabeler
from transformers import pipeline

class TransformerRelationLabeler(BaseRelationLabeler):
    def __init__(self,
                 model_output_checkpoint: str,
                 relation_builder: RelationBuilder,
                 threshold: float=.5):
        super().__init__(relation_builder)
        self._classifier = pipeline(
            'text-classification', 
            model=model_output_checkpoint
        )
        self._threshold = threshold

    def label(self, text: str, entities: List[Entity]) -> List[RelationLabel]:
        relation_labels = super().label(text, entities)
        observations = self._classifier(
            list(map(lambda label: label.sentence, relation_labels))
        )

        labels = []
        for relation_label, response in zip(relation_labels, observations):
            if response['score'] < self._threshold:
                continue

            relation = relation_label.relation
            relation_label.relation = Relation(
                response['label'],
                e1=relation.e1,
                e2=relation.e2
            )

            labels.append(relation_label)

        return labels

Извлечение именованных объектов и отношений

RelationClassification позаботится об остальном. Вызов .totuple(exclude=['NO_RELATION']) наших результатов помогает отфильтровать любую связь, которая была классифицирована как несуществующая.

from extr_ds.labelers.relation import RelationBuilder, \
                                      RelationClassification

ner_model_output_checkpoint = 'transformers/nfl_pbp_token_classifier'
re_model_output_checkpoint = 'transformers/nfl_pbp_relation_classifier'

classifier = RelationClassification(
    TransformerEntityExtractor(ner_model_output_checkpoint),
    TransformerRelationLabeler(
        re_model_output_checkpoint,
        relation_builder=RelationBuilder(relation_formats=[
            ('TEAM', 'QUANTITY', 'NO_RELATION')
        ]),
    ),
    additional_relation_labelers=[]
)

text = '(6:51 - 1st) (Shotgun) ' + \
       'P.Mahomes scrambles right end to LAC 34 for 2 yards (S.Joseph; K.Van Noy). ' + \
       'FUMBLES (S.Joseph), and recovers at LAC 34.'

results = classifier.label(text)
entities, relation_labels = results.totuple(exclude=['NO_RELATION'])

Извлеченные именованные объекты

[
  <Entity label="TIME" text="6:51" span=(1, 5)>,
  <Entity label="PERIOD" text="1st" span=(8, 11)>,
  <Entity label="FORMATION" text="Shotgun" span=(14, 21)>, 
  <Entity label="PLAYER" text="P.Mahomes" span=(23, 32)>, 
  <Entity label="EVENT" text="scrambles" span=(33, 42)>, 
  <Entity label="DIRECTION" text="right" span=(43, 48)>, 
  <Entity label="TEAM" text="LAC" span=(56, 59)>, 
  <Entity label="QUANTITY" text="34" span=(60, 62)>, 
  <Entity label="QUANTITY" text="2" span=(67, 68)>, 
  <Entity label="PLAYER" text="S.Joseph" span=(76, 84)>, 
  <Entity label="PLAYER" text="K.Van Noy" span=(86, 95)>, 
  <Entity label="PLAYER" text="S.Joseph" span=(107, 115)>, 
  <Entity label="TEAM" text="LAC" span=(134, 137)>, 
  <Entity label="QUANTITY" text="34" span=(138, 140)>
]

Извлеченные отношения

[
    <RelationLabel
        sentence="(6:51 - 1st) (Shotgun) P.Mahomes scrambles right end to <e1:TEAM>LAC</e1:TEAM> <e2:QUANTITY>34</e2:QUANTITY> for 2 yards (S.Joseph; K.Van Noy). FUMBLES (S.Joseph), and recovers at LAC 34."
        relation=<Relation e1="LAC" r="is_spot_of_ball" e2="34">
    >,
    <RelationLabel
        sentence="(6:51 - 1st) (Shotgun) P.Mahomes scrambles right end to LAC 34 for 2 yards (S.Joseph; K.Van Noy). FUMBLES (S.Joseph), and recovers at <e1:TEAM>LAC</e1:TEAM> <e2:QUANTITY>34</e2:QUANTITY>."
        relation=<Relation e1="LAC" r="is_spot_of_ball" e2="34">
    >
]