Про NER написано немало, но этот материал носит прикладной характер. Статья будет полезна тем, кто интересуется NLP и ищет разные подходы для решения узкопрофильных задач, требующих извлечения сущностей из текста.
Для джунов это возможность пройти весь путь — от разметки данных до обучения собственной кастомной NER-модели, попутно понять типичные сложности и ограничения.
Привет, меня зовут Александр Агеев, на протяжении года я занимался NER-моделями для определения сущностей на этикетках продуктов питания. Несмотря на мою любовь к NER, у этой технологии есть свои границы — кейсы, которые она не может решить хорошо, поэтому надо подключать другие инструменты. В статье я дам критерии применимости NER для решения практических задач.
Краткий экскурс
Named Entity Recognition (NER) — это технология извлечения именованных сущностей из текста: значимых фрагментов вроде имен, организаций, городов, дат, медицинских кодов и т. д.
Пример: «Илон Маск анонсировал запуск Starship в Техасе весной 2025 года».
NER выделит:
-
Илон Маск → персона (PER)
-
Starship → продукт / проект (PRODUCT)
-
Техас → гео (LOC)
-
весна 2025 года → дата (DATA)
NER широко используется в:
-
банках и страховых для выделения названий компаний, ИНН, адресов, сумм, реквизитов;
-
юридических документах для поиска дат, номеров договоров, участников;
-
чат‑ботах и техподдержке для извлечения имен, аккаунтов, заявок, местоположения;
-
медицине — извлекает коды диагнозов, названия препаратов, даты процедур;
-
логистике: выделение адресов, номеров накладных, получателей, грузов.
Звучит просто, но за этим стоит довольно сложная инженерия.
На практике я столкнулся с рядом проблем. Например, название организации может быть связано с географией — ООО «Москва». Также одно словосочетание порой содержит в себе две сущности — эта задача не решаема в рамках spaCy, так как сущности не могут накладываться друг на друга и определяться к двум разным категориям. Нередки случаи слипания слов друг с другом или с предлогами (решается постпроцессингом). И, наконец, данные могут попадать из многошумовых каналов, тогда проблем будет сразу несколько: «Клиент ООО АльфаБизнесСнаб прислал счет из Питера. Не уверен, что юр. лицо корректно, уточнить по ИНН 7 812 345 678» — тут и слитные слова, и пропущенные кавычки, и обрывки сущностей.
На чем основан NER
Исторически NER развивался в несколько этапов:
-
Правила и регулярные выражения
-
Быстро, дешево, работает только если данные строго формализованы.
-
Подходит исключительно для строго формализованных сущностей: email'ов, номеров телефонов, паспортов.
-
-
Классическое машинное обучение
-
Conditional Random Fields, SVM.
-
Требуют ручной инженерии признаков (features), работают лучше, чем регулярки.
-
-
Глубокое обучение
-
BiLSTM‑CRF, Transformer‑модели (BERT и пр.)
-
Дает лучшие результаты, особенно при обучении на больших объемах данных.
-
Следующим этапом решении задач можно назвать применение LLM. Но в реальности из практических соображений обычно применяется золотая середина — что‑то лучше регулярки, но не требующее GPU. Именно здесь и приходит на помощь практичный NER на CPU.
Когда NER — хорошее решение
Использовать NER стоит, если:
-
у вас структурированный или полуформализованный текст (новости, документы, соцсети),
-
требуется извлекать сущности вроде имен людей, компаний, дат, локаций,
-
вы не хотите или не можете использовать глубокие модели,
-
нужно решение, работающее быстро на CPU,
-
нет возможности использовать любую LLM.
Также можно быстро протестировать применимость — разметьте до 100 примеров одного класса, и уже станет понятно, можно ли строить модель или нет.
Когда NER не подходит
NER‑модели, особенно на базе spaCy или других легковесных фреймворков, плохо справляются с сущностями с высокой вариативностью и длинным хвостом, например, названиями товаров, адресами, описаниями или целыми предложениями.
Причина проста — нет ярко выраженных паттернов, нет повторяемости, нет контекста для обобщения. Модель не понимает, что объединяет эти сущности, особенно при маленьком датасете.
В этих случаях целесообразнее использовать классификацию текста, классификацию эмбеддингов, семантический поиск, LLM.
Аннотаторы для NER
Разметка данных — самая трудозатратная часть NER. К счастью, есть удобные инструменты.
Мне больше всего нравится NER Annotator
-
Прост в использовании
-
Позволяет разметить текст вручную, выбрать тип сущности
-
Можно экспортировать данные в JSON / IOB
Важные нюансы:
-
Можно выбрать разделители, чтобы исходный текст был разбросан на задания.
-
Слова выделяются целиком, то есть если у нас будет два слипшихся слова, то их нельзя будет разделить. Обязательно проверяйте пробелы, поэтому так важна предобработка исходника.
-
Работать командой неудобно — нельзя сохранить прогресс и выйти.

Еще несколько хороших аннотаторов:
-
Label Studio — open‑source аннотация
-
Prodigy — коммерческий, но очень быстрый
-
doccano — легкий веб‑аннотатор
Проблемы spaCy в контексте NER
spaCy популярен благодаря простоте и скорости — удобная вещь из коробки, запустить можно демо за 30 минут.
Дока по установке https://spacy.io/usage
Генерация конфига под GPU/CPU https://spacy.io/usage/training
Внимательно проверьте пути, подстройте под свои!
spaCy имеет ограничения:
-
Нет оценки вероятности. Нельзя узнать, насколько модель уверена в том, что фраза — это, например, организация. В коробке метрики отсутствуют, следовательно, сложно строить аналитику качества. Если вы хотите понимать, где модель ошибается, и на каких типах данных — потребуются внешние метрики.
-
Проблемы с кастомными сущностями, особенно если сущность длинная или переменная по структуре.
Аналоги spaCy
https://medium.com/ubiai‑nlp/mastering‑named‑entity‑recognition‑with‑bert‑ca8d04b67b18 — BERT
https://github.com/flairNLP/flair — FLAIR
https://stanfordnlp.github.io/CoreNLP/ner.html — StanfordCoreNLP
Давайте обучим свою первую NER-модель
Обучение модели NER из spaCy
Eстановим все необходимые либы:
!pip install -U pip setuptools wheel
!pip install -U 'spacy[cuda11x,transformers,lookups]'
!python3 -m spacy download ru_core_news_lg
import spacy
from spacy.tokens import DocBin
from tqdm import tqdm
import json
cv_data = json.load(open('path to your json','r'))
Если не создан базовый конфиг - создаем (но у меня он уже есть, поэтому этот пункт пропускаем):
!python3 -m spacy init fill-config /home/alex/ner/base_config.cfg /home/alex/ner/config.cfg
Функция, которая обрабатывает аннотации в формат библиотеки spaCy:
# Define a function to create spaCy DocBin objects from the annotated data
def get_spacy_doc(file, data):
# Create a blank spaCy pipeline
nlp = spacy.blank('ru')
db = DocBin()
# Iterate through the data
for text, annot in tqdm(data):
doc = nlp.make_doc(text)
annot = annot['entities']
print(annot)
ents = []
entity_indices = []
# Extract entities from the annotations
for start, end, label in annot:
skip_entity = False
for idx in range(start, end):
if idx in entity_indices:
skip_entity = True
break
if skip_entity:
continue
entity_indices = entity_indices + list(range(start, end))
try:
span = doc.char_span(start, end, label=label, alignment_mode='strict')
except:
continue
if span is None:
# Log errors for annotations that couldn't be processed
err_data = str([start, end]) + " " + str(text) + "n"
file.write(err_data)
else:
ents.append(span)
try:
doc.ents = ents
db.add(doc)
except:
pass
return db
Делим датасет на треин и тест 80/20:
from sklearn.model_selection import train_test_split
train, test = train_test_split(cv_data, test_size=0.2)
Избавляемся от пустых строк:
train = [x for x in train if x is not None]
test = [x for x in test if x is not None]
file = open('/home/alex/ner/train_file.txt','w')
Cоздаем spaCy DocBin объект для тренировочного и тестового датасета:
db = get_spacy_doc(file, train)
db.to_disk('/home/alex/ner/trained_models/train_data.spacy')
db = get_spacy_doc(file, test)
db.to_disk('/home/Ageev/ner/trained_models/test_data.spacy')
# Close the error log file
file.close()
Обучение модели подтягивает конфиг, указываем место для обученной модели, а также берем тренировочный и валидационный экземпляры, созданные ранее:
!python3 -m spacy train /home/Ageev/ner/config.cfg --output /home/Ageev/ner/trained_models/dir --paths.train /home/Ageev/ner/trained_models/train_data.spacy --paths.dev /home/Ageev/ner/trained_models/test_data.spacy --gpu-id 0
Что дальше?
Оцените качество на тестовом наборе, при необходимости — добавьте данные и улучшите разметку. При обучении должны получить примерно такую табличку с метриками:

В случае ошибок — дебаггер:
!python3 -m spacy debug data /home/alex/ner/config.cfg --paths.train /home/Ageev/ner/trained_models/train_data.spacy --paths.dev /home/Ageev/ner/trained_models/test_data.spacy
Проверка модели:
text =”тут ВАШ текст для проверки ВАШЕЙ модели”
import spacy
# Load the trained spaCy NER model from the specified path
nlp = spacy.load('/home/alex/ner/model-last')
#
# /home/Ageev/ner/trained_models/15000epochs/model-last
doc = nlp(text)
# Iterate through the named entities (entities) recognized by the model
for ent in doc.ents:
# Print the recognized text and its corresponding label
print(ent.text, " ->>>> ", ent.label_)
Постобработка
Постобработка под каждую задачу будет своя. Важно понимать бизнес-цель, глобальные метрики и где применяется модель. Постобработка может быть следующей:
-
Очистка мусора (лишние символы, цифры и тд).
-
Правильная обрезка границ сущности.
-
Выправление слов до начальных форм (через тот же GPT или LLM).
-
Нормализация (например, приведение к строчным буквам).
-
Фильтрация по длине.
-
Удаление дубликатов.
Спасибо всем, кто дочитал до конца. Приглашаю всех начинающих специалистов, интересующихся AI и ML, в мой телеграм-канал.
Автор: Alexandr1997ag