Migliorare il riconoscimento dei nomi con GLiNER e FastAPI

DaTDaT
3 min read

πŸ“Œ Obiettivo: Estrarre nomi di persone da documenti lunghi riducendo i falsi positivi.
πŸ“Œ Tecnologia usata: GLiNER, FastAPI, spaCy, Python


πŸš€ Problema iniziale: Troppi falsi positivi

Abbiamo testato il modello GLiNER (DeepMount00/GLiNER_ITA_LARGE) per riconoscere i nomi di persone in documenti lunghi.

πŸ’‘ Primo test: Il modello ha estratto 700+ falsi positivi da un documento con molte ripetizioni di parole comuni.
❌ Errori comuni rilevati:

  • "Lavoratore", "telelavoratore", "colleghi diretti" β†’ Riconosciuti erroneamente come persone.

  • Frasi spezzate con perdita di contesto β†’ Generazione di entitΓ  inesatte.


πŸ” Soluzione 1: Segmentazione ottimizzata

πŸ›‘ Problema: Il modello ha un limite di 384 token per input.
βœ… Soluzione: Abbiamo suddiviso il testo in paragrafi invece che in frasi singole, preservando il contesto.

Tecnologia usata:

import re
import spacy
nlp = spacy.load("it_core_news_sm")

def split_text_by_paragraphs(text: str, max_tokens: int = 384):
    paragraphs = re.split(r'\n\s*\n+', text)
    segments = []
    start_index = 0  

    for para in paragraphs:
        doc = nlp(para)
        tokens = [token.text for token in doc]
        token_count = len(tokens)

        if token_count > max_tokens:
            sentences = list(doc.sents)
            current_segment = []
            current_token_count = 0
            for sent in sentences:
                sent_tokens = [token.text for token in sent]
                if current_token_count + len(sent_tokens) > max_tokens:
                    segment_text = " ".join(current_segment)
                    end_index = start_index + len(segment_text)
                    segments.append({"text": segment_text, "start": start_index, "end": end_index})
                    current_segment = []
                    current_token_count = 0
                    start_index = end_index + 1

                current_segment.extend(sent_tokens)
                current_token_count += len(sent_tokens)

            if current_segment:
                segment_text = " ".join(current_segment)
                end_index = start_index + len(segment_text)
                segments.append({"text": segment_text, "start": start_index, "end": end_index})
                start_index = end_index + 1
        else:
            end_index = start_index + len(para)
            segments.append({"text": para, "start": start_index, "end": end_index})
            start_index = end_index + 1

    return segments

πŸ“Œ Risultato: Diminuzione drastica dei falsi positivi, ma ancora qualche errore.


πŸ” Soluzione 2: Validazione con il cognome

πŸ“Œ Problema: Il modello riconosce ancora parole non valide come "persona".
πŸ“Œ Soluzione: Abbiamo aggiunto una verifica sul cognome:

  • Se un'entitΓ  riconosciuta come "persona" non ha un cognome valido, viene scartata.

πŸ“Œ Codice ottimizzato nella classe NamedEntityRecognizer

def predict_people(self, text: str):
    predictions = self.model.predict_entities(
        text, ["persona"], flat_ner=self.flat_ner, threshold=0.5, multi_label=self.multi_label
    )

    if not predictions:
        return predictions  

    return self.validate_person_with_cognome(predictions)

def validate_person_with_cognome(self, predictions: List[dict]) -> List[dict]:
    valid_people = []
    for entity in predictions:
        name = entity["text"]

        cognome_predictions = self.model.predict_entities(
            name, ["cognome"], flat_ner=self.flat_ner, threshold=0.5, multi_label=self.multi_label
        )

        if cognome_predictions:
            valid_people.append(entity)

    return valid_people

πŸ“Œ Risultato: I falsi positivi sono scesi da 700 a 24, ma rimanevano ancora 2 errori.


πŸ” Soluzione 3: Doppia validazione con nome e cognome

πŸ“Œ Problema: Alcuni termini con cognomi generici venivano riconosciuti erroneamente.
πŸ“Œ Soluzione: Aggiunto un secondo controllo per verificare che l'entitΓ  abbia sia un "nome" che un "cognome".

def validate_person_with_nome_cognome(self, predictions: List[dict]) -> List[dict]:
    valid_people = []
    for entity in predictions:
        name = entity["text"]

        cognome_predictions = self.model.predict_entities(
            name, ["cognome"], flat_ner=self.flat_ner, threshold=0.5, multi_label=self.multi_label
        )

        nome_predictions = self.model.predict_entities(
            name, ["nome"], flat_ner=self.flat_ner, threshold=0.5, multi_label=self.multi_label
        )

        if cognome_predictions and nome_predictions:
            valid_people.append(entity)

    return valid_people

πŸ“Œ Risultato: Abbiamo eliminato i 2 falsi positivi residui. 🎯


πŸš€ Risultato finale

Dopo tutte le ottimizzazioni, il modello Γ¨ passato da:
❌ 700 falsi positivi ➝ βœ… 24 risultati validi (solo 2 errori) ➝ βœ… 0 errori dopo il filtro finale


πŸ“Œ Moduli richiesti

Per replicare l'intero processo, sono necessari i seguenti moduli Python:

pip install gliner fastapi spacy uvicorn
python -m spacy download it_core_news_sm

βœ… gliner β†’ Modello NER per l'estrazione dei nomi.
βœ… fastapi β†’ API per eseguire il modello su rete locale.
βœ… uvicorn β†’ Server per FastAPI.
βœ… spacy + it_core_news_sm β†’ Segmentazione e pre-processing del testo.


πŸ” Conclusione

Grazie a queste ottimizzazioni, abbiamo migliorato drasticamente l'accuratezza del riconoscimento di persone in documenti lunghi, eliminando quasi tutti i falsi positivi.

πŸ“Œ Se hai un problema simile, prova queste strategie e gioca con il valore threshold per trovare il bilanciamento perfetto! πŸš€

0
Subscribe to my newsletter

Read articles from DaT directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

DaT
DaT