jupyter:
jupytext:
formats: md,ipynb
text_representation:
extension: .md
format_name: markdown
format_version: "1.3"
jupytext_version: 1.13.5
kernelspec:
display_name: "Python 3.9.5 64-bit ('.env': venv)"
name: python3
with open('example.txt', 'r') as f:
text = f.read()
print(text)
```python slideshow={"slide_type": "slide"}
import spacy
```python
# Chargement des composants EDS-NLP
# Création de l'instance spaCy
nlp = spacy.blank('fr')
# Normalisation des accents, de la casse et autres caractères spéciaux
nlp.add_pipe('normalizer')
# Détection des fins de phrases
nlp.add_pipe('sentences')
# Extraction d'entités nommées
nlp.add_pipe(
'matcher',
config=dict(
terms=dict(respiratoire=[
'difficultes respiratoires',
'asthmatique',
'toux',
]),
regex=dict(
covid=r'(?i)(?:infection\sau\s)?(covid[\s\-]?19|corona[\s\-]?virus)',
traitement=r'(?i)traitements?|medicaments?'),
attr='NORM',
),
)
nlp.add_pipe('dates')
# Qualification des entités
nlp.add_pipe('negation')
nlp.add_pipe('hypothesis')
nlp.add_pipe('family')
nlp.add_pipe('rspeech')
doc = nlp(text)
doc
Les traitements effectués par EDS-NLP (et spaCy en général) sont non-destructifs :
# Non-destruction
doc.text == text
Pour des tâches comme la normalisation, EDS-NLP ajoute des attributs aux tokens, sans perte d'information :
# Normalisation
print(f"{'texte':<15}", 'normalisation')
print(f"{'-----':<15}", '-------------')
for token in doc[3:15]:
print(f"{token.text:<15}", f"{token.norm_}")
Le pipeline que nous avons appliqué a extrait des entités avec le matcher
.
Les entités détectées se retrouvent dans l'attribut ents
:
doc.ents
EDS-NLP étant fondée sur spaCy, on peut utiliser tous les outils proposés autour de cette bibliothèque :
from spacy import displacy
displacy.render(
doc,
style='ent',
options={'colors': dict(respiratoire='green', covid='orange')},
)
Focalisons-nous sur la première entité :
entity = doc.ents[0]
entity
Chaque entité a été qualifiée par les pipelines de négation, hypothèse, etc. Ces pipelines utilisent des extensions spaCy pour stocker leur résultat :
entity._.negated
Le pipeline n'a pas détecté de négation pour cette entité.
Les textes seront le plus souvent disponibles sous la forme d'un DataFrame pandas, qu'on peut simuler ici :
import pandas as pd
note = pd.DataFrame(dict(note_text=[text] * 10))
note['note_id'] = range(len(note))
note = note[['note_id', 'note_text']]
note
On peut appliquer la pipeline à l'ensemble des documents en utilisant la fonction nlp.pipe
, qui permet d'accélérer les traitements en les appliquant en parallèle :
# Ici on crée une liste qui va contenir les documents traités par spaCy
docs = list(nlp.pipe(note.note_text))
On veut récupérer les entités détectées et les information associées (empans, qualification, etc) :
def get_entities(doc):
"""Extract a list of qualified entities from a spaCy Doc object"""
entities = []
for ent in doc.ents:
entity = dict(
start=ent.start_char,
end=ent.end_char,
label=ent.label_,
lexical_variant=ent.text,
negated=ent._.negated,
hypothesis=ent._.hypothesis,
)
entities.append(entity)
return entities
note['entities'] = [get_entities(doc) for doc in nlp.pipe(note.note_text)]
note
On peut maintenant récupérer les entités détectées au format NOTE_NLP
(ou similaire) :
# Sélection des colonnes
note_nlp = note[['note_id', 'entities']]
# "Explosion" des listes d'entités, et suppression des lignes vides (documents sans entité)
note_nlp = note_nlp.explode('entities').dropna()
# Re-création de l'index, pour des raisons internes à pandas
note_nlp = note_nlp.reset_index(drop=True)
note_nlp
Il faut maintenant passer d'une colonne de dictionnaires à une table NOTE_NLP
:
note_nlp = note_nlp[['note_id']].join(pd.json_normalize(note_nlp.entities))
note_nlp
On peut aggréger la qualification des entités en une unique colonne :
# Création d'une colonne "discard" -> si l'entité est niée ou hypothétique, on la supprime des résultats
note_nlp['discard'] = note_nlp[['negated', 'hypothesis']].max(axis=1)
note_nlp
%%timeit
for text in texts:
nlp(text)
%%timeit
for text in nlp.pipe(texts, n_process=-1):
pass