
GliNER2 : extraire des informations structurées à partir d’un texte
nous avions SpaCy, qui était la bibliothèque PNL de facto pour les utilisateurs débutants et avancés. Cela vous a permis de vous lancer facilement dans la PNL, même si vous n’êtes pas un expert en apprentissage profond. Cependant, avec la montée en puissance de ChatGPT et d’autres LLM, il semble avoir été mis de côté.
Bien que les LLM comme Claude ou Gemini puissent faire toutes sortes de choses en PNL de manière automatique, vous ne voulez pas toujours utiliser un lance-roquettes dans un combat au poing. GLINER est à l’origine du retour de modèles plus petits et ciblés pour les techniques classiques de PNL telles que l’extraction d’entités et de relations. Il est suffisamment léger pour fonctionner sur un processeur, mais suffisamment puissant pour construire une communauté florissante autour de lui.
Sorti plus tôt cette année, GliNER2 constitue un grand pas en avant. Où l’original GLINER axé sur la reconnaissance des entités (générant diverses retombées comme GLiREL pour les relations et GLiClass pour la classification), GliNER2 unifie reconnaissance d’entité nommée, classement du texte, extraction de relationset extraction de données structurées dans un cadre unique.
Le principal changement dans GliNER2 est son approche basée sur un schémaqui vous permet de définir les exigences d’extraction de manière déclarative et d’exécuter plusieurs tâches en un seul appel d’inférence. Malgré ces capacités étendues, le modèle reste économe en CPU, ce qui en fait une solution idéale pour transformer du texte désordonné et non structuré en données claires sans la surcharge d’un modèle de langage volumineux.
En tant que passionné de graphes de connaissances chez Neo4j, j’ai été particulièrement attiré par la nouvelle extraction de données structurées via extract_json méthode. Bien que l’extraction d’entités et de relations soit précieuse en soi, la possibilité de définir un schéma et d’extraire du JSON structuré directement à partir du texte est ce qui m’enthousiasme vraiment. C’est un choix naturel pour l’ingestion de graphes de connaissances, où sortie structurée et cohérente est essentiel.

Dans cet article de blog, nous évaluerons les capacités de GliNER2, en particulier le modèle fastino/gliner2-large-v1en mettant l’accent sur la manière dont cela peut nous aider à créer des graphiques de connaissances propres et structurés.
Le code est disponible sur GitHub.
Sélection de l’ensemble de données
Nous n’exécutons pas de benchmarks formels ici, juste une vérification rapide de l’ambiance pour voir ce que GliNER2 peut faire. Voici notre texte de test, extrait du Page Wikipédia d’Ada Lovelace:
Augusta Ada King, comtesse de Lovelace (10 décembre 1815-27 novembre 1852), également connue sous le nom d’Ada Lovelace, était une mathématicienne et écrivaine anglaise principalement connue pour ses travaux sur l’ordinateur mécanique à usage général proposé par Charles Babbage, le moteur analytique. Elle fut la première à reconnaître que la machine avait des applications au-delà du simple calcul. Lovelace est souvent considéré comme le premier programmeur informatique. Lovelace était le seul enfant légitime du poète Lord Byron et de la réformatrice Anne Isabella Milbanke. Tous ses demi-frères et sœurs, les autres enfants de Lord Byron, sont nés hors mariage d’autres femmes. Lord Byron s’est séparé de sa femme un mois après la naissance d’Ada et a quitté l’Angleterre pour toujours. Il mourut en Grèce pendant la guerre d’indépendance grecque, alors qu’elle avait huit ans. Lady Byron s’inquiétait de l’éducation de sa fille et encourageait l’intérêt de Lovelace pour les mathématiques et la logique, afin de l’empêcher de développer la folie perçue par son père. Malgré cela, Lovelace est restée intéressée par son père, nommant un fils Byron et l’autre, pour le deuxième prénom de son père, Gordon. Lovelace a été enterrée à côté de son père à sa demande. Bien que souvent malade dans son enfance, Lovelace poursuit assidûment ses études. Elle épousa William King en 1835. King était baron et fut créé vicomte Ockham et 1er comte de Lovelace en 1838. Le nom Lovelace a été choisi parce qu’Ada descendait du baron Lovelaces, aujourd’hui disparu. Le titre donné à son mari faisait ainsi d’Ada la comtesse de Lovelace.
Avec 322 jetons, c’est un morceau de texte solide avec lequel travailler. Allons-y.
Extraction d’entité
Commençons par l’extraction d’entités. À la base, l’extraction d’entités est le processus de identifier et catégoriser automatiquement les entités clés dans le textetel que personnes, emplacements, organisationsou notions techniques. GliNER1 a déjà bien géré cela, mais GliNER2 va plus loin en vous permettant d’ajouter des descriptions aux types d’entités, vous donnant ainsi un contrôle plus précis sur ce qui est extrait.
entities = extractor.extract_entities(
text,
{
"Person": "Names of people, including nobility titles.",
"Location": "Countries, cities, or geographic places.",
"Invention": "Machines, devices, or technological creations.",
"Event": "Historical events, wars, or conflicts."
}
)
Les résultats sont les suivants :

Fournir des descriptions personnalisées pour chaque type d’entité permet de résoudre les ambiguïtés et d’améliorer la précision de l’extraction. Ceci est particulièrement utile pour les catégories larges comme Événement, où À lui seul, le modèle pourrait ne pas savoir s’il doit inclure les guerres, les cérémonies ou les événements personnels. Ajout événements historiques, guerres ou conflits clarifie la portée prévue.
Extraction de relations
L’extraction de relations identifie les relations entre des paires d’entités dans le texte. Par exemple, dans la phrase « Steve Jobs a fondé Apple »un modèle d’extraction de relation identifierait la relation Fondé entre les entités Steve Emplois et Pomme.
Avec GLiNER2, vous définissez uniquement les types de relations que vous souhaitez extraire car vous ne pouvez pas limiter les types d’entités autorisés comme tête ou queue de chaque relation. Cela simplifie l’interface mais peut nécessiter un post-traitement pour filtrer les appariements indésirables.
Ici, j’ai ajouté une expérience simple en ajoutant à la fois les définitions de relation alias et identique_as.
relations = extractor.extract_relations(
text,
{
"parent_of": "A person is the parent of another person",
"married_to": "A person is married to another person",
"worked_on": "A person contributed to or worked on an invention",
"invented": "A person created or proposed an invention",
"alias": "Entity is an alias, nickname, title, or alternate reference for another entity",
"same_as": "Entity is an alias, nickname, title, or alternate reference for another entity"
}
)
Les résultats sont les suivants :

L’extraction a correctement identifié les relations clés : Lord Byron et Anne Isabella Milbanke en tant que parents d’Ada, son mariage avec William King, Babbage en tant qu’inventeur du moteur analytique et le travail d’Ada sur celui-ci. Notamment, le modèle a détecté Augusta Ada Roi comme un alias de Ada Lovelace, mais pareil_que n’a pas été capturé malgré une description identique. La sélection ne semble pas aléatoire car le modèle remplit toujours l’alias mais jamais la relation identique à celle-ci. Cela met en évidence à quel point l’extraction de relations est sensible à la dénomination des étiquettes, et pas seulement aux descriptions.
Idéalement, GLiNER2 permet combinant plusieurs types d’extraction en un seul appel afin que vous puissiez obtenir des types d’entités ainsi que des types de relations en un seul passage. Cependant, les opérations sont indépendantes : l’extraction d’entités ne filtre ni ne limite les entités qui apparaissent dans l’extraction de relations, et vice versa. Considérez-le comme exécutant les deux extractions en parallèle plutôt que comme un pipeline.
schema = (extractor.create_schema()
.entities({
"Person": "Names of people, including nobility titles.",
"Location": "Countries, cities, or geographic places.",
"Invention": "Machines, devices, or technological creations.",
"Event": "Historical events, wars, or conflicts."
})
.relations({
"parent_of": "A person is the parent of another person",
"married_to": "A person is married to another person",
"worked_on": "A person contributed to or worked on an invention",
"invented": "A person created or proposed an invention",
"alias": "Entity is an alias, nickname, title, or alternate reference for another entity"
})
)
results = extractor.extract(text, schema)
Les résultats sont les suivants :

L’extraction combinée nous donne désormais des types d’entités, qui se distinguent par leur couleur. Cependant, plusieurs nœuds apparaissent isolés (Grèce, Angleterre, Guerre d’Indépendance grecque) puisque toutes les entités extraites ne participent pas à une relation détectée.
Extraction JSON structurée
La fonctionnalité la plus puissante est peut-être l’extraction de données structurées via extract_json. Cela imite la fonctionnalité de sortie structurée des LLM comme ChatGPT ou Gemini mais fonctionne entièrement sur le processeur. Contrairement à l’extraction d’entités et de relations, cela vous permet de définir des champs arbitraires et de les intégrer dans des enregistrements structurés. La syntaxe suit un field_name::type::description motif, où le type est str ou list.
results = extractor.extract_json(
text,
{
"person": [
"name::str",
"gender::str::male or female",
"alias::str::brief summary of included information about the person",
"description::str",
"birth_date::str",
"death_date::str",
"parent_of::str",
"married_to::str"
]
}
)
Ici, nous expérimentons un certain chevauchement : alias, parent_ofet married_to pourrait également être modélisé sous forme de relations. Cela vaut la peine d’explorer quelle approche fonctionne le mieux pour votre cas d’utilisation. Un ajout intéressant est le description field, qui repousse un peu les limites : c’est plus proche de la génération récapitulative que de l’extraction pure.
Les résultats sont les suivants :
{
"person": [
{
"name": "Augusta Ada King",
"gender": null,
"alias": "Ada Lovelace",
"description": "English mathematician and writer",
"birth_date": "10 December 1815",
"death_date": "27 November 1852",
"parent_of": "Ada Lovelace",
"married_to": "William King"
},
{
"name": "Charles Babbage",
"gender": null,
"alias": null,
"description": null,
"birth_date": null,
"death_date": null,
"parent_of": "Ada Lovelace",
"married_to": null
},
{
"name": "Lord Byron",
"gender": null,
"alias": null,
"description": "reformer",
"birth_date": null,
"death_date": null,
"parent_of": "Ada Lovelace",
"married_to": null
},
{
"name": "Anne Isabella Milbanke",
"gender": null,
"alias": null,
"description": "reformer",
"birth_date": null,
"death_date": null,
"parent_of": "Ada Lovelace",
"married_to": null
},
{
"name": "William King",
"gender": null,
"alias": null,
"description": null,
"birth_date": null,
"death_date": null,
"parent_of": "Ada Lovelace",
"married_to": null
}
]
}
Les résultats révèlent certaines limites. Tous gender les champs sont nuls, même si Ada est explicitement appelé un fillele modèle ne déduit pas qu’elle est une femme. Le description Le champ capture uniquement des expressions superficielles (« mathématicien et écrivain anglais », « réformateur ») plutôt que de générer des résumés significatifs, ce qui n’est pas utile pour les flux de travail comme GraphRAG de Microsoft qui reposent sur des descriptions d’entités plus riches. Il y a aussi des erreurs évidentes : Charles Babbage et William King sont marqués à tort comme parent_of Ada, et Lord Byron est qualifié de réformateur (c’est Anne Isabelle). Ces erreurs avec parent_ofn’est pas apparu lors de l’extraction de la relation, c’est peut-être la meilleure méthode ici. Dans l’ensemble, les résultats suggèrent que le modèle excelle dans l’extraction mais a des difficultés avec le raisonnement ou l’inférence, probablement en raison de sa taille compacte.
De plus, tous les attributs sont facultatifs, ce qui est logique et simplifie les choses. Cependant, vous devez être prudent car parfois l’attribut name sera nul, rendant ainsi l’enregistrement invalide. Enfin, nous pourrions utiliser quelque chose comme PyDantic pour valider les résultats et les convertir en types appropriés comme des flottants ou des dates et gérer les résultats non valides.
Construire des graphiques de connaissances
Étant donné que GLiNER2 permet plusieurs types d’extraction en un seul passage, nous pouvons combiner toutes les méthodes ci-dessus pour construire un graphe de connaissances. Plutôt que d’exécuter des pipelines distincts pour l’extraction des entités, des relations et des données structurées, une seule définition de schéma gère les trois. Cela permet de passer facilement d’un texte brut à une représentation riche et interconnectée.
schema = (extractor.create_schema()
.entities({
"Person": "Names of people, including nobility titles.",
"Location": "Countries, cities, or geographic places.",
"Invention": "Machines, devices, or technological creations.",
"Event": "Historical events, wars, or conflicts."
})
.relations({
"parent_of": "A person is the parent of another person",
"married_to": "A person is married to another person",
"worked_on": "A person contributed to or worked on an invention",
"invented": "A person created or proposed an invention",
})
.structure("person")
.field("name", dtype="str")
.field("alias", dtype="str")
.field("description", dtype="str")
.field("birth_date", dtype="str")
)
results = extractor.extract(text, schema)
La manière dont vous mappez ces sorties à votre graphique (nœuds, relations, propriétés) dépend de votre modèle de données. Dans cet exemple, nous utilisons le modèle de données suivant :

Ce que vous pouvez remarquer, c’est que nous incluons également le morceau de texte original dans le graphique, ce qui nous permet de récupérer et de référencer le matériel source lors de l’interrogation du graphique, permettant ainsi des résultats plus précis et traçables. Le chiffre d’importation ressemble à ceci :
import_cypher_query = """
// Create Chunk node from text
CREATE (c:Chunk {text: $text})
// Create Person nodes with properties
WITH c
CALL (c) {
UNWIND $data.person AS p
WITH p
WHERE p.name IS NOT NULL
MERGE (n:__Entity__ {name: p.name})
SET n.description = p.description,
n.birth_date = p.birth_date
MERGE (c)-[:MENTIONS]->(n)
WITH p, n WHERE p.alias IS NOT NULL
MERGE (m:__Entity__ {name: p.alias})
MERGE (n)-[:ALIAS_OF]->(m)
}
// Create entity nodes dynamically with __Entity__ base label + dynamic label
CALL (c) {
UNWIND keys($data.entities) AS label
UNWIND $data.entities[label] AS entityName
MERGE (n:__Entity__ {name: entityName})
SET n:$(label)
MERGE (c)-[:MENTIONS]->(n)
}
// Create relationships dynamically
CALL (c) {
UNWIND keys($data.relation_extraction) AS relType
UNWIND $data.relation_extraction[relType] AS rel
MATCH (a:__Entity__ {name: rel[0]})
MATCH (b:__Entity__ {name: rel[1]})
MERGE (a)-[:$(toUpper(relType))]->(b)
}
RETURN distinct 'import completed' AS result
"""
La requête Cypher prend les résultats de la sortie GliNER2 et les stocke dans Neo4j. Nous pourrions également inclure des intégrations pour les morceaux de texte, les entités, etc.
Résumé
GliNER2 est un pas dans la bonne direction pour l’extraction de données structurées. Avec l’essor des LLM, il est facile d’accéder à ChatGPT ou à Claude chaque fois que vous avez besoin d’extraire des informations d’un texte, mais c’est souvent excessif. Exécuter un modèle de plusieurs milliards de paramètres pour extraire quelques entités et relations semble inutile lorsque des outils plus petits et spécialisés peuvent faire le travail sur un processeur.
GliNER2 unifie la reconnaissance d’entités nommées, l’extraction de relations et la sortie JSON structurée dans un cadre unique. Il est bien adapté aux tâches telles que la construction de graphes de connaissances, où vous avez besoin d’une extraction cohérente et basée sur un schéma plutôt que d’une génération ouverte.
Certes, le modèle a ses limites. Cela fonctionne mieux pour l’extraction directe plutôt que pour l’inférence ou le raisonnement, et les résultats peuvent être incohérents. Mais les progrès depuis le GliNER1 original vers le GliNER2 sont encourageants et nous espérons voir un développement continu dans ce domaine. Pour de nombreux cas d’utilisation, un modèle d’extraction ciblée surpasse un LLM qui fait bien plus que ce dont vous avez besoin.
Le code est disponible sur GitHub.



