Outil destiné à être utilisé en production :
MAIS pas de choix de la méthode ou de l'algorithme utilisé
spaCy est un des frameworks de TAL disponibles
fr
appris sur les corpus Sequoia et WikiNertrf
qui est issu de camembert-base distribué par Hugging Face, entraîné sur Oscarimport spacy
nlp = spacy.load(' ')
doc = nlp("Et tu recherches dans le vague une ombre, un sourire qui soulage.")
for tok in doc:
print(tok)
Et tu recherches dans le vague une ombre , un sourire qui soulage .
La tokénisation est non destructive. On peut découper un texte en tokens et le restituer dans sa forme originale
doc = nlp("""Le dictionnaire de mes souvenirs
N'a qu'une page, une seule rubrique
Qui commence par ton absence
Et dans l'ordre alphabétique
Se termine par l'état d'amnésie
Tes états d'âme sont un leurre
Et tes larmes sont les armes dont tu te sers
Mais ce piège ne tromperait qu'un amateur
Ton âme sœur est une meilleure adversaire""")
for tok in doc:
print(tok.text_with_ws, end="")
Le dictionnaire de mes souvenirs N'a qu'une page, une seule rubrique Qui commence par ton absence Et dans l'ordre alphabétique Se termine par l'état d'amnésie Tes états d'âme sont un leurre Et tes larmes sont les armes dont tu te sers Mais ce piège ne tromperait qu'un amateur Ton âme sœur est une meilleure adversaire
Un objet de la classe Doc
contient aussi le produit du découpage en phrases
doc = nlp("C’était pas l'année dernière. C'était pas à Marienbad. \
Comment voulez-vous que je m'en rappelle, à force de l'attendre, \
je ne savais plus qui l'attendait. Le temps est un traître de cape et d'épée \
qui vous glisse sa poudre d'oubli dans votre coca. Faudrait pouvoir choisir son film. \
J'n'avais plus qu'à me barricader dans la p'tite maison près du lac \
avec le canoë rose, à deux places qui flotterait, comme ça pour personne")
for sent in doc.sents:
print(sent, "\n")
C’était pas l'année dernière. C'était pas à Marienbad. Comment voulez-vous que je m'en rappelle, à force de l'attendre, je ne savais plus qui l'attendait. Le temps est un traître de cape et d'épée qui vous glisse sa poudre d'oubli dans votre coca. Faudrait pouvoir choisir son film. J'n'avais plus qu'à me barricader dans la p'tite maison près du lac avec le canoë rose, à deux places qui flotterait, comme ça pour personne
doc = nlp("Tous mes beaux châteaux d'Équateur s'écroulent.")
for tok in doc:
print(tok, tok.pos_)
Tous ADJ mes DET beaux ADJ châteaux NOUN d' ADP Équateur PROPN s' PRON écroulent VERB . PUNCT
Les annotations portant sur les tokens sont accessibles via les attributs des objets de type token
: https://spacy.io/api/token#attributes
pos_
contient l'étiquette de partie du discours de universal dependanciestag_
contient l'étiquette du corpus original, parfois plus détailléelemma_
pour le lemmemorph
pour l'analyse morphologiquefor token in doc:
print(token, token.lemma_, token.pos_, token.morph)
Tous tout ADJ Gender=Masc|Number=Plur mes mon DET Number=Plur|Poss=Yes beaux beal ADJ Gender=Masc|Number=Plur châteaux château NOUN Gender=Masc|Number=Plur d' de ADP Équateur Équateur PROPN s' se PRON Person=3|Reflex=Yes écroulent écrouler VERB Mood=Ind|Number=Plur|Person=3|Tense=Pres|VerbForm=Fin . . PUNCT
doc = nlp("Le bandit s'appelle Mister Kali Jones \
avec l'ami Bill Ballantine, \
sauvé de justesse des crocodiles, \
stop au trafic des Caraïbes.")
for ent in doc.ents:
print(ent, ent.label_)
Mister Kali Jones PER Bill Ballantine PER Caraïbes LOC
from spacy import displacy
displacy.render(doc, style="ent", jupyter=True)
doc = nlp("À l’heure où le président russe, Vladimir Poutine, prononçait sur la place Rouge son discours annuel \
sur la guerre de 1941-1945, cette année presque entièrement consacrée au conflit en Ukraine, \
son homologue ukrainien, Volodymyr Zelensky, diffusait une vidéo de lui-même marchant seul \
sur l'avenue Khrechtchatyk de Kiev, là où ont lieu, d’habitude, les cérémonies nationales dans son pays.")
displacy.render(doc, style="ent", jupyter=True)
Dans l'analyse en dépendance produite par Spacy, chaque mot d'une phrase a un gouverneur unique (head), la relation de dépendance entre le mot et son gouverneur est typée (nsubj, obj, …).
Pour la tête de la phrase on utilise la relation ROOT.
La structure produite par l'analyse syntaxique est un arbre, un graphe acyclique et connexe.
Les tokens sont les nœuds, les arcs sont les dépendances, le type de la relation est l'étiquette de l'arc.
doc = nlp("Il te refile en stéréo la chanson des sirènes.")
for token in doc:
print(token, token.dep_, token.head)
Il expl:subj refile te iobj refile refile ROOT refile en case stéréo stéréo obl:arg refile la det chanson chanson obj refile des case sirènes sirènes nmod chanson . punct refile
from spacy import displacy
doc = nlp("Il te refile en stéréo la chanson des sirènes.")
displacy.render(doc, style="dep", jupyter=True, options={'distance':110})
import explacy
# https://spacy.io/universe/project/explacy
explacy.print_parse_info(nlp, "Il te refile en stéréo la chanson des sirènes.")
Dep tree Token Dep type Lemma Part of Sp ────────── ─────── ───────── ─────── ────────── ┌──► Il expl:subj il PRON │┌─► te iobj te PRON ┌┬──┬─┴┴── refile ROOT refile VERB ││ │ ┌─► en case en ADP ││ └─►└── stéréo obl:arg stéréo NOUN ││ ┌─► la det le DET │└─►┌──┴── chanson obj chanson NOUN │ │ ┌─► des case de ADP │ └─►└── sirènes nmod sirène NOUN └────────► . punct . PUNCT
Les attributs de token suivants peuvent être utilisés pour parcourir l'arbre de dépendance :
children
les tokens dépendants du tokensubtree
tous les descendants du tokenancestors
tous les parents du tokenrights
les enfants à droite du tokenlefts
les enfants à gauche du tokenroot = [token for token in doc if token.head == token][0]
subjects = [tok for tok in root.lefts if "subj" in tok.dep_]
subject = subjects[0]
objs = [tok for tok in root.rights if tok.dep_ == "obj"]
obj = objs[0]
print(f"sujet : {subject}, prédicat : {root}, objet : {obj}")
sujet : Il, prédicat : refile, objet : chanson
Spacy a une classe Matcher
qui permet de repérer des tokens ou des séquences de tokens à l'aide de patrons (pattern).
Ces patrons peuvent porter sur la forme des tokens ou leurs attributs (pos, ent, …).
On peut aussi utiliser des catégories comme IS_ALPHA
ou IS_NUM
, voir la doc
(Il existe une démo avec interface graphique mais pas pour le français 🙁)
from spacy.matcher import Matcher
doc = nlp("Ce modèle est aussi disponible en taille XL ; je vous le conseille.")
matcher = Matcher(nlp.vocab)
pattern = [{"LOWER": "en"}, {"LOWER": "taille"}, {"IS_ALPHA": True, "IS_UPPER": True}]
# 'en' 'taille' + lettres en maj
matcher.add("tailles", [pattern])
matches = matcher(doc)
for _, start, end in matches:
span = doc[start:end] # The matched span
print(f"{span.text} ({start}, {end})")
en taille XL (5, 8)
Ça fonctionne pour les séquences comme « en taille M » ou « en taille XL » mais pas pour « vous l'avez en XL ? »
doc = nlp("vous l'avez en XL ?")
matches = matcher(doc)
for _, start, end in matches:
span = doc[start:end] # The matched span
print(f"{span.text} ({start}, {end})")
On peut essayer d'améliorer les règles :
matcher = Matcher(nlp.vocab)
pattern_1 = [{"LOWER": "en"}, {"LOWER": "taille"}, {"IS_ALPHA": True, "IS_UPPER": True}]
pattern_2 = [{"LOWER": "en"}, {"IS_ALPHA": True, "IS_UPPER": True}]
matcher.add("tailles", [pattern_1, pattern_2])
# règle avec deux patterns
doc = nlp("vous l'avez en XL ?")
matches = matcher(doc)
for _, start, end in matches:
span = doc[start:end] # The matched span
print(f"{span.text} ({start}, {end})")
en XL (3, 5)
Ou encore :
matcher = Matcher(nlp.vocab)
sizes = ['XS', 'S', 'M', 'L', 'XL']
pattern_1 = [{"LOWER": "en"}, {"LOWER": "taille"}, {"TEXT": {"IN": sizes}}]
pattern_2 = [{"LOWER": "en"}, {"TEXT": {"IN": sizes}}]
matcher.add("tailles", [pattern_1, pattern_2])
# règle avec deux patterns
doc = nlp("vous l'avez en XL ?")
matches = matcher(doc)
for _, start, end in matches:
span = doc[start:end] # The matched span
print(f"{span.text} ({start}, {end})")
en XL (3, 5)
histoire_da = """Valérie s'ennuyait
Dans les bras de Nicolas
Mais Nicolas, celui-là
Ne le savait pas
Isabelle a attendu, attendu
Mais Patrick n'est jamais reparu
Les histoires d'A
Les histoires d'amour
Les histoires d'amour finissent mal
Les histoires d'amour finissent mal en général
Michel aimait Gérard
Et Gérard le lui rendait si bien
Qu'à la fin ça ne rendait rien
Evelyne toute sa vie attendit
Que le monsieur en gris lui sourit
Gilbert partit en voyage
Juste au moment de son mariage
Hector est mort en faisant une fugue
Il allait retrouver Gertrude
Simone et Tom s'engueulaient
Dès que vingt et une heures sonnaient
Les histoires d'amour finissent mal en général
Les histoires d'amour finissent mal en général
Les histoires d'amour finissent mal en général """
doc = nlp(histoire_da)
displacy.render(doc, style="ent", jupyter=True)
prenoms = ["Valérie", "Nicolas", "Isabelle", "Patrick", "Michel", "Gérard",\
"Evelyne", "Gilbert", "Hector", "Gertrude", "Simone", "Tom"]
patterns = [{"label": "PER", "pattern":[{"TEXT": {"IN": prenoms}}]}]
ruler = nlp.add_pipe("entity_ruler", config={'overwrite_ents':True})
ruler.add_patterns(patterns)
doc = nlp(histoire_da)
displacy.render(doc, style="ent", jupyter=True)
Depuis la v3, Spacy a ajouté un Dependancy Matcher qui permet de faire de l'extraction de patrons syntaxiques
Il est maintenant possible de faire porter des requêtes sur l'arbre syntaxique et non plus seulement sur la séquence des tokens.
Ce dispositif utilise Semgrex, la syntaxe utilisée dans Tgrep et Tregex, les outils de requête sur Treebank de Stanford.
Voir la documentation
ventre_short = ""
with open('Le_Ventre_de_Paris-short.txt') as input_f:
ventre_short = input_f.read()
doc = nlp(ventre_short)
from spacy.matcher import DependencyMatcher
matcher = DependencyMatcher(nlp.vocab)
pattern = [
{
"RIGHT_ID": "vendre",
"RIGHT_ATTRS": {"LEMMA": "vendre"}
}
]
matcher.add("VENDRE", [pattern])
matches = matcher(doc)
for m_id, t_ids in matches:
for t_id in t_ids:
print(doc[t_id])
vend vendant vendait vendait vendaient vendaient vend vendu vendu vendre vendait vendu vendais vendu vendrait
from spacy.matcher import DependencyMatcher
matcher = DependencyMatcher(nlp.vocab)
pattern = [
{
"RIGHT_ID": "vendre",
"RIGHT_ATTRS": {"LEMMA": {"IN": ["vendre", "acheter"]}}
},
{
"LEFT_ID": "vendre",
"REL_OP": ">",
"RIGHT_ID": "sujet",
"RIGHT_ATTRS": {"DEP": "nsubj"},
},
{
"LEFT_ID": "vendre",
"REL_OP": ">",
"RIGHT_ID": "objet",
"RIGHT_ATTRS": {"DEP": {"IN": ["obj", "iobj", "obl"]}},
}
]
# {lemma:/vendre|acheter/} > {dep:nsubj} : {lemma:vendre|acheter} > {dep:/obj|iobj|obl/}
matcher.add("VENDRE", [pattern])
matches = matcher(doc)
for m_id, t_ids in matches:
print("verbe, sujet, objet : ", " -> ".join([doc[t_id].text for t_id in t_ids]))
print("objet complet : ", " ".join([t.text for t in doc[t_ids[2]].subtree]))
print("Phrase compléte : ", doc[t_ids[0]].sent)
print()
verbe, sujet, objet : acheta -> il -> derniers objet complet : ses deux derniers sous de pain Phrase compléte : Mais, à Vernon, il acheta ses deux derniers sous de pain. verbe, sujet, objet : achetait -> elle -> navets objet complet : ses navets à mon père Phrase compléte : J’étais gamine, qu’elle achetait déjà ses navets à mon père. verbe, sujet, objet : vendait -> on -> fruits objet complet : des fruits Phrase compléte : Derrière lui, sur le carreau de la rue Rambuteau, on vendait des fruits. verbe, sujet, objet : vendaient -> qui -> bottes objet complet : des bottes de fougère et des paquets de feuilles de vigne , bien réguliers , attachés par quarterons Phrase compléte : Ils s’arrêtèrent curieusement devant des femmes qui vendaient des bottes de fougère et des paquets de feuilles de vigne, bien réguliers, attachés par quarterons. verbe, sujet, objet : vend -> Lui -> volaille objet complet : toute la volaille Phrase compléte : Lui, vend toute la volaille qu’il veut… verbe, sujet, objet : achetait -> il -> morceau objet complet : un morceau de dinde ou un morceau d’ oie de douze sous Phrase compléte : Quand Florent rentrait trop tard pour faire cuire quelque bout de viande, il achetait en bas un morceau de dinde ou un morceau d’oie de douze sous. verbe, sujet, objet : vendu -> Il -> mobilier objet complet : le pauvre mobilier de la rue Royer - Collard Phrase compléte : Il avait vendu le pauvre mobilier de la rue Royer-Collard, et en gardait l’argent, quarante et quelques francs, pour que ce farceur de Quenu, disait-il, ne le jetât pas par les fenêtres. verbe, sujet, objet : vendu -> vérité -> rôtisserie objet complet : la rôtisserie , il vécut de ses rentes pendant un an Phrase compléte : La vérité fut qu’après avoir vendu la rôtisserie, il vécut de ses rentes pendant un an. verbe, sujet, objet : vendait -> elle -> où objet complet : où Phrase compléte : Lorsqu’elle le vit s’établir aux Halles, à deux pas du pavillon où elle vendait du beurre, des fromages et des œufs, elle l’accusa d’avoir « inventé ça pour la taquiner et lui porter mauvaise chance. verbe, sujet, objet : vendait -> elle -> fromages objet complet : , des fromages et des œufs Phrase compléte : Lorsqu’elle le vit s’établir aux Halles, à deux pas du pavillon où elle vendait du beurre, des fromages et des œufs, elle l’accusa d’avoir « inventé ça pour la taquiner et lui porter mauvaise chance. verbe, sujet, objet : vendu -> vous -> paire objet complet : cette paire de soles Phrase compléte : quand vous m’avez vendu cette paire de soles, vous savez, est-ce que je suis allée vous dire qu’elles étaient pourries devant le monde ! verbe, sujet, objet : vendu -> m’ -> paire objet complet : cette paire de soles Phrase compléte : quand vous m’avez vendu cette paire de soles, vous savez, est-ce que je suis allée vous dire qu’elles étaient pourries devant le monde ! verbe, sujet, objet : vendrait -> Il -> semelles objet complet : des semelles de bottes Phrase compléte : Il vendrait des semelles de bottes pour des paires de soles.
Il est possible d'amender un modèle existant avec un jeu de données annotées de taille réduite
Exemple sur les entités nommées mais la procèdure d'entraînement fonctionne pour d'autres niveaux d'annotations (pos, dépendance)
Petit exemple avec des extraits de la page Wikipedia https://fr.wikipedia.org/wiki/Personnages_de_Mario
Nous conservons le tagset utilisés dans le français (LOC, MISC, ORG, PER)
Nous travaillerons sur 5 petits fichiers
!ls txt
luigi.txt mario.txt peach.txt toad.txt yoshi.txt
!more txt/yoshi.txt
Yoshi est un dinosaure ami de Mario. Il peut attraper des objets éloignés grâce à sa longue langue et les avaler pour ensuite pondre des œufs. Comme Toad, son e spèce existe en plusieurs couleurs : bleu clair, bleu foncé, rose, violet, vert, jaune, noir, blanc et rouge. On peut le remarquer également grâce à son gros ne z. Il est apparu pour la première fois dans Super Mario World en 1990, et est le personnage principal de la série Yoshi's Island où il doit sauver Bébé Luigi, r eprendre des fruits volés, récupérer des pelotes…
nlp = spacy.load('fr_core_news_md')
with open('txt/yoshi.txt') as input:
content = input.read()
doc = nlp(content)
displacy.render(doc, style="ent", jupyter=True)
Ces fichiers doivent être tokenizés puis annotés au format BIO. Voir l'exemple https://github.com/explosion/spaCy/blob/master/extra/example_data/ner_example_data/ner-token-per-line.iob
Puis les fichiers seront convertis à l'aide de la commande convert
(https://spacy.io/api/cli#convert).
Exemple :
python -m spacy convert dev_conll/yoshi.conll dev_dir/ --converter ner
!head dev_conll/yoshi.conll
Yoshi B-PER est O un O dinosaure O ami O de O Mario B-PER . O Il O peut O
Dans la version 3.0, Spacy utilise un fichier de configuration dont le format est défini dans Thinc (https://thinc.ai/docs/usage-config).
Le plus simple est d'utiliser le widget de la doc pour définir les paramètres principaux : https://spacy.io/usage/training#quickstart
La commande ci-dessous permet de générer votre fichier de configuration :
python -m spacy init fill-config base_config.cfg config.cfg
Il y a quantité de paramètres à définir dans ce fichier de config évidemment. init
utilise des valeurs par défaut qu'on peut modifier.
From scratch :
[components.ner]
factory = "ner"
Depuis un modèle :
[components.ner]
source = "fr_core_news_md"
L'entraînement à proprement parler se fait en ligne de commande :
!python -m spacy train config.cfg -o model --paths.train train_corpus/all.spacy --paths.dev dev_corpus/yoshi.spacy
ℹ Saving to output directory: model ℹ Using CPU =========================== Initializing pipeline =========================== [2022-05-19 12:13:37,632] [INFO] Set up nlp object from config [2022-05-19 12:13:37,640] [INFO] Pipeline: ['tok2vec', 'ner'] [2022-05-19 12:13:37,644] [INFO] Created vocabulary [2022-05-19 12:13:37,644] [INFO] Finished initializing nlp object [2022-05-19 12:13:37,746] [INFO] Initialized pipeline components: ['tok2vec', 'ner'] ✔ Initialized pipeline ============================= Training pipeline ============================= ℹ Pipeline: ['tok2vec', 'ner'] ℹ Initial learn rate: 0.001 E # LOSS TOK2VEC LOSS NER ENTS_F ENTS_P ENTS_R SCORE --- ------ ------------ -------- ------ ------ ------ ------ 0 0 0.00 232.94 20.00 25.00 16.67 0.20 200 200 752.01 5987.48 61.54 57.14 66.67 0.62 400 400 0.00 0.00 61.54 57.14 66.67 0.62 600 600 0.00 0.00 61.54 57.14 66.67 0.62 800 800 0.00 0.00 61.54 57.14 66.67 0.62 1000 1000 0.00 0.00 61.54 57.14 66.67 0.62 1200 1200 0.00 0.00 61.54 57.14 66.67 0.62 1400 1400 0.00 0.00 61.54 57.14 66.67 0.62 1600 1600 0.00 0.00 61.54 57.14 66.67 0.62 1800 1800 0.00 0.00 61.54 57.14 66.67 0.62 ✔ Saved pipeline to output directory model/model-last
Spacy propose également un outil d'évaluation qui vous permettra de comparer les performances des modèles que vous avez généré. Les métriques sont choisies en fonction du/des types d'annotations du modèle. Pour les entités nommées on a : Précision, Rappel, F-Mesure.
python -m spacy evaluate model/model-best/ dev_corpus/yoshi.spacy
python -m spacy evaluate fr_core_news_md dev_corpus/yoshi.spacy
!python -m spacy evaluate fr_core_news_md dev_corpus/yoshi.spacy
ℹ Using CPU ================================== Results ================================== TOK - TAG 0.00 POS - MORPH - LEMMA - UAS - LAS - NER P 33.33 NER R 33.33 NER F 33.33 SENT P 0.00 SENT R 0.00 SENT F 0.00 SPEED 3669 =============================== NER (per type) =============================== P R F MISC 33.33 50.00 40.00 PER 33.33 25.00 28.57
!python -m spacy evaluate model/model-best dev_corpus/yoshi.spacy
ℹ Using CPU ================================== Results ================================== TOK - NER P 57.14 NER R 66.67 NER F 61.54 SPEED 16436 =============================== NER (per type) =============================== P R F PER 50.00 75.00 60.00 MISC 100.00 50.00 66.67
nlp = spacy.load('model/model-best')
with open('txt/yoshi.txt') as input:
content = input.read()
doc = nlp(content)
displacy.render(doc, style="ent", jupyter=True)
spaCy prévoit les mécanismes d'export et import des modèles et des données : https://spacy.io/usage/saving-loading
Dans l'ordre :