Switch to unified view

a b/notebooks/premier-pipeline.md
1
---
2
jupyter:
3
  jupytext:
4
    formats: md,ipynb
5
    text_representation:
6
      extension: .md
7
      format_name: markdown
8
      format_version: "1.3"
9
      jupytext_version: 1.13.5
10
  kernelspec:
11
    display_name: "Python 3.9.5 64-bit ('.env': venv)"
12
    name: python3
13
---
14
15
<!-- #region slideshow={"slide_type": "slide"} -->
16
17
# EDS-NLP – Présentation
18
19
<!-- #endregion -->
20
21
## Texte d'exemple
22
23
```python
24
with open('example.txt', 'r') as f:
25
    text = f.read()
26
```
27
28
```python
29
print(text)
30
```
31
32
## Définition d'un pipeline spaCy
33
34
```python slideshow={"slide_type": "slide"}
35
# Importation de spaCy
36
import spacy
37
```
38
39
```python
40
# Chargement des composants EDS-NLP
41
42
```
43
44
```python
45
# Création de l'instance spaCy
46
nlp = spacy.blank('fr')
47
48
# Normalisation des accents, de la casse et autres caractères spéciaux
49
nlp.add_pipe('normalizer')
50
# Détection des fins de phrases
51
nlp.add_pipe('sentences')
52
53
# Extraction d'entités nommées
54
nlp.add_pipe(
55
    'matcher',
56
    config=dict(
57
        terms=dict(respiratoire=[
58
            'difficultes respiratoires',
59
            'asthmatique',
60
            'toux',
61
        ]),
62
        regex=dict(
63
            covid=r'(?i)(?:infection\sau\s)?(covid[\s\-]?19|corona[\s\-]?virus)',
64
            traitement=r'(?i)traitements?|medicaments?'),
65
        attr='NORM',
66
    ),
67
)
68
69
nlp.add_pipe('dates')
70
71
# Qualification des entités
72
nlp.add_pipe('negation')
73
nlp.add_pipe('hypothesis')
74
nlp.add_pipe('family')
75
nlp.add_pipe('rspeech')
76
```
77
78
## Application du pipeline
79
80
```python
81
doc = nlp(text)
82
```
83
84
```python
85
doc
86
```
87
88
Les traitements effectués par EDS-NLP (et spaCy en général) sont non-destructifs :
89
90
```python
91
# Non-destruction
92
doc.text == text
93
```
94
95
Pour des tâches comme la normalisation, EDS-NLP ajoute des attributs aux tokens, sans perte d'information :
96
97
```python
98
# Normalisation
99
print(f"{'texte':<15}", 'normalisation')
100
print(f"{'-----':<15}", '-------------')
101
for token in doc[3:15]:
102
    print(f"{token.text:<15}", f"{token.norm_}")
103
```
104
105
Le pipeline que nous avons appliqué a extrait des entités avec le `matcher`.
106
107
Les entités détectées se retrouvent dans l'attribut `ents` :
108
109
```python
110
doc.ents
111
```
112
113
EDS-NLP étant fondée sur spaCy, on peut utiliser tous les outils proposés autour de cette bibliothèque :
114
115
```python
116
from spacy import displacy
117
```
118
119
```python
120
displacy.render(
121
    doc,
122
    style='ent',
123
    options={'colors': dict(respiratoire='green', covid='orange')},
124
)
125
```
126
127
Focalisons-nous sur la première entité :
128
129
```python
130
entity = doc.ents[0]
131
```
132
133
```python
134
entity
135
```
136
137
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 :
138
139
```python
140
entity._.negated
141
```
142
143
Le pipeline n'a pas détecté de négation pour cette entité.
144
145
## Application du pipleline sur une table de textes
146
147
Les textes seront le plus souvent disponibles sous la forme d'un DataFrame pandas, qu'on peut simuler ici :
148
149
```python
150
import pandas as pd
151
```
152
153
```python
154
note = pd.DataFrame(dict(note_text=[text] * 10))
155
note['note_id'] = range(len(note))
156
note = note[['note_id', 'note_text']]
157
```
158
159
```python
160
note
161
```
162
163
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 :
164
165
```python
166
# Ici on crée une liste qui va contenir les documents traités par spaCy
167
docs = list(nlp.pipe(note.note_text))
168
```
169
170
On veut récupérer les entités détectées et les information associées (empans, qualification, etc) :
171
172
```python
173
def get_entities(doc):
174
    """Extract a list of qualified entities from a spaCy Doc object"""
175
    entities = []
176
177
    for ent in doc.ents:
178
        entity = dict(
179
            start=ent.start_char,
180
            end=ent.end_char,
181
            label=ent.label_,
182
            lexical_variant=ent.text,
183
            negated=ent._.negated,
184
            hypothesis=ent._.hypothesis,
185
        )
186
187
        entities.append(entity)
188
189
    return entities
190
```
191
192
```python
193
note['entities'] = [get_entities(doc) for doc in nlp.pipe(note.note_text)]
194
```
195
196
```python
197
note
198
```
199
200
On peut maintenant récupérer les entités détectées au format `NOTE_NLP` (ou similaire) :
201
202
```python
203
# Sélection des colonnes
204
note_nlp = note[['note_id', 'entities']]
205
206
# "Explosion" des listes d'entités, et suppression des lignes vides (documents sans entité)
207
note_nlp = note_nlp.explode('entities').dropna()
208
209
# Re-création de l'index, pour des raisons internes à pandas
210
note_nlp = note_nlp.reset_index(drop=True)
211
```
212
213
```python
214
note_nlp
215
```
216
217
Il faut maintenant passer d'une colonne de dictionnaires à une table `NOTE_NLP` :
218
219
```python
220
note_nlp = note_nlp[['note_id']].join(pd.json_normalize(note_nlp.entities))
221
```
222
223
```python
224
note_nlp
225
```
226
227
On peut aggréger la qualification des entités en une unique colonne :
228
229
```python
230
# Création d'une colonne "discard" -> si l'entité est niée ou hypothétique, on la supprime des résultats
231
note_nlp['discard'] = note_nlp[['negated', 'hypothesis']].max(axis=1)
232
```
233
234
```python
235
note_nlp
236
```
237
238
```python
239
240
```
241
242
```python
243
244
```
245
246
```python
247
248
```
249
250
```python
251
%%timeit
252
for text in texts:
253
    nlp(text)
254
```
255
256
```python
257
%%timeit
258
for text in nlp.pipe(texts, n_process=-1):
259
    pass
260
```