Faza 05 · lecția 06

Recunoașterea entităților denumite (NER)

Scopul lecției: Extrageți numele. Pare ușor până când apar limite ambigue, entități imbricate și jargon de domeniu.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
NLP — de la fundamente la subiecte avansate
Lectură
19 min.
Verificat
Cuprinsul lecției
  1. Problema
  2. Conceptul
  3. Construiți
  4. Pasul 1: funcții ajutătoare pentru etichetarea BIO
  5. Pasul 2: caracteristici construite manual
  6. Pasul 3: o bază de referință simplă cu reguli și dicționar
  7. Pasul 4: pasul CRF (schiță, nu implementare completă)
  8. Pasul 5: ce adaugă un BiLSTM-CRF
  9. Folosiți
  10. NER bazat pe LLM (opțiunea din 2026)
  11. Unde NER clasic încă învinge
  12. Unde se destramă
  13. Livrați
  14. Exerciții
  15. Termeni-cheie
  16. Lecturi suplimentare

Extrageți numele. Pare ușor până când apar limite ambigue, entități imbricate și jargon de domeniu.

Tip: Construire Limbaje: Python Cerințe prealabile: Faza 5 · 02 (BoW + TF-IDF), Faza 5 · 03 (reprezentări vectoriale ale cuvintelor) Timp: ~75 de minute

Problema

„Apple sued Google over its iPhone search deal in the US.” Cinci entități: Apple (ORG), Google (ORG), iPhone (PRODUCT), search deal (poate), US (GPE). Un sistem NER bun le extrage pe toate cu tipurile corecte. Unul slab ratează iPhone, confundă Apple, fructul, cu Apple, compania, și etichetează „US” ca PERSON.

NER este mecanismul de lucru din spatele fiecărui flux de extragere structurată: analiza CV-urilor, scanarea jurnalelor de conformitate, anonimizarea fișelor medicale, înțelegerea interogărilor de căutare, fundamentarea răspunsurilor unui chatbot, extragerea din contracte juridice. Rareori îl observați, dar depindeți mereu de el.

Această lecție parcurge traseul clasic (bazat pe reguli, HMM, CRF) către cel modern (BiLSTM-CRF, apoi Transformere). Fiecare pas rezolvă o limitare specifică a celui anterior. Acesta este tiparul lecției.

Conceptul

Etichetarea BIO (sau BILOU) transformă extragerea entităților într-o problemă de etichetare a secvențelor. Etichetați fiecare token cu B-TYPE (începutul entității), I-TYPE (interiorul entității) sau O (în afara oricărei entități).

Apple    B-ORG
sued     O
Google   B-ORG
over     O
its      O
iPhone   B-PRODUCT
search   O
deal     O
in       O
the      O
US       B-GPE
.        O

Entitățile cu mai multe tokenuri se leagă în lanț: New B-GPE, York I-GPE, City I-GPE. Un model care înțelege BIO poate extrage segmente de lungime arbitrară.

Evoluția arhitecturilor:

  • Bazată pe reguli. Regex + căutări în gazetteer. Precizie mare pentru entitățile cunoscute, acoperire zero pentru cele noi.
  • HMM. Model ascuns Markov. Probabilitatea de emisie a tokenului dată eticheta și probabilitatea de tranziție etichetă-la-etichetă. Decodare Viterbi. Antrenat pe date etichetate.
  • CRF. Câmp aleator condițional. Ca HMM, dar discriminativ, astfel încât pot fi combinate caracteristici arbitrare (forma cuvântului, capitalizare, cuvinte vecine). Rămâne mecanismul clasic de producție în 2026 pentru implementări cu resurse reduse.
  • BiLSTM-CRF. Caracteristici învățate în locul celor construite manual. LSTM citește propoziția în ambele direcții, iar stratul CRF de deasupra impune secvențe coerente de etichete.
  • Bazată pe Transformere. Ajustați fin BERT cu un cap de clasificare a tokenurilor. Cea mai bună acuratețe. Cel mai mult calcul.

Notă tehnică a traducerii: Această comparație este o schemă didactică, nu un clasament universal. Acuratețea, latența, memoria și robustețea depind de limbă, domeniu, ontologie, date etichetate, model, hardware și protocolul de evaluare. Un CRF sau un model bazat pe reguli poate fi alegerea potrivită în anumite implementări, dar nu există un statut garantat de „mecanism de lucru” ori de „cea mai bună acuratețe” pentru toate sarcinile.

ner-bio-tagging

Ilustrație originală: etichetarea BIO și abordările NER

Ilustrație originală din assets/ner.svg al lecției-sursă: prezintă etichetarea BIO și comparația dintre abordările bazate pe reguli, CRF, BiLSTM-CRF și Transformer. Etichetele originale în limba engleză din SVG rămân neschimbate și sunt explicate în textul românesc.

Notă tehnică a traducerii: Cheia figurii ner-bio-tagging este mapată explicit la asset-ul-sursă original ner.svg; copia locală are același conținut după normalizarea documentată CRLF→LF. Diagrama este păstrată ca sursă originală, nu este o ilustrație generată sau un substitut aproximativ.

Construiți

Pasul 1: funcții ajutătoare pentru etichetarea BIO

def spans_to_bio(tokens, spans):
    labels = ["O"] * len(tokens)
    for start, end, label in spans:
        labels[start] = f"B-{label}"
        for i in range(start + 1, end):
            labels[i] = f"I-{label}"
    return labels


def bio_to_spans(tokens, labels):
    spans = []
    current = None
    for i, label in enumerate(labels):
        if label.startswith("B-"):
            if current:
                spans.append(current)
            current = (i, i + 1, label[2:])
        elif label.startswith("I-") and current and current[2] == label[2:]:
            current = (current[0], i + 1, current[2])
        else:
            if current:
                spans.append(current)
                current = None
    if current:
        spans.append(current)
    return spans
>>> tokens = ["Apple", "sued", "Google", "over", "iPhone", "sales", "."]
>>> labels = ["B-ORG", "O", "B-ORG", "O", "B-PRODUCT", "O", "O"]
>>> bio_to_spans(tokens, labels)
[(0, 1, 'ORG'), (2, 3, 'ORG'), (4, 5, 'PRODUCT')]

Notă tehnică a traducerii: Exemplul presupune etichete BIO valide. Un I-TYPE apărut după O sau după alt tip nu pornește o entitate în această funcție, ci poate fi pierdut silențios. Validați etichetele înainte de conversie și stabiliți explicit dacă datele IOB2 invalide sunt respinse sau reparate.

Notă tehnică a traducerii: Aceste ajutoare nu verifică faptul că len(tokens) == len(labels), nici limitele, segmentele vide sau segmentele suprapuse. În spans_to_bio, un segment vid poate totuși produce o etichetă B-, iar segmentele suprapuse se suprascriu în funcție de ordine; indicii negativi urmează regulile Python. Validați separat intervalele semideschise 0 <= start < end <= len(tokens), absența suprapunerilor și corespondența dintre tokenuri și etichete înainte de conversie.

Pasul 2: caracteristici construite manual

Pentru NER clasic (non-neuronal), caracteristicile sunt esențiale. Cele utile sunt:

def token_features(token, prev_token, next_token):
    return {
        "lower": token.lower(),
        "is_upper": token.isupper(),
        "is_title": token.istitle(),
        "has_digit": any(c.isdigit() for c in token),
        "suffix_3": token[-3:].lower(),
        "shape": word_shape(token),
        "prev_lower": prev_token.lower() if prev_token else "<BOS>",
        "next_lower": next_token.lower() if next_token else "<EOS>",
    }


def word_shape(word):
    out = []
    for c in word:
        if c.isupper():
            out.append("X")
        elif c.islower():
            out.append("x")
        elif c.isdigit():
            out.append("d")
        else:
            out.append(c)
    return "".join(out)

word_shape("iPhone") întoarce xXxxxx. word_shape("USA-2024") întoarce XXX-dddd. Tiparele de capitalizare oferă un semnal puternic pentru numele proprii.

Pasul 3: o bază de referință simplă cu reguli și dicționar

ORG_GAZETTEER = {"Apple", "Google", "Microsoft", "OpenAI", "Meta", "Amazon", "Netflix"}
GPE_GAZETTEER = {"US", "USA", "UK", "India", "Germany", "France"}
PRODUCT_GAZETTEER = {"iPhone", "Android", "Windows", "ChatGPT", "Claude"}


def rule_based_ner(tokens):
    labels = []
    for token in tokens:
        if token in ORG_GAZETTEER:
            labels.append("B-ORG")
        elif token in GPE_GAZETTEER:
            labels.append("B-GPE")
        elif token in PRODUCT_GAZETTEER:
            labels.append("B-PRODUCT")
        else:
            labels.append("O")
    return labels

Gazetteerele de producție au milioane de intrări extrase din Wikipedia și DBpedia. Acoperirea este bună. Dezambiguizarea (Apple, compania, față de fruct) este foarte slabă. De aceea au câștigat modelele statistice.

Notă tehnică a traducerii: Dimensiunea gazetteerelor, sursele, licențele, normalizarea și acoperirea lor depind de produs. Potrivirea exactă nu rezolvă ambiguitatea și nici limitele segmentului; un sistem de producție are nevoie de proveniența listelor, de politici de actualizare și de evaluare pe domeniul țintă. Afirmația istorică despre „câștigul” modelelor statistice nu stabilește o alegere automată pentru fiecare ontologie stabilă.

Pasul 4: pasul CRF (schiță, nu implementare completă)

Un CRF complet de la zero în 50 de linii nu este instructiv fără fundamentele teoriei probabilităților. Folosiți în schimb sklearn-crfsuite:

import sklearn_crfsuite

def to_features(tokens):
    out = []
    for i, tok in enumerate(tokens):
        prev = tokens[i - 1] if i > 0 else ""
        nxt = tokens[i + 1] if i + 1 < len(tokens) else ""
        out.append({
            "word.lower()": tok.lower(),
            "word.isupper()": tok.isupper(),
            "word.istitle()": tok.istitle(),
            "word.isdigit()": tok.isdigit(),
            "word.suffix3": tok[-3:].lower(),
            "word.shape": word_shape(tok),
            "prev.word.lower()": prev.lower(),
            "next.word.lower()": nxt.lower(),
            "BOS": i == 0,
            "EOS": i == len(tokens) - 1,
        })
    return out


crf = sklearn_crfsuite.CRF(algorithm="lbfgs", c1=0.1, c2=0.1, max_iterations=100, all_possible_transitions=True)
X_train = [to_features(s) for s in sentences_tokenized]
crf.fit(X_train, bio_labels_train)

c1 și c2 sunt regularizări L1 și L2. all_possible_transitions=True permite modelului să învețe că secvențele ilegale (de exemplu, I-ORG după O) sunt puțin probabile, ceea ce îi permite unui CRF să păstreze consistența BIO fără să scrieți constrângerea.

Notă tehnică a traducerii: În sklearn-crfsuite, all_possible_transitions=True generează caracteristici pentru toate perechile de etichete, inclusiv tranziții care nu apar în antrenare; nu impune o constrângere BIO strictă. Un CRF obișnuit poate totuși prezice I-ORG după O. Pentru secvențe BIO valide, folosiți măști de tranziție sau un decodor constrâns și raportați o evaluare strictă IOB2, nu doar o pierdere bună. Consultați API-ul sklearn-crfsuite.

Notă tehnică a traducerii: sentences_tokenized și bio_labels_train sunt marcatori nedefiniți, nu un set de date gata de folosit. Construiți liste aliniate, cu același număr de propoziții și aceeași lungime token–etichetă pentru fiecare propoziție; apoi separați datele în antrenare, validare și test fără scurgere între documente. Schița nu definește nici evaluarea la nivel de entitate; măsurați separat pe validare/test cu segmentele de aur, nu pe datele de antrenare.

Pasul 5: ce adaugă un BiLSTM-CRF

Caracteristicile devin învățate. Intrările sunt reprezentări vectoriale ale tokenurilor (GloVe sau fastText). LSTM citește propoziția de la stânga la dreapta și de la dreapta la stânga. Stările ascunse concatenate trec printr-un strat de ieșire CRF. CRF-ul continuă să impună consistența secvenței de etichete; LSTM înlocuiește caracteristicile construite manual cu unele învățate.

import torch
import torch.nn as nn


class BiLSTM_CRF_Head(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, n_labels):
        super().__init__()
        self.embed = nn.Embedding(vocab_size, embed_dim)
        self.lstm = nn.LSTM(embed_dim, hidden_dim, bidirectional=True, batch_first=True)
        self.fc = nn.Linear(hidden_dim * 2, n_labels)

    def forward(self, token_ids):
        e = self.embed(token_ids)
        h, _ = self.lstm(e)
        emissions = self.fc(h)
        return emissions

Pentru stratul CRF, utilizați torchcrf.CRF (pip install pytorch-crf). Câștigul față de un CRF cu caracteristici construite manual este măsurabil, dar mai mic decât ați putea crede dacă nu aveți zeci de mii de propoziții etichetate.

Notă tehnică a traducerii: Clasa de mai sus produce numai scorurile de emisie; nu instanțiază un CRF, nu calculează log-verosimilitatea, nu aplică mască pentru padding și nu decodează Viterbi. Este un schelet al encoderului BiLSTM, nu un BiLSTM-CRF complet. pytorch-crf documentează separat antrenarea cu emisii și etichete, precum și decode; cantitatea de date necesară pentru un câștig depinde de sarcină, nu de un prag universal. Consultați documentația pytorch-crf.

Folosiți

spaCy oferă NER de nivel de producție direct din cutie.

import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple sued Google over its iPhone search deal in the US.")
for ent in doc.ents:
    print(f"{ent.text:20s} {ent.label_}")
Apple                ORG
Google               ORG
iPhone               ORG
US                   GPE

Observați că iPhone este etichetat ORG, nu PRODUCT: modelul mic spaCy are o acoperire slabă a entităților de produs. Modelul mare (en_core_web_lg) se descurcă mai bine. Modelul Transformer (en_core_web_trf) se descurcă și mai bine.

Notă tehnică a traducerii: Aceste rezultate sunt un instantaneu dependent de versiunea spaCy, pachetul de model, componentele active și textul de intrare; nu presupuneți că eticheta iPhone sau ordinea de performanță se reproduce. spaCy atribuie etichete statistice unor segmente contigue, iar clasele disponibile depind de modelul antrenat. Fixați versiunile și evaluați pe datele domeniului. Consultați documentația spaCy despre entități denumite.

Notă tehnică a traducerii: Instalarea bibliotecii spacy nu instalează automat modelul en_core_web_sm. Exemplul necesită instalarea separată a unui pachet de model compatibil cu versiunea spaCy, de exemplu prin python -m spacy download en_core_web_sm, și fixarea ambelor versiuni pentru reproducibilitate.

Hugging Face pentru NER bazat pe BERT:

from transformers import pipeline

ner = pipeline("ner", model="dslim/bert-base-NER", aggregation_strategy="simple")
print(ner("Apple sued Google over its iPhone in the US."))
[{'entity_group': 'ORG', 'word': 'Apple', ...},
 {'entity_group': 'ORG', 'word': 'Google', ...},
 {'entity_group': 'MISC', 'word': 'iPhone', ...},
 {'entity_group': 'LOC', 'word': 'US', ...}]

aggregation_strategy="simple" unește tokenurile B-X, I-X contigue într-un segment. Fără aceasta, primiți etichete la nivel de token și trebuie să le uniți personal.

Notă tehnică a traducerii: Strategia simple încearcă să grupeze entități conform schemei implicite, dar nu garantează limite sau tipuri corecte; tokenizarea, checkpointul, revizia și etichetele modelului rămân decisive. Inspectați start și end în textul original și testați cazurile cu subtokenuri. Consultați documentația Hugging Face pentru TokenClassificationPipeline.

NER bazat pe LLM (opțiunea din 2026)

NER zero-shot și few-shot cu LLM-uri este acum competitiv cu modelele ajustate fin pe multe domenii și mult mai bun când datele etichetate sunt rare.

  • Prompting zero-shot. Oferiți LLM-ului o listă de tipuri de entități și un exemplu de schemă. Cereți ieșire JSON. Funcționează direct; acuratețea este moderată pe domenii noi.
  • Prompting în stil ZeroTuneBio. Descompuneți sarcina în extragerea candidaților → explicarea sensului → judecată → reverificare. Un prompt în mai multe etape (nu one-shot) crește substanțial acuratețea în NER biomedical. Același tipar funcționează pentru domeniile juridic, financiar și științific.
  • Prompting dinamic cu RAG. Regăsiți cele mai similare exemple etichetate dintr-un set inițial mic și adnotat pentru fiecare apel de inferență; construiți promptul few-shot din mers. În benchmarkurile din 2026, aceasta crește F1 pentru NER biomedical cu GPT-4 cu 11–12% față de promptingul static.
  • Descompunere după tipul de entitate. Pentru documente lungi, un singur apel care extrage simultan toate tipurile de entități pierde recall pe măsură ce crește lungimea. Rulați o trecere de extragere pentru fiecare tip de entitate. Cost de inferență mai mare, acuratețe substanțial mai mare. Acesta este tiparul standard pentru note clinice și contracte juridice.

Recomandare de producție pentru 2026: începeți cu o bază de referință LLM zero-shot înainte de a colecta date de antrenare. Adesea F1 este suficient de bun încât să nu mai fie nevoie de ajustare fină.

Notă tehnică a traducerii: Afirmațiile despre „2026”, ZeroTuneBio, creșterea de 11–12% și tiparul „standard” nu au în lecție un benchmark identificabil, o versiune de model, set de date, schemă de etichete sau protocol de cost. Tratați-le ca ipoteze de evaluat, nu ca recomandări garantate. Comparați LLM-ul cu un reper ajustat fin sau clasic pe segmente exacte, F1 pe tip de entitate, rată de JSON invalid, latență, cost, confidențialitate și revizuire umană.

Unde NER clasic încă învinge

Chiar și când LLM-urile sunt disponibile, NER clasic învinge când:

  • Bugetul de latență este sub 50 ms.
  • Aveți mii de exemple etichetate și aveți nevoie de F1 de peste 98%.
  • Domeniul are o ontologie stabilă, unde un CRF sau BiLSTM preantrenat se transferă bine.
  • Constrângerile de reglementare cer un model local, non-generativ.

Notă tehnică a traducerii: Pragurile de 50 ms, mii de exemple și 98%+ F1 sunt euristici, nu limite tehnice. Ele depind de limbă, numărul și raritatea tipurilor, distribuția documentelor, hardware, model, batching, evaluare și cerințele de securitate. Măsurați întregul flux, inclusiv tokenizarea, validarea, extragerea structurată și revizuirea erorilor.

Unde se destramă

  • Schimbarea domeniului. NER antrenat pe CoNLL pentru contracte juridice are rezultate mai slabe decât un gazetteer. Ajustați fin pe domeniul propriu.
  • Entități imbricate. „Bank of America Tower” este simultan ORG și FACILITY. BIO standard nu poate reprezenta segmente suprapuse. Aveți nevoie de NER imbricat (mai multe treceri sau modele bazate pe segmente).
  • Entități lungi. „United States Federal Deposit Insurance Corporation.” Modelele la nivel de token le separă uneori. Folosiți aggregation_strategy sau postprocesare.
  • Tipuri rare. NER medical etichetează tipuri precum DRUG_BRAND, ADVERSE_EVENT, DOSE. Modelele de uz general nu le cunosc. Scispacy și BioBERT sunt punctele de plecare.

Notă tehnică a traducerii: Schimbarea domeniului poate degrada un model, dar nu demonstrează că va fi mereu mai slab decât orice gazetteer. BIO liniar nu exprimă segmente suprapuse, însă definiția tipurilor, schemele de adnotare și evaluarea NER imbricat trebuie stabilite înainte de a alege o abordare span-based sau cu mai multe treceri. Verificați și regulile de segmentare, deoarece postprocesarea nu poate repara în mod fiabil o etichetare fără informația necesară.

Livrați

Salvați ca outputs/skill-ner-picker.md:

---
name: ner-picker
description: Alege abordarea NER potrivită pentru o sarcină de extragere dată.
version: 1.0.0
phase: 5
lesson: 06
tags: [nlp, ner, extraction]
---

Pentru o descriere a sarcinii (domeniu, set de etichete, limbă, latență, volum de date), produceți:

1. Abordarea: bazată pe reguli + gazetteer, CRF, BiLSTM-CRF sau ajustare fină a unui Transformer.
2. Modelul de pornire. Denumiți-l (ID de model spaCy, ID de checkpoint Hugging Face sau „personalizat, antrenat de la zero”).
3. Strategia de etichetare: BIO, BILOU sau bazată pe segmente. Justificați într-o propoziție.
4. Evaluarea. Folosiți `seqeval`. Raportați întotdeauna F1 la nivel de entitate (nu la nivel de token).

Refuzați să recomandați ajustarea fină a unui Transformer pentru sub 500 de exemple etichetate, cu excepția cazului în care utilizatorul are deja un model de domeniu preantrenat. Semnalați entitățile imbricate ca necesitând o abordare bazată pe segmente sau cu mai multe treceri. Cereți un audit al gazetteerului dacă utilizatorul menționează „scară de producție”, iar etichetele rămân neschimbate față de CoNLL-2003.

Notă tehnică a traducerii: Regulile promptului privind 500 de exemple și refuzul ajustării fine sunt prea rigide fără detalii despre transfer, modelul de domeniu, limbă, buget și metrică. F1 la nivel de entitate este esențială pentru potrivirea exactă a segmentelor, dar raportați și F1 pe tip, suportul, schema de potrivire, erorile de frontieră și comportamentul pentru entități imbricate. seqeval nu înlocuiește validarea datelor și a schemei de etichete.

Notă tehnică a traducerii: În seqeval, rezultatele implicite și cele stricte pot trata diferit secvențele BIO invalide. Pentru o evaluare IOB2 reproductibilă la nivel de entitate, transmiteți explicit mode="strict" și scheme=IOB2 către metricile folosite (de exemplu, f1_score sau classification_report) și validați atât etichetele de referință, cât și predicțiile înainte de scorare.

Exerciții

  1. Ușor. Implementați bio_to_spans (inversa lui spans_to_bio) și verificați consistența dus-întors pe 10 propoziții.
  2. Mediu. Antrenați CRF-ul sklearn-crfsuite de mai sus pe setul de date englez CoNLL-2003 pentru NER. Raportați F1 pentru fiecare entitate folosind seqeval. Rezultat tipic: ~84 F1.
  3. Dificil. Ajustați fin distilbert-base-cased pe un set de date NER specific domeniului (medical, juridic sau financiar). Comparați cu modelul mic spaCy. Documentați verificările pentru scurgeri de date și descrieți ce v-a surprins.

Notă tehnică a traducerii: Rezultatul „~84 F1” nu este garantat: variază cu împărțirea CoNLL, preprocesarea, caracteristicile, hiperparametrii, versiunea bibliotecii și protocolul de scor. Păstrați separat seturile de antrenare, validare și test, fixați preprocesarea și raportați setul exact, seed-ul, definiția F1 și verificările pentru scurgeri înainte de comparație.

Termeni-cheie

Termen Ce spun oamenii Ce înseamnă de fapt
NER Extrage nume Etichetează segmente de tokenuri cu tipuri (PERSON, ORG, GPE, DATE, …).
BIO Schemă de etichetare B-X începe, I-X continuă, O este în afară.
BILOU BIO mai bun Adaugă L-X (ultimul), U-X (unitate) pentru limite mai clare.
CRF Clasificator structurat Modelează tranzițiile dintre etichete, nu numai emisiile. Impune secvențe valide.
NER imbricat Entități suprapuse Un segment este o entitate diferită de un subsegment. BIO nu poate exprima aceasta.
F1 la nivel de entitate Metrica NER potrivită Segmentul prezis trebuie să coincidă exact cu cel adevărat. F1 la nivel de token supraestimează acuratețea.

Lecturi suplimentare

Sursă: Originalul în limba engleză

Navigare: ← Lecția 05.05 — Analiza sentimentului · Faza 5 — NLP: de la fundamente la subiecte avansate · Lecția 05.07 — Etichetarea POS și analiza sintactică → · Catalog complet