Перейти к содержанию

Автоматический перевод

Эта статья была автоматически переведена с оригинальной английской версии.

Полное руководство по NER в 2026 году: энкодеры, LLM и 3-уровневая production-архитектура

Еще два года назад выбор подхода к NER означал компромисс между скоростью (модели-энкодеры) и точностью (LLM). Теперь этого компромисса больше нет. Модель GLiNER с 300 млн параметров достигает zero-shot точности UniNER с 13 млрд параметров, работая при этом в 100 раз быстрее. Более новый вариант bi-encoder масштабируется до миллионов типов сущностей с преимуществом по throughput в 130 раз по сравнению с исходным cross-encoder. Production-паттерн, который в итоге сформировался: использовать LLM для разметки данных, дообучать компактные энкодеры, деплоить через ONNX или Rust.

Я собрал companion-репозиторий и прогнал бенчмарки по всем основным подходам. Энкодеры выиграли рынок production NER. LLM по-прежнему критически важны, но уже не как inference-движки, а как генераторы обучающих данных. В этом гайде разобраны статьи, бенчмарки и deployment-паттерны, стоящие за этим сдвигом.

Companion repo: ner-field-guide — runnable-демо для GLiNER, экспорта в ONNX, pipeline LLM-as-teacher и структурированного извлечения с Instructor.

Кратко: больше не нужно строить NER-модели с нуля. Для zero-shot извлечения GLiNER на CPU через ONNX обходит даже новейшие LLM почти при нулевой стоимости. Для domain-specific задач pipeline LLM-as-teacher (LLM размечает → ревью → fine-tuning) дает энкодеры с F1 на уровне 90-93%+. Для кейсов, где нужно рассуждение, используйте Instructor или Outlines с LLM API — но закладывайте в бюджет \$2+/1K документов. 3-уровневая архитектура объединяет все три варианта.


Почему NER сейчас важнее, чем раньше: агенты, RAG и document intelligence

NER по-прежнему означает одно и то же: найти именованные спаны в тексте и классифицировать их. Изменилось то, где именно он применяется. Раньше это был последний шаг в NLP-pipeline, тихий вспомогательный компонент, о котором никто не писал посты в блогах. Теперь это строительный блок внутри RAG-систем, agent loop и платформ обработки документов. Именно поэтому волна исследований по NER в 2024-2026 годах важна не только в контексте академических бенчмарков.

RAG: более точный retrieval через извлечение сущностей

Стандартный RAG — разбить текст на чанки, построить embeddings, извлечь по similarity и надеяться на лучшее — разваливается, когда пользователь спрашивает о конкретных сущностях. Если кто-то спрашивает: «что Anthropic говорила о model safety в Q4 2024?», система должна распознать «Anthropic» и «Q4 2024» как фильтры, а не полагаться только на embedding similarity.

На этапе индексации вы извлекаете сущности из каждого чанка и сохраняете их как metadata: {"organizations": ["Anthropic"], "dates": ["Q4 2024"], ...}. Это позволяет фильтровать по сущностям до запуска vector search. Knowledge graph RAG (GraphRAG, LlamaIndex property graphs) идет дальше: NER плюс relation extraction строят граф, который умеет отвечать на multi-hop вопросы, с которыми плоские embeddings не справляются.

Во время выполнения запроса сущности, извлеченные из вопроса пользователя, управляют routing. Вопрос с названием компании идет в финансовый индекс; вопрос с названиями препаратов — в клиническую knowledge base. GLiNER здесь особенно хорош, потому что сущности в запросах непредсказуемы — нельзя переобучать модель под каждый новый тип сущности, о котором пользователь может спросить.

AI-агенты: превращение текста в структурированные факты

Агенты получают неструктурированный текст — веб-страницы, API-ответы, сообщения пользователя — и должны действовать на его основе. NER преобразует этот текст в структурированные факты, с которыми агент может рассуждать, сохранять их или передавать в инструменты.

Есть два места, где это особенно важно.

Первое — routing к инструментам. Когда пользователь говорит: «запланируй встречу с Sarah Chen из Accenture в четверг в 14:00», агенту нужно извлечь PERSON: Sarah Chen, ORGANIZATION: Accenture и DATETIME: Thursday 2pm до вызова calendar API. Энкодерная NER-модель делает это менее чем за 10 мс. LLM добавляет 1-2 секунды на вызов, и это накапливается по мере прохождения многошагового workflow, пока ваш «мгновенный» агент не начинает ощущаться так, будто у него диплом по философии.

Второе — трекинг сущностей между сообщениями в диалоге. Системам agent memory нужно понимать, что «Sarah» на 3-м ходе и «Ms. Chen» на 12-м — один и тот же человек. NER находит спаны, entity linking сопоставляет их одному ID.

Ограничение в обоих случаях — latency. Вызов NER на 200 мс внутри agent chain из 10 шагов добавляет 2 секунды воспринимаемой задержки. Именно поэтому для работы с сущностями внутри agent loop подходят энкодерные модели, а не LLM-based extraction.

Document intelligence: от изображений к структурированным данным

OCR превращает изображения в текст. NER превращает текст в структурированные поля. Вместе они обеспечивают цифровизацию документов в промышленном масштабе.

Стандартный pipeline: запустить OCR (Tesseract, Azure Document Intelligence, AWS Textract), получить текст и bounding boxes, затем запустить NER для извлечения структурированных полей — invoice_number, vendor_name, line_items, total, due_date — из того, что еще недавно было JPEG-файлом бумажного счета. Тот же подход работает для контрактов, медицинских записей и регуляторной отчетности.

Современные платформы объединяют три шага: понимание layout (это заголовок или ячейка таблицы?), извлечение сущностей (какой тип у этого текста?) и relation extraction (какие значения связаны между собой). GLiNER 2 умеет делать все три за один forward pass; один вызов модели может вернуть {vendor: "Acme Corp", amount: "\$4,200", due_date: "2026-04-15"} из счета.

Именно здесь становится критична стоимость. Бухгалтерская фирма среднего размера обрабатывает десятки тысяч счетов в месяц. Если отправлять каждый из них в LLM, даже в экономичную модель вроде GPT-5.4 Nano, это обойдется в сотни долларов в день. Fine-tuned GLiNER на CPU стоит центы. Ваш CFO заметит разницу. Путь такой: разметить 500 примеров счетов с помощью LLM, дообучить GLiNER на результатах, задеплоить на CPU за \$0.10/час.

Детекция PII и guardrails для LLM

Регулирование приватности (GDPR, HIPAA, CCPA) требует находить персональные данные до того, как они попадут в downstream-системы. Для LLM-деплойментов это значит сканировать входы до отправки в модель и выходы до передачи пользователю.

NER решает эту задачу напрямую. Модели деидентификации находят спаны PERSON, SSN, PHONE, EMAIL, ADDRESS и либо редактируют их, либо заменяют на синтетические эквиваленты. Клинические модели John Snow Labs достигают 96% F1 на детекции PHI (против Azure 91%, AWS 83%, GPT-4o 79%) при обработке 100K+ клинических заметок в день.

Для LLM guardrails NER работает как слой предварительного скрининга: сканировать пользовательский ввод на PII до отправки во внешний API, затем блокировать или анонимизировать. Это быстрее и проще, чем просить LLM модерировать саму себя. GLiNER здесь особенно полезен, потому что категории PII отличаются по юрисдикциям. Вы можете добавить новые типы сущностей вроде «генетическая информация» под новое регулирование без переобучения.


GLiNER меняет экономику NER с помощью модели на 300 млн параметров

GLiNER (NAACL 2024, Zaratiana et al.) сделал encoder-based NER конкурентоспособным по отношению к LLM при гораздо меньшей стоимости. Вместо того чтобы рассматривать NER как sequence labeling или генерацию текста, GLiNER трактует его как задачу matching: оценивает каждую кандидатную текстовую последовательность (каждый непрерывный спан слов вроде «Bill Gates» или «Microsoft») против каждого типа сущности, а затем оставляет пары с высоким score.

Модель принимает метки типов сущностей и входной текст как единую последовательность: [ENT] person [ENT] organization [ENT] date [SEP] Bill Gates founded Microsoft.... Двунаправленный transformer (DeBERTa-v3) кодирует все вместе.

На выходе модель строит два набора представлений: один для типов сущностей (из позиций токенов [ENT]), второй для текстовых спанов (путем объединения векторов начального и конечного токенов через небольшой FFN). Dot product между представлением спана и представлением типа сущности дает score.

После применения sigmoid получаем вероятность того, что спан от токена \(i\) до токена \(j\) принадлежит типу сущности \(t\): \(\\phi(i, j, t) = \\sigma(S_{ij}^T \\cdot q_t)\), где \(S_{ij}\) — вектор спана, созданный FFN, а \(q_t\) — embedding типа сущности из соответствующего токена [ENT] (Zaratiana et al., 2024, Eq. 1). Чтобы сохранять высокую скорость, длина спанов ограничена 12 токенами.

Архитектура GLiNER: токены типов сущностей и текстовые токены совместно кодируются DeBERTa, после чего представления спанов сравниваются с embedding-представлениями типов сущностей через dot product

На практике это означает, что в качестве label на этапе inference подходит любое описание на естественном языке. Переобучение не требуется. Вы просто передаете нужные типы сущностей («person», «adverse drug reaction», «financial instrument»), и модель оценивает спаны относительно них. Доступны три размера: GLiNER-S (50M параметров), GLiNER-M (90M), и GLiNER-L (300M). Обучающие данные взяты из датасета Pile-NER: 44,889 фрагментов с 240K спанов сущностей по 13K типам сущностей, все размечены с помощью ChatGPT. Обучение GLiNER-L занимает около 4 часов на одном A100 (Zaratiana et al., 2024).

Результаты бенчмарков

Zero-shot результаты из Zaratiana et al. (2024), Таблицы 1 и 2:

Model Params CrossNER F1 Avg (20 datasets)
GLiNER-L 300M 60.9% 47.8%
GoLLIE 7B 58.0% --
UniNER-13B 13B 55.6% --
GLiNER-M 90M 55.4% --
UniNER-7B 7B 53.7% 45.7%
GLiNER-S 50M 52.7% --
ChatGPT (GPT-3.5) -- 47.5% 36.5%

GLiNER-M с 90 млн параметров почти совпадает с UniNER-13B (55.4% против 55.6% F1), будучи при этом в 140 раз меньше. Даже крошечный GLiNER-S на 50M обходит ChatGPT (GPT-3.5) на 5 пунктов F1. Мультиязычная версия — обученная только на английских данных — обходит ChatGPT в 8 из 10 неанглоязычных языков (Zaratiana et al., 2024). Замечание: более новые LLM (GPT-5.4, Claude 4.6), вероятно, покажут лучший результат на этих бенчмарках, но разрыв в стоимости только вырос — эти модели по-прежнему на порядки дороже, чем энкодер на 90M на CPU.

Экосистема большая: 280+ совместимых с GLiNER моделей на HuggingFace, около 350,000 загрузок с PyPI в месяц, около 2,800 звезд на GitHub. Есть варианты для биомедицины, детекции PII, новостей и мультиязычной поддержки.

Из quickstart.py:

from gliner import GLiNER

model = GLiNER.from_pretrained("urchade/gliner_medium-v2.1")
text = "Bill Gates founded Microsoft on April 4, 1975."
labels = ["person", "organization", "date"]
entities = model.predict_entities(text, labels, threshold=0.5)

for entity in entities:
    print(f"  {entity['text']} => {entity['label']} ({entity['score']:.3f})")
# Bill Gates => person (0.987)
# Microsoft => organization (0.991)
# April 4, 1975 => date (0.974)

Как GLiNER соотносится со spaCy

Любой гайд по NER будет неполным без spaCy~21 млн загрузок в месяц, неубиваемого таракана мира NLP-библиотек (в хорошем смысле — он переживает все). Но он решает другую задачу, чем GLiNER.

Пайплайны spaCy (en_core_web_sm, en_core_web_trf) выполняют closed-vocabulary NER: фиксированный набор типов сущностей (PERSON, ORG, GPE, DATE и т. д.), определенный на этапе обучения. Нужен новый тип сущности? Собирайте размеченные данные и переобучайте. Transformer-backed en_core_web_trf достигает 89.8% F1 на OntoNotes 5.0, но только для своих 18 предопределенных типов.

GLiNER выполняет open-vocabulary NER: на этапе inference работает любой label, переобучение не нужно. Это делает его лучшим выбором, когда типы сущностей заранее неизвестны, часто меняются или специфичны для домена («adverse drug reaction», «financial instrument», «threat indicator»).

Моя рекомендация: используйте spaCy для стандартных типов сущностей, где pretrained-пайплайны хорошо валидированы. Используйте GLiNER, когда нужны гибкие zero-shot типы или когда pipeline должен адаптироваться без переобучения. Они хорошо сочетаются — spaCy берет на себя tokenization и sentence splitting, GLiNER — извлечение сущностей.


UniNER и NuNER: насколько маленькой может быть модель?

UniNER (ICLR 2024, Zhou et al.) и NuNER (EMNLP 2024, Bogdanov et al.) оба дистиллируют аннотации LLM в более компактные NER-модели — но расходятся во мнении, насколько маленькой может быть такая модель.

UniNER: путь максималиста

UniNER дообучает LLaMA-7B/13B на 44,889 NER-парах (240K сущностей, 13K типов), сгенерированных ChatGPT. Для каждого типа сущности модель отвечает на вопрос «What describes [type] in the text?» и выдает JSON-списки. Ключевой прием обучения: frequency-based negative sampling поднимает F1 с 31.5% до 53.4% (Zhou et al., 2024).

UniNER-7B достигает 41.7% zero-shot F1 на 43 датасетах — это на 7 пунктов выше, чем у ChatGPT с 34.9%. Вариант на 13B доходит до 43.4%, то есть выигрывает только 1.7 пункта при почти двукратном росте compute (Zhou et al., 2024).

Проблема для production: как autoregressive-модель на 7B, UniNER требует N forward pass для N типов сущностей, нуждается в 14GB+ VRAM (то есть ваш GPU-бюджет закончился еще до обеда) и имеет ограничительную лицензию CC BY-NC 4.0.

NuNER: путь минималиста

NuNER стартует с RoBERTa-base (125M параметров) и использует contrastive training на 4.38 млн аннотаций GPT-3.5 по 200K концептам — суммарная стоимость аннотации менее \$500. После обучения concept encoder выбрасывается; text encoder встраивается в любой стандартный NER-pipeline как замена RoBERTa (Bogdanov et al., 2024).

Результаты: NuNER обходит обычную RoBERTa на 6-15 пунктов F1 во всех few-shot режимах. Имея всего около дюжины примеров на тип сущности, NuNER сопоставим с UniNER-7B, будучи в 56 раз меньше (Bogdanov et al., 2024).

Обе статьи показывают одно и то же: дистилляция аннотаций LLM в более компактные модели дает NER, который превосходит teacher-модель. И 7B параметров не нужны — 125M достаточно, когда есть данные для fine-tuning, плюс MIT-лицензия и удобный inference на CPU.


GLiNER 2: одна модель, четыре задачи

У исходной экосистемы GLiNER появилась растущая проблема: отдельные модели для NER (GLiNER), relation extraction (GLiREL), classification (GLiClass) и document-level RE (GLiDRE) — и каждая требует собственного деплоя, собственного Docker-контейнера и собственной строки в таблице «сервисы, чтобы они только не сломались». GLiNER 2 (EMNLP 2025, Zaratiana et al.) объединяет все четыре в одну модель на 205 млн параметров с schema-driven интерфейсом.

Архитектура сохраняет дизайн cross-encoder, но увеличивает контекст до 2,048 токенов (в 4 раза больше исходного) и добавляет декларативные схемы для определения задач извлечения. Обучение использует 135,698 реальных документов, размеченных GPT-4o, плюс 118,636 синтетических примеров (Zaratiana et al., 2025).

В zero-shot CrossNER GLiNER 2 получает 0.590 F1 — близко к 0.599 у GPT-4o по измерениям из статьи (середина 2025 года). Более новые модели вроде GPT-5.4, вероятно, набирают больше, но при этом работают за долю скорости GLiNER 2 и при гораздо более высокой стоимости. Для classification модель показывает 0.72 в среднем по 7 бенчмаркам, обгоняя DeBERTa-v3-large (0.69). На CPU GLiNER 2 выполняет classification за 130-208 мс независимо от числа labels. DeBERTa масштабируется линейно: 1,714 мс для 5 labels, 16,897 мс для 50 (Zaratiana et al., 2025).

from gliner2 import GLiNER2
extractor = GLiNER2.from_pretrained("fastino/gliner2-base-v1")

# Multi-task composition in ONE forward pass
schema = (extractor.create_schema()
    .entities({"person": "Names of people", "company": "Organization names"})
    .classification("sentiment", ["positive", "negative", "neutral"])
    .relations(["works_for", "founded", "located_in"])
    .structure("product_info")
        .field("name", dtype="str")
        .field("price", dtype="str"))
results = extractor.extract(text, schema)

Один deployment модели заменяет четыре. Инфраструктура проще, точность конкурентоспособна.


Bi-encoder: масштабирование NER до миллиона labels

Исходный GLiNER кодирует labels и текст совместно — и это создает bottleneck. Чем больше типов сущностей, тем длиннее входная последовательность, и производительность быстро падает после примерно 30 типов. GLiNER bi-encoder (февраль 2026, Stepanov et al.; arXiv 2602.18487) решает это, разделяя кодирование текста и labels на два отдельных transformer-а.

Cross-encoder против bi-encoder: cross-encoder совместно кодирует labels и текст, тогда как bi-encoder использует отдельные энкодеры с предвычисленными embedding-представлениями labels

Text encoder использует ModernBERT (семейство Ettin), label encoder — sentence transformers (BGE или MiniLM). Спаны и labels оцениваются через dot product. Ключевой прием: embedding-представления типов сущностей можно один раз предвычислить и закэшировать. На этапе inference кодировать нужно только текст — lookup по labels выполняется мгновенно.

Доступны четыре размера модели, все протестированы на CrossNER (Stepanov et al., 2026, Table 1):

Model Parameters CrossNER F1 Throughput (H100) With Pre-computed Labels
bi-edge-v2.0 60M 54.0% 13.64 ex/s 24.62 ex/s
bi-small-v2.0 108M 57.2% 7.99 ex/s 15.22 ex/s
bi-base-v2.0 194M 60.3% 5.91 ex/s 9.51 ex/s
bi-large-v2.0 530M 61.5% 2.68 ex/s 3.60 ex/s

При 1,024 типах сущностей bi-encoder (edge, pre-computed) теряет всего 5.2% throughput по сравнению с одним label. Cross-encoder теряет 98.7% (10.7 → 0.14 ex/s). Это дает преимущество по throughput в 130 раз при масштабировании. При 100 типах сущностей на одном H100 bi-encoder обрабатывает 1.96 млн предсказаний в день против 368K у cross-encoder (Stepanov et al., 2026).

И с точностью тоже все в порядке. Bi-encoder-large достигает 61.5% CrossNER F1, немного обходя cross-encoder с 60.9%. Авторы рекомендуют bi-base-v2.0 (194M) как оптимальную точку, дающую 98% точности большой модели при скорости в 2.6 раза выше (Stepanov et al., 2026).

from gliner import GLiNER

model = GLiNER.from_pretrained("knowledgator/gliner-bi-base-v2.0")

# Pre-compute embeddings for massive label sets — encode once, use forever
entity_types = ["person", "organization", "date"]  # Can be thousands or millions
entity_embeddings = model.encode_labels(entity_types, batch_size=8)

# Inference only encodes text — labels are a cached lookup
outputs = model.batch_predict_with_embeds(texts, entity_embeddings, entity_types)

Среди применений — biomedical NER по онтологии UMLS (4M+ концептов), корпоративные таксономии, которые развиваются без переобучения модели, и entity linking через companion-фреймворк GLiNKER.


LLM как teacher: pipeline за \\(70, который обходит модель за \\\)8/час

Паттерн LLM-as-teacher стал стандартным production-pipeline. Три исследования показывают, сколько это стоит и что дает.

Pipeline LLM-as-teacher: LLM размечает сырые данные, люди проверяют подмножество, энкодер дообучается и деплоится при стоимости в 80 раз ниже

Кейс CFM

Capital Fund Management извлекали названия компаний из ~900K заголовков финансовых новостей. Zero-shot GLiNER показал 87.0% F1. Они использовали Llama 3.1-70b (самую сильную открытую модель на тот момент) для аннотирования всего датасета примерно за 8 часов при стоимости ~\$70, а затем человек проверил 2,714 примеров через Argilla еще за 8 часов.

Fine-tuning GLiNER на этих данных поднял качество до 93.4% F1 — выше даже, чем у teacher-модели Llama 70b с 92.7%. Fine-tuned модель работает по цене \\(0.10/час на CPU** против \\\)8/час у teacher — в 80 раз дешевле** и при этом точнее (CFM Case Study). Сегодня вы получили бы еще более качественную teacher-разметку с Llama 4 Maverick или GPT-5.4 Mini при сопоставимой или меньшей стоимости.

Исследование Refuel AI

Refuel AI сравнили LLM-разметку на 8 NLP-датасетах, включая CoNLL-2003. GPT-4 (март 2023) показал 88.4% согласия с ground truth — выше, чем 86.2% у опытных human-annotators. LLM были в 20 раз быстрее и в 7 раз дешевле. Их подход с ансамблированием — дешевые модели для простых примеров, GPT-4 для сложных — дал 95%+ согласия, сохраняя умеренную стоимость (Refuel AI Technical Report). С появлением GPT-5.4 и Claude 4.6 качество teacher-моделей стало только выше.

Клинический NER от Tonic.ai

Tonic.ai показали, что паттерн работает и для clinical NER. Только на основе LLM-аннотаций они обучили модель RoBERTa LoRA до 0.70 F1 на NCBI Disease Corpus (против 0.81 при полной human-разметке). На задаче извлечения healthcare ID модель достигла 0.947 F1 — выше production-порога в 0.914 — без единого примера с human-разметкой.

Стандартный pipeline

Production-рецепт стабилизировался в шесть шагов:

  1. Написать annotation guidelines на естественном языке
  2. Создать небольшой validation set с human-разметкой (50-200 документов)
  3. Использовать LLM (GPT-5.4 Mini, Llama 4 Maverick или Qwen3.5) для разметки основного обучающего массива
  4. Проверить часть выборки через Argilla или Label Studio
  5. Дообучить компактный энкодер (GLiNER, SpanMarker, RoBERTa)
  6. Задеплоить с inference-стоимостью в 16-80 раз ниже

Стоимость NER теперь больше не выглядит как «разметить все обучающие данные, наняв армию студентов». Теперь это «собрать хороший validation set, написать понятные инструкции и дать LLM сделать остальное».


Где GLiNER не справляется, и почему LLM остаются необходимыми

Бенчмарк Sease (октябрь 2025) сравнил GLiNER и GPT-4.1-mini на 30 задачах query parsing. GPT-4.1-mini дал 100% полностью корректных ответов. GLiNER — 53% (16 из 30). Но GLiNER отвечал за 0.08 секунды против 1.21 секунды у LLM — в 15 раз быстрее.

GLiNER проваливается по трем конкретным паттернам:

  1. Неявные сущности: извлечение «event» из «Elton John performed at Madison Square Garden» — в тексте буквально не написано «event», но LLM выводит «concert»
  2. Чувствительность к формулировке label: «2022» получает score 0.388 для «date», но 0.958 для «year» — небольшие изменения label приводят к большим скачкам score
  3. Value mapping: GLiNER возвращает точный surface text («family houses») вместо канонического значения («Single family house») — LLM делают это естественно

Вложенные и пересекающиеся сущности

GLiNER также испытывает трудности с вложенными сущностями. В «New York University» человек может разметить и «New York» (LOCATION), и «New York University» (ORGANIZATION). GLiNER выбирает только спан с наивысшим score. Это важно в биомедицинских текстах («acute myeloid leukemia» содержит и заболевание, и модификатор) и юридических текстах (вложенные организационные иерархии). Специализированные модели умеют работать с вложенностью, но плоский дизайн спанов в GLiNER — нет.

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


Оценка NER: метрики, ловушки и построение тестовых наборов

Я видел, как команды выпускали модели с 95% F1 на тестовом наборе, которые проваливались в production — потому что test set не отражал реальное распределение документов. Ваш test set — это не ваши production-данные. Звучит очевидно. Я все равно видел, как на этом ломались.

Основные метрики

  • Entity-level F1: стандартная метрика. Предсказание считается корректным только если и границы спана, и тип точно совпадают с ground truth. Именно ее приводит большинство статей.
  • Token-level F1: оценивает каждый токен независимо. Завышает результаты, потому что частично правильное извлечение длинной сущности дает частичный credit. Лучше использовать entity-level F1.
  • Precision vs Recall: стоимость ошибок часто асимметрична. Для деидентификации важнее recall — пропустить имя хуже, чем избыточно что-то замазать. Для извлечения в базу данных важнее precision — ложные записи портят downstream-анализ.

Частые ошибки в оценке

  1. Завышение из-за partial match: извлечено «Bill», тогда как gold label — «Bill Gates» — некоторые скрипты засчитывают это как частичное совпадение. Используйте exact span matching, если нет веской причины поступать иначе.
  2. Путаница типов: если «Microsoft» правильно найден как спан, но размечен как PERSON вместо ORG, это должно давать ноль. Проверьте, что ваш evaluation code обрабатывает это корректно.
  3. Утечка в test set: если сущности в тесте пересекаются с обучающими, score завышается. Zero-shot бенчмарки (CrossNER, Few-NERD) нужны именно для проверки generalization.

Как собрать domain test set

Для production-оценки я рекомендую:

  1. Брать выборку из production-данных, а не из отобранных вручную красивых примеров. Включайте те грязные документы, которые модель реально увидит.
  2. 200-500 размеченных документов дают стабильные оценки F1. Ниже 100 доверительные интервалы слишком широкие.
  3. Минимум два annotator-а, с оценкой inter-annotator agreement (Cohen's kappa > 0.8). Если люди не согласны, модель лучше не сделает.
  4. Стратификация по сложности — простые кейсы (чистый текст, стандартные типы) и сложные кейсы (неоднозначные сущности, жаргон, шумный текст).

Production NER в четырех индустриях

Вот самые зрелые NER-деплойменты, которые я нашел, с конкретными цифрами.

Healthcare

В healthcare наиболее зрелый инструментарий для NER. У John Snow Labs более 2,500 pretrained-моделей, из них 1,200+ для healthcare, покрывающих 400+ клинических типов сущностей, сопоставленных с ICD-10, SNOMED CT, LOINC и RxNorm. Их модели деидентификации достигают 96% F1 (против Azure 91%, AWS 83%, GPT-4o 79%), а Providence St. Joseph Health обрабатывает 100K-500K клинических заметок в день.

Open-source-проект OpenMed project предлагает 380+ biomedical NER-моделей с 29.7 млн загрузок на HuggingFace и ставит новый state-of-the-art на 10 из 12 публичных биомедицинских бенчмарков.

Financial NER

Основной use case — извлечение данных из SEC filing. Finance NLP от John Snow Labs извлекает 11+ типов сущностей из отчетов 10-K/10-Q (адреса, тикеры, финансовые годы, фондовые биржи). Варианты FinBERT-MRC достигают 0.87-0.93 F1 на задачах финансового NER. Ключевая сложность: длинные документы и вложенные сущности в сложных финансовых инструментах.

E-commerce

NER в очень большом масштабе. Система Walmart EAMT system (KDD 2023) обучается на 965 млн запросов с ~60 labels сущностей и дает 0.51% роста GMV в A/B-тестах — это миллионы дополнительной выручки. Фреймворк Home Depot TripleLearn (AAAI 2021) поднял NER F1 с 69.5 до 93.3 через итеративное обучение.

Cybersecurity

Система iACE system (CCS 2016) обработала 71,000 статей из 45 security-блогов, извлекая 900K IOC-объектов с 98% precision и 93% recall. Современные системы, такие как CyNER, объединяют DeBERTa (F1 >91%) с regex-эвристиками для IOC. Унифицированный датасет CyberNER (2025) гармонизирует четыре датасета в 21 тип сущности, выровненный с STIX 2.1; RoBERTa на нем достигает 0.736 F1.


Оптимизация deployment: от Python к inference быстрее миллисекунды

В companion-репозитории я протестировал три способа ускорить GLiNER для production.

Экспорт в ONNX

У GLiNER есть нативная конвертация в ONNX, а на HuggingFace уже доступны предконвертированные модели (onnx-community/gliner_small-v2.1). ONNX Runtime дает ускорение в 1.5-3 раза на CPU по сравнению с PyTorch, с четырьмя уровнями оптимизации — от базового до mixed-precision.

Из onnx_export.py:

# Export with quantization
# python convert_to_onnx.py --model_path model/ --save_path onnx/ --quantize True

# Load ONNX model — same API, faster inference
from gliner import GLiNER
model = GLiNER.from_pretrained("path/to/model", load_onnx_model=True)

# Same predict_entities call, 1.5-3x faster on CPU
entities = model.predict_entities(text, labels, threshold=0.5)

INT8-квантизация

Dynamic quantization уменьшает размер моделей в 2.4 раза (438MB → 181MB) при потере менее 0.6% F1. Скорость на CPU возрастает в 1.8 раза. На Intel VNNI CPU с ONNX Runtime INT8 дает до 6x ускорения относительно PyTorch FP32.

from onnxruntime.quantization import quantize_dynamic, QuantType

# One-line quantization — 2.4x smaller, <1% F1 loss
quantize_dynamic("gliner.onnx", "gliner_int8.onnx", weight_type=QuantType.QInt8)

gline-rs: реализация на Rust

gline-rs (Apache 2.0) убирает overhead Python. На CPU: 6.67 seq/s против 1.61 у Python — ускорение в 4.1 раза. На RTX 4080: 248.75 seq/s (бенчмарки gline-rs). Поддерживаются span- и token-модели, GPU/NPU через ONNX Runtime, и есть crate на crates.io.

use gliner::{GLiNER, TokenMode, Parameters, RuntimeParameters, TextInput};

let model = GLiNER::<TokenMode>::new(
    Parameters::default(), RuntimeParameters::default(),
    "tokenizer.json", "model.onnx")?;

let input = TextInput::from_str(
    &["My name is James Bond."], &["person", "vehicle"])?;
let output = model.inference(input)?;
// => "James Bond" : "person" (99.7%)

Пакет fast-gliner предоставляет Python bindings через PyO3 — скорость Rust с эргономикой Python.

Сводка по стеку оптимизаций

Optimization Speedup vs PyTorch Model Size F1 Impact Best For
ONNX Runtime 1.5-3x Same None Quick win, any hardware
INT8 Quantization 3-6x 2.4x smaller <0.6% loss CPU deployment, memory-constrained
gline-rs (Rust) 4.1x (CPU) ONNX format None High-throughput, latency-critical
gline-rs + INT8 4-8x 2.4x smaller <1% loss Production at scale

Структурированное извлечение: Instructor vs Outlines

Когда нужна большая гибкость, чем дают энкодерные модели — неявные сущности, рассуждение, ontology mapping — две библиотеки решают задачу структурированного извлечения через LLM.

Instructor (~12,600 звезд на GitHub, ~8.8 млн загрузок в месяц по состоянию на март 2026) от Jason Liu патчит LLM SDK так, чтобы они принимали Pydantic response models, с автоматическим retry при ошибке валидации. Он поддерживает 15+ провайдеров и вдохновил native structured output feature в OpenAI.

Из structured_extraction.py:

import instructor
from pydantic import BaseModel
from typing import List, Literal
from openai import OpenAI

class Entity(BaseModel):
    name: str
    label: Literal["PERSON", "ORGANIZATION", "LOCATION"]

class ExtractEntities(BaseModel):
    entities: List[Entity]

client = instructor.from_openai(OpenAI())
result = client.chat.completions.create(
    model="gpt-5.4-mini", temperature=0.0,
    response_model=ExtractEntities,
    messages=[{"role": "user", "content": "BioNTech SE acquired InstaDeep in the U.K."}])
# entities=[Entity(name='BioNTech SE', label='ORGANIZATION'), ...]

Outlines (~13,600 звезд на GitHub) от dottxt использует другой подход: constrained token generation через Finite State Machines. Вместо генерации результата с последующим retry при провале валидации, Outlines просто не позволяет сгенерировать недопустимые токены — 100% соответствие схеме, без повторных попыток. Бенчмарки AWS показывают 98% соответствия схеме против 76% у post-generation validation, а также 5x более быструю генерацию по сравнению с unconstrained generation с post-validation retry.

import outlines

model = outlines.models.transformers("microsoft/Phi-3-mini-128k-instruct")
generator = outlines.generate.json(model, ExtractEntities)
result = generator("Extract entities from: BioNTech SE acquired InstaDeep in the U.K.")

Выбор зависит от того, где выполняются ваши модели. Instructor лучше для облачных LLM API — быстрое прототипирование, multi-provider support, привычные паттерны Pydantic. Outlines выигрывает для локальных моделей, где нужны гарантии формата без внешних зависимостей. Оба инструмента подходят для NER-подобного извлечения, но ни один не приближается к throughput энкодеров: Instructor добавляет 200 мс-2 с API latency на вызов, а Outlines зависит от скорости локальной модели. Для high-throughput NER энкодеры все еще в 10-100 раз быстрее.


3-уровневая production-архитектура

Вот как я бы собрал все это вместе для production NER.

3-уровневая архитектура: энкодерные модели обрабатывают 90%+ объема при низкой стоимости, GLiNER 2 берет на себя multi-task extraction, а LLM закрывают самые сложные 10% и одновременно обучают модели Tier 1

Tier 1 — энкодерные модели для известных типов сущностей. GLiNER (cross-encoder для <30 типов, bi-encoder для 30+), дообученный через pipeline LLM-as-teacher. Деплой через ONNX + INT8 или gline-rs. Обрабатывает >90% объема с latency менее 50 мс и почти нулевой стоимостью. Bi-encoder масштабируется до миллионов типов сущностей с помощью pre-computed embeddings.

Tier 2 — GLiNER 2 для multi-task extraction. Когда в одном запросе нужны NER + classification + relation extraction + structured data, модель GLiNER 2 на 205M параметров заменяет четыре отдельных deployment-а. Работает на CPU за 130-208 мс независимо от числа labels — хороший вариант для document-pipeline, где в одном проходе нужны несколько задач извлечения.

Tier 3 — LLM для extraction с интенсивным reasoning. Когда сущности неявны, требуют контекстного вывода или ontology mapping, направляйте запрос в LLM (GPT-5.4 Mini, Claude Sonnet 4.6, Llama 4 Maverick) через Instructor (облако) или Outlines (локально). Это закрывает ~10% запросов, которые пропускают энкодеры. Те же LLM одновременно генерируют обучающие данные для Tier 1.

Во сколько это обходится? Fine-tuned GLiNER дает 93.4% F1 при \\(0.10/час на CPU**, что соответствует **92.7% F1** у его teacher-модели Llama-70b при **\\\)8/час — в 80 раз дешевле.


Компромиссы и ограничения

Ничто из этого не дается бесплатно. В ML всегда есть подвох — вопрос только в том, когда именно вы его обнаружите.

Ошибки LLM-as-teacher распространяются дальше. Если LLM систематически ошибается на конкретном типе сущностей (например, путает названия дочерних компаний с материнскими), fine-tuned энкодер унаследует этот bias. Решение — таргетированное human review: сосредоточьте усилия на типах сущностей, где уверенность LLM низкая или нестабильная, а не на случайной выборке.

Потери от квантизации неравномерны. Средняя потеря около 0.6% F1 от INT8 может быть выше на редких типах сущностей со сложными граничными паттернами (химические соединения, многословные аббревиатуры). Всегда бенчмаркайте quantized-модели на своих конкретных типах сущностей, а не только по aggregate F1.

Когда 3-уровневая архитектура — избыточна. Один домен, хорошо определенные типы сущностей, 500+ размеченных примеров? Fine-tuned RoBERTa или spaCy pipeline проще и достаточны. 3-tier-паттерн окупается, когда есть (a) несколько доменов, (b) эволюционирующие типы сущностей, или (c) смесь простых и сложных задач извлечения. Если вы просто извлекаете имена и даты из счетов, достаточно одного Tier 1.

Предел качества bi-encoder. Bi-encoder обменивает joint attention на throughput. Когда семантика label взаимодействует с текстовым контекстом («date» vs «year» vs «period» для одного и того же спана), cross-encoder все еще выигрывает. Используйте cross-encoder для критичных задач с малым числом labels; bi-encoder — для широты покрытия.


Ключевые выводы

  1. Энкодеры победили. GLiNER и варианты bi-encoder обходят LLM на стандартных NER-бенчмарках при стоимости в 80-130 раз ниже. Даже при снижении цен за счет GPT-5.4 Nano и Llama 4 использование LLM для каждого NER-запроса больше не оправдано.
  2. LLM необходимы — но как teacher-модели. Они размечают обучающие данные за \$70, которые стоили бы тысячи долларов при human-аннотации, а fine-tuned энкодер обычно в итоге обходит LLM, которая его обучала.
  3. Bi-encoder открывает масштаб до миллиона labels. Pre-computed embeddings решают проблему квадратичной сложности, с потерей throughput всего 5.2% при 1,024 labels.
  4. GLiNER 2 консолидирует multi-task extraction. Одна модель на 205M для NER + classification + RE + structured extraction.
  5. Оценивайте на своих данных. Используйте entity-level F1, собирайте test set из production-документов и бенчмаркайте quantized-модели на своих конкретных типах сущностей.
  6. Используйте гибридный паттерн. Tier 1/2 — для быстрого извлечения, Tier 3 — для reasoning. Те же LLM, что обрабатывают самые сложные 10%, генерируют обучающие данные для остальных 90%.

References

Papers

Industry Papers

Case Studies

Tools and Frameworks

  • ner-field-guide demo repo - companion-демо к этой статье: быстрый старт с GLiNER, экспорт в ONNX, LLM-as-teacher и structured extraction.
  • gline-rs: Rust reimplementation of GLiNER - ускорение на CPU в 4.1 раза по сравнению с Python, лицензия Apache 2.0.
  • Instructor - структурированное извлечение из LLM через Pydantic-модели, ~8.8M загрузок в месяц.
  • Outlines - constrained token generation через FSM, гарантирует соответствие схеме.
  • AWS: Structured Output with Outlines - бенчмарк с 98% соответствия схеме.