Дисклеймер.Все примеры текстов и сущностей в статье являются синтетическими и не содержат реальных персональных данных. Любые совпадения с реальностью случайны.Дисклеймер.Все примеры текстов и сущностей в статье являются синтетическими и не содержат реальных персональных данных. Любые совпадения с реальностью случайны.

NER не про токены: почему span важнее BIO

Интро

В последние годы системы детекции и очистки персональных данных стали неотъемлемой частью NLP-пайплайнов, особенно в сценариях, где тексты передаются во внешние LLM-провайдеры и используются в LLM-агентах.
На практике такие системы решают задачу детекции и маскирования персональных данных, среди них можно выделить: Presidio, LLM Guard, NvidiaNeMo Guardrails и другие.
Хотя на уровне API результат выглядит достаточно простым.
Например, когда вы используете presidio и получаете подобный ответ:

from presidio_analyzer import AnalyzerEngine text="My phone number is 212-555-5555" analyzer = AnalyzerEngine() # Call analyzer to get results results = analyzer.analyze(text=text, entities=["PHONE_NUMBER"], language='en') print(results) # type: PHONE_NUMBER, start: 19, end: 31, score: 0.75]

Или обращаетесь в какой-то сервис и получаете аналогичный ответ:

{ "text": "My phone number is 212-555-5555", "entities": [ { "type": "PHONE_NUMBER", "text": "212-555-5555", "start": 19, "end": 31, "score": 0.75 } ] }

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

Разметка в NER — это не вспомогательный этап, а основа, которая напрямую влияет на: выбор подхода (regex, ML, эвристики), архитектуру модели, качество, скорость и сложность пост-обработки.

Ground Truth: разметка на уровне span-ов

Задача NER заключается в том, чтобы найти некоторые фрагменты (spans) текста, которые считаются именованными сущностями, например:

[PER] Петр Петров [/PER] работает в [LOC] Москве [/LOC]

PER span: Петр Петров
LOC span: Москве

Спаны в NER датасетах как правило представлены следующим образом:

{ "text": "Письмо от Ивана Петрова Сергею Сидорову", "entities": [ { "start": 10, "end": 23, "type": "PERSON" }, { "start": 24, "end": 39, "type": "PERSON" } ] }

Некоторые датасеты могут содержать в себе вложенные сущности:

{ "text": "Санкт-Петербург, Невский проспект, дом 45", "entities": [ { "start": 0, "end": 42, "label": "ADDRESS" }, // весь адрес { "start": 0, "end": 15, "label": "CITY" }, // город { "start": 17, "end": 33, "label": "STREET" }, // улица { "start": 39, "end": 42, "label": "BUILDING" } // номер дома ] }

Но далеко не все схемы кодирования могут представить такую структуру.

Данные с разметкой на уровне спанов можно найти у Nvidia:
https://huggingface.co/datasets/nvidia/Nemotron-PII

Также есть небольшой набор архитектур моделей, которые работают именно со спанами, например: Span marker и Gliner.

Span-level разметка удобна тем, что она универсальна, поскольку любая модель или эвристика в NER-пайплайне после некоторой пост-обработки будет выдавать свой ответ именно в этом формате, что дает возможность держать данные в едином формате для бенчмаркинга пайплайнов.
Однако большинство моделей работают иначе - они классифицируют токены, а спаны восстанавливаются из меток. Что ведет нас к следующей части данной статьи.

Token-level схемы

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

from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained( "distilbert/distilbert-base-uncased", num_labels=13, id2label=id2label, label2id=label2id )

Модель оптимизируется под задачу классификации каждого входного токена в один из классов.
На уровне модели не существует понятия сущности как цельного объекта — она появляется только после декодирования последовательности токенов в спаны.

Аналогично, если у вас есть некоторый набор данных в span-level разметке, вам нужно будет конвертировать их в token-level схему.

Давайте посмотрим какие они бывают.

К распространенным token-level схемам относятся:
IO, IOB, IOE, IOBES, BI, IE и BIES

Рассмотрим их более подробно.

IO

Итак, мы решаем задачу классификации и наша задача классифицировать токены, самое элементарное что нам придет в голову - это сказать, что каждый токен это какой-то конкретный класс, если классов много - значит много или их просто нет (I-CLASS или O).

Формально:
Каждый токен в датасете принимает значение I-CLASS или O
I-inside tag - означает что токен отмечен как именованная сущность
O- outside tag - означает что у токена сущностей нет

Примеры
Вот так может выглядеть IO схема:

["Петр", "Иванов", "живет", "на", "ленина", "13"] ["I-PERSON", "I-PERSON", "O", "O", "I-ADDRESS", "I-ADDRESS"]

А еще вот так:

["Петр Иванов", "живет", "на", "ленина", "13"] ["I-PERSON", "O", "O", "I-ADDRESS", "I-ADDRESS"]

Или вот так:

["Петр Иванов", "живет", "на", "ленина 13"] ["I-PERSON", "O", "O", "I-ADDRESS"]

Все 3 варианта формально допустимы и зависят от контекста постановки задачи.
Просто помните что если модель выучит что любое число это адрес - скорее всего у вас будет высокий FP.

С IO возникает проблема, заключающаяся в том, что мы не совсем понимаем когда закончилась конкретная сущность:

["Письмо", "от", "Ивана", "Петрова", "Сергею", "Сидорову"] ["O", "O", "I-PERSON", "I-PERSON", "I-PERSON", "I-PERSON"]

Как видите ["Ивана", "Петрова", "Сергею", "Сидорову"] - все это 4 I-сущности без начала и конца. Если вам надо понимать кого из них выделять и не терять контекст при этом, у вас возникнут сложности.
Например вы решили замаскировать их:

["PERSON_1", "PERSON_2", "PERSON_3", "PERSON_4"]

Получается так что 2 человека внезапно превратились в 4 разных, что может быть критичным в определенных юзкейсах, где важно не просто замаскировать, а сохранить контекст.

Технически пример выше можно разметить еще и вот так:

["Письмо", "от", "Ивана Петрова Сергею Сидорову"] ["O", "O", "I-PERSON"]

Поздравляю, теперь у вас не 2 а одна PERSON сущность.

С точки зрения семантики это выглядит полным абсурдом, ведь письмо от Ивана Сергею, это два разных человека.

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

В качестве решения проблемы этой была представлена BIO схема.

IOB (BIO)

Как мы уже поняли нам нужен некоторый разделитель, который
скажет нам когда сущность начинается (или заканчивается но это уже IOE)

Формально
I- inside
O- outside
B- begin тег - означающий начало сущности

Примеры
Тогда данные можно разметить так:

["Письмо", "от", "Ивана", "Петрова", "Сергею", "Сидорову"] ["O", "O", "B-PERSON", "I-PERSON", "B-PERSON", "I-PERSON"]

Данный вид разметки также известен как CONLL формат.

Тут начинаются потенциальные проблемы с дисбалансом классов, поскольку "O" будет доминирующим тегом, а частота B/I зависит от средней длины сущностей

Также возникает проблема вложенных сушностей, а точнее невозможность их как то разметить:

ADDRESS: "Санкт-Петербург"_CITY, "Невский проспект"_STREET, "дом 45"_HOUSE_NUMBER, "квартира 23"_APARTMENT

Данная проблема характерна для всех видов token-level аннотаций.
Попробуйте адаптировать эту сущность к BIO схеме, а к IO?

Вот примеры для BIO:

["Санкт-Петербург", ",", "Невский", "проспект", ",", "дом", "45", ",", "корпус", "2", ",", "квартира", "23"] ["B-ADDRESS", "O", "B-ADDRESS", "I-ADDRESS", "O", "B-ADDRESS", "I-ADDRESS", "O", "B-ADDRESS", "I-ADDRESS", "O", "B-ADDRESS", "I-ADDRESS"]

тут мы теряем все вложенные сущности (CITY, STREET, HOUSE_NUMBER, APARTMENT)

или:

["Санкт-Петербург", ",", "Невский", "проспект", ",", "дом", "45", ",", "корпус", "2", ",", "квартира", "23"] ["B-CITY", "O", "B-STREET", "I-STREET","O", "B-NUM", "I-NUM", "O", "B-NUM", "I-NUM", "O", "B-APART" "I-APART",]

Тут мы теряем сущность которая придает какой-то смысл происходящему здесь - ADDRESS.

IOE

Аналогичен IOB, просто вместо Begin-тега - у нас идет End-тег.

Формально
I- inside
O- outside
E- end tag - означающий конец сущности

Пример

["Письмо", "от", "Ивана", "Петрова", "Сергею", "Сидорову"] ["O", "O", "I-PERSON", "E-PERSON", "I-PERSON", "E-PERSON"]

BI

Не судите по названию что тут всего лишь 2 вида тэгов, хотя тэгов действительно 2, но они применяются теперь не только к токенам сущностей (entities) но к "не сущностям" (non-entities), то есть токенам которые не являются сущностями.

Формально
B - begin of entity/non-entity
I - inside entity/non-entity

Пример

["Письмо", "от", "Ивана", "Петрова", "Сергею", "Сидорову"] ["B-O", "I-O", "B-PERSON", "I-PERSON", "B-PERSON", "I-PERSON"]

Как следствие классов у нас уже не 2 как в IO, а 4, что повышает потенциальный дисбаланс классов.

IE

Помните IOB и IOE?
Так вот кейс такой же, просто "переворачиваем" и берем что? Правильно End tag.
Формально
I - inside entity/non-entity
E - end of entity/non-entity
Пример

["Письмо", "от", "Ивана", "Петрова", "Сергею", "Сидорову"] ["I-O", "E-O", "I-PERSON", "E-PERSON", "I-PERSON", "E-PERSON"]

IOBES (BILOU)

Продолжает идею IOB и IOE, сделав merge и добавив в них S-тег
Ее также называют BILOU и она является достаточно популярной, наравне с BIO.

Формально
I- inside - где-то внутри сущности (между началом и концом)
O- outside
B- begin - начало сущности
E- end - конец сущности
S- single - одиночная сущность

Пример

["Петр", "Сергеевич", "Иванов", "живет", "в", "Москве", "на", "Тверской", "15"] ["B-PERSON", "I-PERSON", "E-PERSON", "O", "O", "S-GPE", "O","B-ADDRESS", "E-ADDRESS"]

BIES

BIES берет идею IOBES(BILOU), но еще делаем concat с BI и IE
Звучит как апофеоз всего зоопарка, поскольку проблема вложенных сущностей все еще не решена, а классов становится все больше и больше...
Формально
I- inside entity/non-entity
O- outside of entity/non-entity
B- begin - начало сущности
E- end - конец сущности
S- single - одиночная сущность
Пример

["Петр", "Сергеевич", "Иванов", "живет", "в", "Москве", "на", "Тверской", "15"] ["B-PERSON", "I-PERSON", "E-PERSON", "B-O", "E-O", "S-GPE", "S-O","B-ADDRESS", "E-ADDRESS"]

Какая разметка лучше?

Каждая схема позволяет решить определенный класс задач с некоторыми трейд-оффами. Тем не менее, если вы не совсем понимаете какая именно разметка вам нужна, просто берите span-level, а из него конвертируйте в BIO, BILOU или что-то менее популярное по необходимости.

Вывод

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

Span-level разметка является универсальным форматом представления сущностей.
Не все датасеты и бенчмарки используют её напрямую, однако именно к этому формату в итоге приводятся результаты работы NER-пайплайнов. Span-level разметка удобна для сравнения разных подходов, анализа ошибок и оценки качества на уровне целых сущностей. Также она дает возможность решать проблему вложенных сущностей, однако для этого требуется отдельный класс моделей.

Token-level разметка используется в первую очередь для обучения и тюнинга моделей.
Большинство архитектур работают с последовательностями токенов, поэтому при обучении span-level аннотации (если они есть) преобразуются в token-level схемы. Разные схемы (IO, BIO, IOBES и другие) отличаются способом кодирования границ сущностей.
Главное ограничение token-level подхода заключается в том, что он не поддерживает вложенные сущности и не оперирует спанами как цельными объектами.

На практике это приводит к следующему пайплайну: данные хранятся в span-level формате, затем конвертируются в token-level схемы для обучения модели, а предсказания модели декодируются обратно в спаны на этапе инференса.

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.