Le traitement du langage naturel est désormais omniprésent: les interfaces vocales et les robots de discussion se développent rapidement, des modèles sont en cours de développement pour traiter des données textuelles volumineuses et la traduction automatique continue de se développer.
Dans cet article, nous examinerons la bibliothèque relativement nouvelle SpaCy, qui est actuellement l'une des solutions les plus populaires et les plus pratiques pour le traitement de texte en Python. Sa fonctionnalité vous permet de résoudre un très large éventail de tâches: de l'identification des parties du discours et l'extraction d'entités nommées à la création de vos propres modèles d'analyse.
Pour commencer, voyons comment les données sont traitées dans SpaCy. Le texte chargé pour le traitement passe séquentiellement à travers divers composants de traitement et est enregistré comme une instance de l'objet Doc:
Doc est la structure de données centrale de SpaCy, c'est dans celle-ci que sont stockées des séquences de mots ou, comme on les appelle aussi, des jetons. Dans l'objet Doc, deux autres types d'objets peuvent être distingués: Token et Span. Le jeton est un lien vers des mots individuels d'un document, et Span est un lien vers une séquence de plusieurs mots (vous pouvez les créer vous-même):
Une autre structure de données importante est l'objet Vocab, qui stocke un ensemble de tables de recherche communes à tous les documents. Cela économise de la mémoire et fournit une source unique d'informations pour tous les documents traités.
Les jetons de document sont connectés à l'objet Vocab via un hachage, à l'aide duquel vous pouvez obtenir les formes initiales des mots ou d'autres attributs lexicaux des jetons:
nous savons maintenant comment le stockage et le traitement des données dans la bibliothèque SpaCy sont organisés. Comment profiter des opportunités qu'il offre? Jetons un coup d'œil aux opérations qui peuvent être utilisées pour traiter le texte en séquence.
1. Opérations de base
Avant de commencer à travailler avec du texte, vous devez importer le modèle de langue. Pour la langue russe, il existe un modèle officiel de SpaCy qui prend en charge la tokenisation (fractionnement du texte en jetons séparés) et un certain nombre d'autres opérations de base:
from spacy.lang.ru import Russian
Après avoir importé et instancié le modèle de langage, vous pouvez commencer à traiter le texte. Pour ce faire, il vous suffit de transmettre le texte à l'instance créée:
nlp = Russian()
doc = nlp(" , .")
Travailler avec l'objet Doc résultant est très similaire à travailler avec des listes: vous pouvez accéder au jeton souhaité par index ou créer des tranches à partir de plusieurs jetons. Et pour obtenir le texte d'un jeton ou d'une tranche, vous pouvez utiliser l'attribut text:
token = doc[0]
print(token.text)
span = doc[3:6]
print(span.text)
Pour plus d'informations sur le type d'informations contenues dans le jeton, les attributs suivants peuvent être utilisés:
- is_alpha - vérifie si le jeton ne contient que des caractères alphabétiques
- is_punct - vérifie si le jeton est un signe de ponctuation
- like_num - vérifie si un jeton est un nombre
print("is_alpha: ", [token.is_alpha for token in doc])
print("is_punct: ", [token.is_punct for token in doc])
print("like_num: ", [token.like_num for token in doc])
Prenons un autre exemple, où tous les jetons précédant le point sont affichés à l'écran. Pour obtenir ce résultat, lors de l'itération sur des jetons, vérifiez le jeton suivant à l'aide de l'attribut token.i:
for token in doc:
if token.i+1 < len(doc):
next_token = doc[token.i+1]
if next_token.text == ".":
print(token.text)
2. Opérations avec syntaxe
D'autres modèles sont utilisés pour des opérations de traitement de texte plus complexes. Ils sont spécialement formés pour les tâches liées à la syntaxe, à l'extraction d'entités nommées et à l'utilisation de la signification des mots. Par exemple, pour l'anglais, il existe 3 modèles officiels de taille différente. Pour la langue russe, pour le moment, le modèle officiel n'a pas encore été formé, mais il existe déjà un modèle ru2 provenant de sources tierces qui peut fonctionner avec la syntaxe.
À la fin de cet article, nous discuterons de la façon de créer vos propres modèles ou de former en plus les modèles existants afin qu'ils fonctionnent mieux pour des tâches spécifiques.
Pour illustrer pleinement les capacités de SpaCy, nous utiliserons les modèles en anglais de cet article. Configurons un petit modèle en_core_web_sm, ce qui est excellent pour démontrer les possibilités. Pour l'installer sur la ligne de commande, vous devez taper:
python -m spacy download en_core_web_sm
En utilisant ce modèle, nous pouvons obtenir pour chacun des jetons une partie du discours, un rôle dans une phrase et un jeton dont il dépend:
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("New Apple MacBook set launch tomorrow")
for token in doc:
token_text = token.text
token_pos = token.pos_
token_dep = token.dep_
token_head = token.head.text
print(f"{token_text:<12}{token_pos:<10}" \
f"{token_dep:<10}{token_head:<12}")
New PROPN compound MacBook Apple PROPN compound MacBook MacBook PROPN nsubj set set VERB ROOT set to PART aux launch launch VERB xcomp set tomorrow NOUN npadvmod launch
Sans aucun doute, la meilleure façon de voir les dépendances n'est pas de lire les données textuelles, mais de construire une arborescence de syntaxe. La fonction de déplacement peut vous aider, il vous suffit de transférer le document:
from spacy import displacy
displacy.render(doc, style='dep', jupyter=True)
À la suite de l'exécution du code, nous obtenons un arbre sur lequel se trouvent toutes les informations syntaxiques sur la phrase:
Pour déchiffrer les noms de balises, vous pouvez utiliser les fonctions explicatives:
print(spacy.explain("aux"))
print(spacy.explain("PROPN"))
auxiliary
proper noun
Ici, les abréviations sont affichées sur l'écran, à partir duquel nous pouvons apprendre que aux représente une particule auxiliaire (auxiliaire) et PROPN représente un nom propre.
SpaCy implémente également la possibilité de trouver la forme initiale d'un mot pour l'un des jetons (-PRON- est utilisé pour les pronoms):
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("I saw a movie yesterday")
print(' '.join([token.lemma_ for token in doc]))
'-PRON- see a movie yesterday'
3. Mise en évidence des entités nommées
Souvent, pour travailler avec du texte, vous devez mettre en évidence les entités mentionnées dans le texte. L'attribut doc.ents est utilisé pour lister les entités nommées dans le document, et l'attribut ent.label_ est utilisé pour obtenir l'étiquette de cette entité:
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp("Apple is looking at buying U.K. startup for 1$ billion")
for ent in doc.ents:
print(ent.text, ent.label_)
Apple ORG
U.K. GPE
1$ billion MONEY
Vous pouvez également utiliser l'attribut expliquer ici pour découvrir le décodage des étiquettes d'entités nommées:
print(spacy.explain("GPE"))
Pays, villes, Ă©tats
Et la fonction de déplacement vous aidera à visualiser les listes d'entités directement dans le texte:
depuis spacy import déplacy
déplacy.render (doc, style = 'ent', jupyter = True)
4. Créez vos propres modèles pour la recherche de texte
Le module spaCy contient un outil très utile qui vous permet de créer vos propres modèles de recherche de texte. En particulier, vous pouvez rechercher des mots d'une certaine partie du discours, toutes les formes d'un mot par sa forme initiale, vérifier le type de contenu dans le jeton. Voici une liste des principaux paramètres:
Essayons de créer notre propre modèle pour reconnaître une séquence de jetons. Disons que nous voulons extraire des lignes de texte sur les Coupes du monde de cricket FIFA ou ICC avec la mention de l'année:
import spacy
from spacy.matcher import Matcher
nlp = spacy.load("en_core_web_sm")
matcher = Matcher(nlp.vocab)
pattern = [
{"IS_DIGIT": True},
{"LOWER": {"REGEX": "(fifa|icc)"}},
{"LOWER": "cricket", "OP": "?"},
{"LOWER": "world"},
{"LOWER": "cup"}
]
matcher.add("fifa_pattern", None, pattern)
doc = nlp("2018 ICC Cricket World Cup: Afghanistan won!")
matches = matcher(doc)
for match_id, start, end in matches:
matched_span = doc[start:end]
print(matched_span)
2018 ICC Cricket World Cup
Ainsi, dans ce bloc de code, nous avons importé un objet Matcher spécial qui nous permet de stocker un ensemble de modèles personnalisés. Après l'initialisation, nous avons créé un modèle dans lequel nous avons spécifié la séquence de jetons. Veuillez noter que nous avons utilisé des expressions régulières pour choisir entre ICC et FIFA, et pour le jeton Cricket - une clé indiquant la présence facultative de ce jeton.
Après avoir créé un modèle, vous devez l'ajouter à l'ensemble à l'aide de la fonction d'ajout, en spécifiant un ID de modèle unique dans les paramètres. Les résultats de la recherche sont présentés sous la forme d'une liste de tuples. Chacun des tuples se compose de l'ID de correspondance et des indices de début et de fin de la tranche trouvée dans le document.
5. Détermination de la proximité sémantique
Deux mots peuvent avoir un sens très similaire, mais comment mesurez-vous leur proximité? Dans de telles tâches, les vecteurs sémantiques peuvent venir à la rescousse. Si deux mots ou expressions verbeuses sont similaires, leurs vecteurs seront proches l'un de l'autre.
Le calcul de la proximité sémantique des vecteurs dans SpaCy n'est pas difficile si le modèle de langage a été formé pour résoudre de tels problèmes. Le résultat dépend fortement de la taille du modèle, donc pour cette tâche prenons un modèle plus grand:
import spacy
nlp = spacy.load("en_core_web_md")
doc1 = nlp("I like burgers")
doc2 = nlp("I like pizza")
print(doc1.similarity(doc2))
0.9244169833828932
La valeur peut aller de zéro à un: plus il est proche de un, plus la similitude est grande. Dans l'exemple ci-dessus, nous avons comparé deux documents, cependant, vous pouvez comparer des jetons et des tranches individuels de la même manière.
L'évaluation de proximité sémantique peut être utile pour résoudre de nombreux problèmes. Par exemple, vous pouvez l'utiliser pour mettre en place un système de recommandation afin qu'il propose à l'utilisateur des textes similaires basés sur ceux déjà lus.
Il est important de se rappeler que l'affinité sémantique est très subjective et dépend toujours du contexte de la tâche. Par exemple, les expressions «j'aime les chiens» et «je déteste les chiens» sont similaires, car les deux expriment des opinions sur les chiens, mais en même temps diffèrent grandement dans leur humeur. Dans certains cas, vous devrez former des modèles de langage supplémentaires afin que les résultats correspondent au contexte de votre problème.
6. Création de vos propres composants de traitement
Le module SpaCy prend en charge un certain nombre de composants intégrés (tokenizer, mise en évidence d'entités nommées), mais vous permet également de définir vos propres composants. En fait, les composants sont appelés séquentiellement des fonctions qui prennent un document en entrée, le modifient et le renvoient. De nouveaux composants peuvent être ajoutés à l'aide de l'attribut add_pipe:
import spacy
def length_component(doc):
doc_length = len(doc)
print(f"This document is {doc_length} tokens long.")
return doc
nlp = spacy.load("en_core_web_sm")
nlp.add_pipe(length_component, first=True)
print(nlp.pipe_names)
doc = nlp("This is a sentence.")
['length_component', 'tagger', 'parser', 'ner']
This document is 5 tokens long.
Dans l'exemple ci-dessus, nous avons créé et ajouté notre propre fonction qui affiche le nombre de jetons dans le document traité. En utilisant l'attribut nlp.pipe_names, nous avons obtenu l'ordre d'exécution des composants: comme nous pouvons le voir, le composant créé est le premier de la liste. Vous pouvez utiliser les options suivantes pour spécifier où ajouter un nouveau composant: La
possibilité d'ajouter des composants personnalisés est un outil très puissant pour optimiser le traitement en fonction de vos besoins.
7. Modèles de formation et de mise à jour
Les modèles statistiques font des prédictions basées sur les exemples sur lesquels ils ont été formés. En règle générale, la précision de ces modèles peut être améliorée en les formant en plus sur des exemples spécifiques à votre tâche. Une formation supplémentaire sur les modèles existants peut être très utile (par exemple, pour la reconnaissance ou l'analyse d'entités nommées).
Des exemples de formation supplémentaires peuvent être ajoutés directement dans l'interface SpaCy. Les exemples eux-mêmes doivent être constitués de données textuelles et d'une liste d'étiquettes pour cet exemple sur lequel le modèle sera entraîné.
À titre d'illustration, envisagez de mettre à jour le modèle pour récupérer les entités nommées. Pour mettre à jour un tel modèle, vous devez lui transmettre de nombreux exemples contenant du texte, une indication des entités et de leur classe. Dans les exemples, vous devez utiliser des clauses entières, car le modèle repose fortement sur le contexte de clause lors de l'extraction d'entités. Il est très important d'entraîner complètement le modèle afin qu'il puisse reconnaître les jetons non-entité.
Par exemple:
("What to expect at Apple's 10 November event", {"entities": [(18,23,"COMPANY")]})
("Is that apple pie I smell?", {"entities": []})
Dans le premier exemple, une entreprise est mentionnée: pour la formation, nous mettons en évidence les postes où son nom commence et se termine, puis nous apposons notre étiquette que cette entité est une entreprise. Dans le deuxième exemple, nous parlons d'un fruit, donc il n'y a pas d'entités.
Les données pour l'entraînement du modèle sont généralement marquées par des humains, mais ce travail peut être légèrement automatisé en utilisant les propres modèles de recherche dans SpaCy ou des programmes de balisage spécialisés (par exemple, Prodigy ).
Une fois les exemples préparés, vous pouvez procéder directement à la formation du modèle. Pour que le modèle s'entraîne efficacement, vous devez exécuter une série de plusieurs formations. A chaque formation, le modèle optimisera les poids de certains paramètres. Les modèles de SpaCy utilisent la technique de descente de gradient stochastique, c'est donc une bonne idée de mélanger les exemples à chaque entraînement et de les transférer également par petites portions (paquets). Cela augmentera la fiabilité des estimations de gradient.
import spacy
import random
from spacy.lang.en import English
TRAINING_DATA = [
("What to expect at Apple's 10 November event",
{"entities": [(18,23,"COMPANY")]})
# ...
]
nlp = English()
for i in range(10):
random.shuffle(TRAINING_DATA)
for batch in spacy.util.minibatch(TRAINING_DATA):
texts = [text for text, annotation in batch]
annotations = [annotation for text, annotation in batch]
nlp.update(texts, annotations)
nlp.to_disk("model")
Dans l'exemple ci-dessus, la boucle était constituée de 10 entraînements. Une fois la formation terminée, le modèle a été enregistré sur le disque dans le dossier du modèle.
Dans les cas où il est nécessaire non seulement de mettre à jour, mais aussi de créer un nouveau modèle, un certain nombre d'opérations sont nécessaires avant de commencer la formation.
Considérez le processus de création d'un nouveau modèle pour mettre en évidence les entités nommées:
nlp = spacy.blank("en")
ner = nlp.create_pipe("ner")
nlp.add_pipe(ner)
ner.add_label("COMPANY")
nlp.begin_training()
Tout d'abord, nous créons un modèle vide en utilisant la fonction spacy.blank ("en"). Le modèle contient uniquement des données de langue et des règles de tokenisation. Ensuite, nous ajoutons un composant ner qui est responsable de la mise en évidence des entités nommées et, à l'aide de l'attribut add_label, ajoutons des étiquettes pour les entités. Ensuite, nous utilisons la fonction nlp.begin_training () pour initialiser le modèle d'entraînement avec une distribution aléatoire des poids. Eh bien, il suffira alors de former le modèle, comme le montre l'exemple précédent.