Про 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, в мой телеграм-канал.