Завершите обход, в ходе которого мы точно настроим Transformer для распознавания именованных объектов (NER) с использованием Weak Supervision.

Слабый контроль — это подход к машинному обучению, при котором высокоуровневые и часто более шумные источники контроля используются для создания гораздо больших обучающих наборов гораздо быстрее, чем это могло бы быть создано с помощью ручного контроля
Snorkel AI

В этом посте будут использоваться функции маркировки и агрегирования без ручной проверки, чтобы создать единый источник для тонкой настройки нашего трансформатора.

Слабый контроль с классификацией токенов немного неудобен при объединении выходных данных различных функций маркировки. Чтобы обойти это, мы будем использовать метод большинства голосов, чтобы сократить «многие» метки до одного пригодного для использования источника.

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

pip install extr-ds

Код этого проекта можно найти — Github Repository.

Данные



Маркировка

Мы настроим шесть небольших функций маркировки, чтобы программно пометить наш набор данных. Выходные данные этих функций маркировки будут объединены вместе для создания нашего набора данных IOB2 со слабой маркировкой.

from typing import List, Dict, Optional

import re
import nltk

from extr import RegExLabel, RegEx
from extr.entities import create_entity_extractor
from extr_ds.rules import StaticUnit


class RegExStaticUnit(StaticUnit):
    def __init__(self, \
                 regex_labels: List[RegExLabel], \
                 kb: Optional[Dict[str, List[str]]] = None) -> None:
        super().__init__(
            nltk.tokenize.word_tokenize,
            create_entity_extractor(
                regex_labels=regex_labels,
                kb=kb
            )
        )


Игрок

«П.Махоумс борется» — П.Махоумс

class Player(RegExStaticUnit):
    def __init__(self) -> None:
        super().__init__(
            regex_labels=[
                RegExLabel(
                    label='PLAYER',
                    regexes=[
                        RegEx(
                            expressions=[
                                r'\b[A-Z][a-z]?\.[A-Z][a-z]+\b',
                                r'\b[A-Z][a-z]?\.[A-Z]\'[A-Z][a-z]+\b',
                                r'(?<=Pass From ).+?(?= for)',
                                r'(?<=\) ).+?(?= Pass From)',
                            ]
                        )
                    ]
                )
            ]
        )

Команда

«к ЛАК 34» — ЛАК

class Team(RegExStaticUnit):
    def __init__(self) -> None:
        super().__init__(
            regex_labels=[],
            kb={
                'TEAM': [
                    'ARZ',
                    'Arizona',
                    'ATL',
                    ...
                ]
            }
        )

Временной период

“(6:51–1st)” — 6:51, 1st

class Time(RegExStaticUnit):
    def __init__(self):
        super().__init__(
            regex_labels=[
                RegExLabel(
                    label='TIME',
                    regexes=[
                        RegEx(
                            expressions=[
                                r'\b[0-9]{1,2}:[0-9]{2}\b',
                            ]
                        )
                    ]
                )
            ]
        )

class Period(RegExStaticUnit):
    def __init__(self):
        super().__init__(
            regex_labels=[],
            kb={
                'PERIOD': [
                    '1st',
                    '2nd',
                    '3rd',
                    '4th',
                    'OT',
                ],
            }
        )

Количество/Единицы

«24 ярда» — 24, ярда

class Quantity(StaticUnit):
    def __init__(self):
        super().__init__(
            regex_labels=[
                RegExLabel(
                    label='QUANTITY',
                    regexes=[
                        RegEx(expressions=[
                            r'(?<=[\s\(])-?\d{1,3}(?=\b)',
                        ])
                    ]
                )
            ]
        )

class Units(StaticUnit):
    def __init__(self):
        super().__init__(
            regex_labels=[
                RegExLabel(
                    label='UNITS',
                    regexes=[
                        RegEx(
                            expressions=[
                                r'\b(y(?:ar)?ds)\b',
                            ],
                            flags=re.IGNORECASE
                        )
                    ]
                )
            ]
        )

Объединить

Мы будем использовать базовую настройку слияния (Union). Использование StaticUnit дает более высокую достоверность для найденных объектов (99%) и немного более низкую, но постоянную достоверность для ненайденных аннотаций (90%).

labeling_functions = [
    Player(),
    Team(),
    Time(),
    Period(),
    Quantity(),
    Units(),
]

def clean(text: str) -> str
    ...
    return text

def annotate(document: str) -> Dict[str, List[str]]:
    text = clean(document)

    results = labeling_functions[0](text)
    for labeling_function in labeling_functions[1:]:
        results = create_static_inference_result(
            labels=Majority().merge([
                results,
                labeling_function(text),
            ]),
            weight=results.weight
        )

    return {
        'tokens': nltk.tokenize.word_tokenize(text),
        'labels': results.labels
    }
[
  'B-PLAYER',
  'O',
  'B-QUANTITY',
  'B-UNITS',
  'O',
  'B-TEAM',
  'B-QUANTITY',
  'O',
  'B-TEAM',
  'B-QUANTITY',
  'O',
  'B-PLAYER',
  'O',
  'B-TEAM',
  'B-QUANTITY',
  'O',
  'B-QUANTITY',
  'B-UNITS',
  'O',
  'B-PLAYER',
  'O',
  'B-PLAYER',
  'O',
  'O'
]

*** Это можно легко расширить, чтобы функции маркировки конкурировали друг с другом. — Вы можете обучить несколько различных моделей NER и применить голосование большинства к их наблюдениям, чтобы создать помеченный набор данных. ***

Тонкая настройка

Эта часть была рассмотрена в предыдущем посте — см. Создание пользовательских моделей распознавания именованных объектов (NER) — преобразователи.

import random

k = 1000

with open('./source.txt', 'r') as source_input:
    rows = source_input.read().split('\n')

random.seed(42)
random.shuffle(rows)

annotations = []
for row in rows[0:k]:
    annotations.append(
        annotate(row)
    )

with open('./ents-iob.json', 'w') as output_iob:
    output_iob.write(json.dumps(annotations))

После обучения мы видим, что он просто не смог достичь 100%, используя небольшой набор данных. В этом случае наш преобразователь фактически находит объекты в нашем наборе данных обучения/тестирования, которые не помечены.

В приведенном ниже примере наш преобразователь пометил K.Van Noy как [B-PLAYER, I-PLAYER], тогда как наша функция маркировки Player пометила бы только K.Van как B-PLAYER.

[
  {
    '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.',
    'iob': [
      {
        'tokens': ['(', '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', '.'], 
        'labels': ['O', 'B-TIME', 'O', 'B-PERIOD', 'O', 'O', 'O', 'O', 'B-PLAYER', 'O', 'O', 'O', 'O', 'B-TEAM', 'B-QUANTITY', 'O', 'B-QUANTITY', 'B-UNITS', 'O', 'B-PLAYER', 'O', 'B-PLAYER', 'I-PLAYER', 'O', 'O', 'O', 'O', 'B-PLAYER', 'O', 'O', 'O', 'O', 'O', 'B-TEAM', 'B-QUANTITY', 'O']
      }
    ]
  }
]

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

Преимущество.Набор данных, который я здесь использовал, содержит более 40 000 строк. Этот подход позволил получить полностью размеченный набор данных за считанные минуты.

Предупреждение.Если у вас возникнут проблемы с проверкой набора данных, убедитесь, что вы сохранили эти аннотации и добавили их в обучающий набор на каждой итерации.

Существуют различные методы для итеративного улучшения ваших результатов, см. пример Точная настройка предварительно обученной языковой модели со слабым контролем: контрастно-регуляризованный подход к самообучению.