Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • document-docker-guix
  • main
2 results

Target

Select target project
No results found
Select Git revision
  • document-docker-guix
  • main
2 results
Show changes

Commits on Source 7

4 files
+ 62
459
Compare changes
  • Side-by-side
  • Inline

Files

Original line number Diff line number Diff line
%% Cell type:markdown id: tags:

![CNRS](https://anf-tdm-2022.sciencesconf.org/data/header/LOGO_CNRS_CMJN_150x150.png)


# Tutoriel - ANF TDM 2022 Python Geoparsing

Supports pour l'atelier [Librairies Python et Services Web pour la reconnaissance d’entités nommées et la résolution de toponymes](https://anf-tdm-2022.sciencesconf.org/resource/page/id/11) de la formation CNRS [ANF TDM 2022](https://anf-tdm-2022.sciencesconf.org).

**Animateurs**: [Ludovic Moncla](https://ludovicmoncla.github.io) (INSA Lyon) et [Alice Brenon](https://perso.liris.cnrs.fr/abrenon/) (CNRS / INSA Lyon)

## 1. En bref


Dans ce tutoriel, nous allons apprendre plusieurs choses :

- Charger des jeux de données :
  - à partir de fichiers txt importés depuis le disque dur ;
  - à partir de la librairie Python [Perdido](https://github.com/ludovicmoncla/perdido) dans un [Pandas dataframe](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html) (articles encyclopédiques et descriptions de randonnées).
- Manipuler et interroger un dataframe
- Utiliser les librairies [Stanza](https://stanfordnlp.github.io/stanza/index.html), [spaCy](https://spacy.io) et [Perdido](https://github.com/ludovicmoncla/perdido) pour la reconnaissance d'entités nommées
  - afficher les entités nommées annotées ;
  - comparer les résultats de `Stanza`, `spaCy` et `Perdido` ;
  - discuter les dimites des 3 outils pour la tâche de NER.
- Utiliser la librarie `Perdido` pour le geoparsing et le geocoding :
  - cartographier les lieux geocodés ;
  - illustrer la problématique de désambiguïsation des toponymes.

%% Cell type:markdown id: tags:

## 2. Configurer l'environnement

### 2.1 Installer les librairies Python

* Si vous avez configuré votre environnement Conda en utilisant le fichier `requirements.txt`, vous pouvez sauter cette étape et aller à la section `3.2 Importer les librairies`.
* Si vous avez configuré votre environnement Conda en utilisant le fichier `environment.yml` ou si vous utilisez un environnement Google Colab / Binder, vous devez installer `perdido` en utilisant `pip` :

%% Cell type:code id: tags:

``` python
!pip install perdido
```
* Si vous avez déjà configuré votre environnement, soit avec conda, soit avec pip (voir le fichier readme), vous pouvez ignorer la cellule suivante.
* Si vous exécutez ce notebook depuis Google Colab / Binder, vous devez exécuter la cellule suivante :

%% Cell type:markdown id: tags:

* Si vous avez déjà configuré votre environnement conda, soit avec conda, soit avec pip (voir le fichier readme), vous pouvez ignorer la cellule suivante.
* Si vous exécutez ce notebook depuis Google Colab / Binder, vous devez exécuter la cellule suivante :


%% Cell type:code id: tags:

``` python
!pip install perdido
!pip install stanza
```

%% Cell type:markdown id: tags:

### 2.2 Importer les librairies


Tout d'abord, nous allons charger certaines bibliothèques spécifiques de `Perdido` que nous utiliserons dans ce notebook. Ensuite, nous importons quelques outils qui nous aideront à analyser et à visualiser le texte.

%% Cell type:code id: tags:

``` python
import warnings
warnings.filterwarnings('ignore')

from perdido.geoparser import Geoparser
from perdido.geocoder import Geocoder

from perdido.datasets import load_edda_artfl, load_edda_perdido, load_choucas_perdido

from spacy import displacy
```

%% Cell type:markdown id: tags:

## 3. Chargement et exploration des données

### 3.1 Chargement d'un document texte à partir d'un fichier

%% Cell type:code id: tags:

``` python
# On définit une fonction qui prend en paramètre le chemin d'un fichier et qui retourne sont contenu

def load_txt(filepath):
    with open(filepath) as f:
        return f.read()
```

%% Cell type:code id: tags:

``` python
# On utilise la fonction précédente pour récupérer le contenu de l'article encyclopédique 'Arques' (volume01-4083.txt) présent dans le dossier data
arques = load_txt('data/volume01-4083.txt')
arques = load_txt('data/edda-volume01-4083.txt')
```

%% Cell type:markdown id: tags:

* Afficher le contenu du fichier

%% Cell type:code id: tags:

``` python
print(arques)
```

%% Output

    * ARQUES, (Géog.) petite ville de France, en Normandie, au pays de Caux, sur la petite riviere d'Arques. Long. 18. 50. lat. 49. 54.

%% Cell type:markdown id: tags:

### 3.2 Chargement d'un jeu de données à partir de la librairie Perdido

La libraire de geoparsing [Perdido](https://github.com/ludovicmoncla/perdido) embarque deux jeux de données :
 1. des articles encyclopédiques (volume 7 de l'Encyclopédie de Diderot et d'Alembert (1751-1772)), fournit par l'[ARTFL](https://encyclopedie.uchicago.edu) dans le cadre du projet [GEODE](https://geode-project.github.io) ;
 2. des descriptions de randonnées (chaque description est associée à sa trace GPS. Elles proviennent du site [www.visorando.fr](https://www.visorando.com) et ont été collectées dans le cadre du projet [ANR CHOUCAS](http://choucas.ign.fr).

 Dans un premier temps nous allons nous intéresser au jeu de données des articles encyclopédiques. Ce jeu de données est présent dans la librairie en deux versions, une version "brute" (articles fournis par l'ARTFL) au format dataframe et une version déjà annotée par Perdido (format PerdidoCollection). Nous allons charger la version brute et voir comment manipuler un dataframe.

%% Cell type:markdown id: tags:

* Charger le jeu de données :

%% Cell type:code id: tags:

``` python
dataset_artfl = load_edda_artfl()
data_artfl = dataset_artfl['data']
```

%% Cell type:markdown id: tags:

* Afficher les informations sur le jeu de données :

%% Cell type:code id: tags:

``` python
data_artfl.info()
```

%% Output

    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 3385 entries, 0 to 3384
    Data columns (total 7 columns):
     #   Column     Non-Null Count  Dtype
    ---  ------     --------------  -----
     0   filename   3385 non-null   object
     1   volume     3385 non-null   int64
     2   number     3385 non-null   int64
     3   head       3384 non-null   object
     4   normClass  3384 non-null   object
     5   author     3384 non-null   object
     6   text       3385 non-null   object
    dtypes: int64(2), object(5)
    memory usage: 185.2+ KB

%% Cell type:markdown id: tags:

On remarque que certaines colonnes ont une donnée manquante (3384 lignes non nulles contre 3385 lignes au total). Pour la suite des opérations que nous allons réaliser il est nécessaire de supprimer les lignes incomplètes.

* Supprimer la ligne incomplète :

%% Cell type:code id: tags:

``` python
data_artfl.dropna(inplace=True)     # data_artfl = data_artfl.dropna()
```

%% Cell type:markdown id: tags:

* Vérifier le résultat :

%% Cell type:code id: tags:

``` python
data_artfl.info()
```

%% Output

    <class 'pandas.core.frame.DataFrame'>
    Int64Index: 3384 entries, 0 to 3384
    Data columns (total 7 columns):
     #   Column     Non-Null Count  Dtype
    ---  ------     --------------  -----
     0   filename   3384 non-null   object
     1   volume     3384 non-null   int64
     2   number     3384 non-null   int64
     3   head       3384 non-null   object
     4   normClass  3384 non-null   object
     5   author     3384 non-null   object
     6   text       3384 non-null   object
    dtypes: int64(2), object(5)
    memory usage: 211.5+ KB

%% Cell type:markdown id: tags:

* Afficher la liste des premiers articles :

%% Cell type:code id: tags:

``` python
data_artfl.head()
```

%% Output

                filename  volume  number              head             normClass  \
    0     volume07-1.tei       7       1        Title Page          unclassified
    1    volume07-10.tei       7      10  FOESNE ou FOUANE        Marine | Pêche
    2   volume07-100.tei       7     100   Fond de la hune          unclassified
    3  volume07-1000.tei       7    1000          Fronteau  Bourrelier | Sellier
    4  volume07-1001.tei       7    1001         FRONTIERE            Géographie
    
         author                                               text
    0  unsigned  ENCYCLOPÉDIE, ou DICTIONNAIRE RAISONNÉ DES SCI...
    1    Bellin  FOESNE ou FOUANE, sub. s. (Marine & Pêche.) c'...
    2    Bellin  Fond de la hune ; ce sont les planches qu on p...
    3   Diderot  * Fronteau, terme de Sellier-Bourrelier ; c'es...
    4   Diderot  * FRONTIERE, s. f. (Géog.) se dit des limites,...

%% Cell type:markdown id: tags:

### 3.3 Manipulation d'un dataframe

%% Cell type:markdown id: tags:

Nous avons maintenant accès à tous les attributs et méthodes de l'objet [dataframe](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html). Par exemple, nous pouvons facilement connaître le nombre de lignes dans notre dataframe qui correspond au nombre d'articles dans notre corpus :

%% Cell type:code id: tags:

``` python
n = data_artfl.shape[0]
print('Il y a ' + str(n) + ' articles dans le jeu de données.')
```

%% Output

    Il y a 3384 articles dans le jeu de données.

%% Cell type:markdown id: tags:

#### 3.3.1 Recherche par métadonnées


Maintenant que les données sont chargées dans un dataframe, nous pouvons sélectionner des groupes d'articles sur la base de leurs métadonnées.

Pour cela on utilise la méthode [loc()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.loc.html).


%% Cell type:markdown id: tags:

* Selectionner la ligne du dataframe qui correspond à l'article 'FRONTIGNAN' :

%% Cell type:code id: tags:

``` python
frontignan = data_artfl.loc[data_artfl['head'] == 'FRONTIGNAN']
frontignan
```

%% Output

                filename  volume  number        head   normClass    author  \
    5  volume07-1002.tei       7    1002  FRONTIGNAN  Géographie  Jaucourt
    
                                                    text
    5  FRONTIGNAN, (Géog.) petite ville de France. au...

%% Cell type:markdown id: tags:

* Récupérer les valeurs des attributs (colonnes) :

%% Cell type:code id: tags:

``` python
print('volume :', frontignan.volume.item())   # similaire à frontignan['volume'].item()
print('number :', frontignan.number.item())
print('text :', frontignan.text.item())
```

%% Output

    volume : 7
    number : 1002
    text : FRONTIGNAN, (Géog.) petite ville de France. au Bas-Languedoc, connue par ses excellens vins muscats, & ses raisins de caisse qu'on appelle passerilles. Quelques savans croyent, sans en donner de preuves, que cette ville est le forum Domitii des Romains. Elle est située sur l'étang de Maguelone, à six lieues N. E. d'Agde, & cinq S. O. de Montpellier. Long. 15d. 24'. lat. 43d. 28'. (D. J.)

%% Cell type:markdown id: tags:

Nous pouvons également filtrer les données sur la base de l'auteur.

* Extraire les articles rédigés par Jaucourt :

%% Cell type:code id: tags:

``` python
req = 'Jaucourt'
d_Jaucourt = data_artfl.loc[data_artfl['author'] == req]

n = d_Jaucourt.shape[0]
print(str(n) + ' articles ont été rédigés par '+ req)
```

%% Output

    698 articles ont été rédigés par Jaucourt

%% Cell type:markdown id: tags:

* Afficher les 5 premiers :

%% Cell type:code id: tags:

``` python
d_Jaucourt.head()
```

%% Output

                 filename  volume  number                           head  \
    5   volume07-1002.tei       7    1002                     FRONTIGNAN
    29  volume07-1024.tei       7    1024               FROWARD, le cap.
    32  volume07-1027.tei       7    1027                      FRUGALITÉ
    37  volume07-1031.tei       7    1031                  Fruit verreux
    38  volume07-1032.tei       7    1032  Fruit, (art de conserver le)
    
                 normClass    author  \
    5           Géographie  Jaucourt
    29          Géographie  Jaucourt
    32              Morale  Jaucourt
    37  Histoire naturelle  Jaucourt
    38   Economie rustique  Jaucourt
    
                                                     text
    5   FRONTIGNAN, (Géog.) petite ville de France. au...
    29  FROWARD, le cap. (Géog.) & par les François le...
    32  FRUGALITÉ, (Morale.) simplicité de moeurs & de...
    37  Fruit verreux, (Hist. nat.) c'est le nom qu'on...
    38  Fruit, (art de conserver le) Economie rustiq. ...

%% Cell type:markdown id: tags:

### 3.3.2 Recherche par mots-clés

%% Cell type:markdown id: tags:

Autre exemple, nous pouvons filtrer les articles en fonction de leur classification dans l'*Encyclopédie*.
Pour cela nous utiliserons la colonne `normclass`, qui indique la classifications retenue (et normalisée) par l'ARTFL.

Par exemple pour la classe 'Géographie', nous pouvons faire la requête suivante (le résultat est stocké dans un nouveau dataframe `d_geo` :

%% Cell type:code id: tags:

``` python
req = 'Géographie'
d_geo = data_artfl[data_artfl['normClass'].str.contains(req, case=False)]

n = d_geo.shape[0]
print(str(n) + ' articles sont classés en '+ req)
```

%% Output

    496 articles sont classés en Géographie

%% Cell type:code id: tags:

``` python
req = 'ville de'
d_geo = data_artfl[data_artfl['text'].str.contains(req, case=False)]

n = d_geo.shape[0]
print(str(n) + " articles contiennent l'expression '"+ req + "'")
```

%% Output

    177 articles contiennent l'expression 'ville de'

%% Cell type:markdown id: tags:

### 3.3.3 Regroupements

%% Cell type:markdown id: tags:

On peut également regrouper les données selon un ou plusieurs attributs (colonnes) et compter le nombre de données de chaque groupe avec les méthodes [groupby()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html) et [count()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.count.html).

* Afficher le nombre d'articles classés en Géographie par auteur :

%% Cell type:code id: tags:

``` python
d_geo.groupby(['author'])["filename"].count()
```

%% Output

    author
    Anonymous5                 1
    Beauzée & Douchet          1
    Boucher d'Argis           14
    Bouchu                     1
    Desmarest                  1
    Diderot                    2
    Jaucourt                 141
    Le Blond                   1
    Le Blond & d'Alembert      1
    Le Roy                     1
    Lucotte5                   1
    Mallet                     1
    Quesnay                    1
    Robert de Vaugondy         1
    Tressan                    1
    Voltaire                   2
    d'Alembert                 2
    d'Holbach                  1
    unsigned                   3
    Name: filename, dtype: int64

%% Cell type:markdown id: tags:

Dans cette partie nous avons vu brievement comment manipuler un dataframe pour selectionner certaines données en filtrant selon certaines métadonnées ou par une recheche par mot clés. Ces opérations sont utiles mais un peu limitées, nous allons voir dans la suite de ce notebook comment enrichir les métadonnées et en particulier comment annoter les entités nommées présents dans les textes.

%% Cell type:markdown id: tags:

## 4. Reconnaissance d'Entités Nommées (NER)

La reconnaissance d'entités nommées, *Named Entity Recognition* (NER) en anglais, est une tâche très importante et incontournable en traitement automatique des langues (TAL) et en compréhension du langage naturel (NLU en anglais).
Cette tâche consiste à rechercher des objets textuels (un mot, ou un groupe de mots, souvent associés aux noms propres) catégorisables dans des classes telles que noms de personnes, noms d'organisations ou d'entreprises, noms de lieux, quantités, distances, valeurs, dates, etc.
Les typologies et les jeux d'étiquettes sont dépendents de chaque outil.

Dans cet atelier nous allons expérimenter et comparer trois outils de NER :

1. [Stanza](https://stanfordnlp.github.io/stanza/index.html)
2. [spaCy](https://spacy.io)
3. [Perdido](https://github.com/ludovicmoncla/perdido)

%% Cell type:markdown id: tags:

### 4.1 Stanza NER

`Stanza` est une librairie Python de traitement du langage naturel. Elle contient des outils, qui peuvent être utilisés dans une chaîne de traitement, pour convertir du texte en listes de phrases et de mots, pour générer les formes de base de ces mots, leurs parties du discours et leurs caractéristiques morphologiques, pour produire une analyse syntaxique de dépendance, et pour reconnaître les entités nommées.

`Stanza` se base sur des modèles entrainés par des réseaux de neurones à partir de la bibliothèque [PyTorch](https://pytorch.org) et permet de traiter plus de 70 langues.

Dans cette partie nous allons voir comment utiliser `Stanza` pour la reconnaissance d'entités nommées à partir de textes en français.

%% Cell type:markdown id: tags:

* Importer la librairie `Stanza` :

%% Cell type:code id: tags:

``` python
import stanza
```

%% Cell type:markdown id: tags:

* Télécharger le modèle pré-entrainé pour le français :

%% Cell type:code id: tags:

``` python
stanza.download('fr')
```

%% Output


    2022-09-29 08:23:00 INFO: Downloading default packages for language: fr (French)...
    2022-09-29 08:23:01 INFO: File exists: /Users/lmoncla/stanza_resources/fr/default.zip.
    2022-09-29 08:23:05 INFO: Finished downloading models and saved to /Users/lmoncla/stanza_resources.

%% Cell type:markdown id: tags:

* Instancier et paramétrer la chaîne de traitement :

%% Cell type:code id: tags:

``` python
stanza_parser = stanza.Pipeline(lang='fr', processors='tokenize,ner')
```

%% Output

    2022-09-29 08:23:58 WARNING: Language fr package default expects mwt, which has been added
    2022-09-29 08:23:58 INFO: Loading these models for language: fr (French):
    =======================
    | Processor | Package |
    -----------------------
    | tokenize  | gsd     |
    | mwt       | gsd     |
    | ner       | wikiner |
    =======================
    
    2022-09-29 08:23:58 INFO: Use device: cpu
    2022-09-29 08:23:58 INFO: Loading: tokenize
    2022-09-29 08:23:58 INFO: Loading: mwt
    2022-09-29 08:23:58 INFO: Loading: ner
    2022-09-29 08:23:59 INFO: Done loading processors!

%% Cell type:markdown id: tags:

* On utilise la variable `arques` qui contient le texte chargé précédemment à partir du fichier txt

%% Cell type:code id: tags:

``` python
print(arques)
```

%% Output

    * ARQUES, (Géog.) petite ville de France, en Normandie, au pays de Caux, sur la petite riviere d'Arques. Long. 18. 50. lat. 49. 54.

%% Cell type:markdown id: tags:

* Executer la reconnaissance d'entités nommées :

%% Cell type:code id: tags:

``` python
arques_stanza = stanza_parser(arques)
```

%% Cell type:markdown id: tags:

* Afficher la liste des entités nommées repérées. Avec Stanza, le résultat de l'analyse est un itérateur:

%% Cell type:code id: tags:

``` python
# On définit une fonction qui prend en paramètre le retour du traitement par Stanza, qui parcourt et affiche la liste des entités et leur type
def show_ents(stanza_output):
    for ent in stanza_output.ents:
        print(ent.text, ent.type)
```

%% Cell type:code id: tags:

``` python
# On utilise la fonction précédente pour afficher la liste des entités repérées
show_ents(arques_stanza)
```

%% Output

    ARQUES LOC
    France LOC
    Normandie LOC
    pays de Caux LOC
    Arques LOC

%% Cell type:markdown id: tags:

### 4.2 SpaCy NER


`spaCy` est également une librairie Python de traitement du langage naturel.
Elle se compose de modèles pré-entrainés et supporte actuellement la tokenisation et l'entrainement pour plus de 60 langues. Elle est dotée de modèles de réseaux de neuronnes pour l'étiquettage, l'analyse syntaxique, la reconnaissance d'entités nommées, la classification de textes, l'apprentissage multi-tâches avec des transformateurs pré-entraînés comme BERT, ainsi qu'un système d'entraînement prêt pour la production et un déploiement simple des modèles. `spaCy` est un logiciel commercial, publié en open-source sous la licence MIT.

Dans cette partie nous allons voir comment utiliser `spaCy` pour la reconnaissance d'entités nommées toujours à partir de notre exemple en français.

%% Cell type:markdown id: tags:

* Installer le modèle français pré-entrainé de `spaCy` :

%% Cell type:code id: tags:

``` python
!python -m spacy download fr_core_news_sm
```

%% Output

    Collecting fr-core-news-sm==3.3.0
      Downloading https://github.com/explosion/spacy-models/releases/download/fr_core_news_sm-3.3.0/fr_core_news_sm-3.3.0-py3-none-any.whl (16.3 MB)
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.3/16.3 MB 10.8 MB/s eta 0:00:0000:0100:01
    [?25hRequirement already satisfied: spacy<3.4.0,>=3.3.0.dev0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from fr-core-news-sm==3.3.0) (3.3.1)
    Requirement already satisfied: requests<3.0.0,>=2.13.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2.28.1)
    Requirement already satisfied: spacy-legacy<3.1.0,>=3.0.9 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (3.0.10)
    Requirement already satisfied: packaging>=20.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (21.3)
    Requirement already satisfied: cymem<2.1.0,>=2.0.2 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2.0.6)
    Requirement already satisfied: jinja2 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (3.1.2)
    Requirement already satisfied: srsly<3.0.0,>=2.4.3 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2.4.4)
    Requirement already satisfied: murmurhash<1.1.0,>=0.28.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (1.0.8)
    Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (4.64.1)
    Requirement already satisfied: pathy>=0.3.5 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (0.6.2)
    Requirement already satisfied: thinc<8.1.0,>=8.0.14 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (8.0.17)
    Requirement already satisfied: langcodes<4.0.0,>=3.2.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (3.3.0)
    Requirement already satisfied: blis<0.8.0,>=0.4.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (0.7.8)
    Requirement already satisfied: preshed<3.1.0,>=3.0.2 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (3.0.7)
    Requirement already satisfied: wasabi<1.1.0,>=0.9.1 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (0.10.1)
    Requirement already satisfied: pydantic!=1.8,!=1.8.1,<1.9.0,>=1.7.4 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (1.8.2)
    Requirement already satisfied: setuptools in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (65.3.0)
    Requirement already satisfied: catalogue<2.1.0,>=2.0.6 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2.0.8)
    Requirement already satisfied: spacy-loggers<2.0.0,>=1.0.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (1.0.3)
    Requirement already satisfied: numpy>=1.15.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (1.23.3)
    Requirement already satisfied: typer<0.5.0,>=0.3.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (0.4.2)
    Requirement already satisfied: pyparsing!=3.0.5,>=2.0.2 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from packaging>=20.0->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (3.0.9)
    Requirement already satisfied: smart-open<6.0.0,>=5.2.1 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from pathy>=0.3.5->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (5.2.1)
    Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from pydantic!=1.8,!=1.8.1,<1.9.0,>=1.7.4->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (4.3.0)
    Requirement already satisfied: charset-normalizer<3,>=2 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2.1.1)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2022.9.14)
    Requirement already satisfied: idna<4,>=2.5 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (3.4)
    Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from requests<3.0.0,>=2.13.0->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (1.26.12)
    Requirement already satisfied: click<9.0.0,>=7.1.1 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from typer<0.5.0,>=0.3.0->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (8.1.3)
    Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39/lib/python3.9/site-packages (from jinja2->spacy<3.4.0,>=3.3.0.dev0->fr-core-news-sm==3.3.0) (2.1.1)
    ✔ Download and installation successful
    You can now load the package via spacy.load('fr_core_news_sm')

%% Cell type:markdown id: tags:

* Importer la librarie `spaCy` :

%% Cell type:code id: tags:

``` python
import spacy
```

%% Cell type:markdown id: tags:

* Charger le modèle français pré-entrainé de `spaCy`

%% Cell type:code id: tags:

``` python
spacy_parser = spacy.load('fr_core_news_sm')
```

%% Cell type:markdown id: tags:

* Executer la reconnaissance d'entités nommées :

%% Cell type:code id: tags:

``` python
arques_spacy = spacy_parser(arques)
```

%% Cell type:markdown id: tags:

* Afficher la liste des entités nommées repérées. Les sorties de SpaCy sont dans un format similaire à celui de Stanza mais les étiquettes sont portées par l'attribut `label_` et pas `type`:

%% Cell type:code id: tags:

``` python
for ent in arques_spacy.ents:
    print(ent.text, ent.label_)
```

%% Output

    ARQUES LOC
    Géog LOC
    de France LOC
    Normandie LOC
    pays de Caux LOC
    Arques LOC
    Long LOC
    lat LOC

%% Cell type:markdown id: tags:

* `spaCy` fournit également une fonction pour effectuer un rendu plus graphique des annotations avec `displaCy` :

%% Cell type:code id: tags:

``` python
displacy.render(arques_spacy, style="ent", jupyter=True)
```

%% Output


%% Cell type:markdown id: tags:

On remarque des différences entre les résultats de Stanza et de spaCy. En particulier spaCy repère trois entités à tord (faux positifs) : `Géog`, `Long` et `lat`.
On remarque des différences entre les résultats de Stanza et de spaCy. En particulier spaCy repère trois entités à tord (faux positifs) : `Géog`, `Long` et `lat`, là où Stanza ne repérait à tord que `Géog)`. Et spaCy ne repère pas la première occurrence `ARQUES` sans doute du au fait que le mot est en majuscule.

%% Cell type:markdown id: tags:

### 4.3 Perdido Geoparser


`Perdido` est une librairie Python pour le geoparsing de texte en français. Le geoparsing se décompose en deux tâches : le geotagging et le geocoding. Le geotagging est similaire à la tâche de reconnaissance des entités nommées avec un focus particulier pour le repérage d'information spatiale. En plus des entités nommées, nous nous intéressons en particuliers aux relations entres ces entités telles que les relations spatiales (distances, topologie, orientation, etc.).
Le geocoding (ou résolution de toponymes) a pour rôle d'attribuer aux entités de lieux des coordonnées géographiques non ambigues.
`Perdido` s'appuie sur une approche hybride principalement construite à base de règles pour la repérage et la classification des entités nommées. La librairie est disponible en 2 versions : une version standard et une version spécialement adaptée pour les articles encyclopédiques.

Dans cette partie nous allons voir comment utiliser `Perdido` pour la reconnaissance d'entités nommées toujours à partir de notre exemple `Arques`.

%% Cell type:markdown id: tags:

* Instancier et paramétrer la chaîne de traitement :

%% Cell type:code id: tags:

``` python
geoparser = Geoparser(version="Encyclopedie")
```

%% Cell type:markdown id: tags:

* Executer la reconnaissance d'entités nommées :

%% Cell type:code id: tags:

``` python
arques_perdido = geoparser(arques)
```

%% Cell type:markdown id: tags:

Perdido effectuant la tâche de geocoding en plus du NER, le temps de traitement est plus long qu'avec Stanza ou spaCy, du fait de l'interrogation de ressources geographiques externes pour chaque nom de lieu repéré.

%% Cell type:markdown id: tags:

* Afficher la liste des entités nommées repérées :

%% Cell type:code id: tags:

``` python
for ent in arques_perdido.named_entities:
    print(ent.text, ent.tag)
```

%% Output

    ARQUES place
    France place
    Normandie place
    Caux place
    Arques place
    Long . 18 . 50 . lat . 49 . 54 . latlong

%% Cell type:markdown id: tags:

* Afficher de manière graphique les entités nommées avec `displaCy` grâce à la méthode de conversion `to_spacy_doc`:

%% Cell type:code id: tags:

``` python
displacy.render(arques_perdido.to_spacy_doc(), style="ent", jupyter=True)
```

%% Output


%% Cell type:markdown id: tags:

* Un rendu similaire mais qui permet de visualiser les entités imbriquées (`style="ent"` -> `style="span"`) :

%% Cell type:code id: tags:

``` python
displacy.render(arques_perdido.to_spacy_doc(), style="span", jupyter=True)
```

%% Output


%% Cell type:markdown id: tags:

Cet exemple permet d'illustrer les différences qu'il peut y avoir entre des outils de NER généraliste et ou un outil de geoparsing. On observe ici que Perdido permet une annotation plus fine grâce aux entités imbriquées (ville de, petite rivière) ainsi que le repérage des coordonnées géographiques. En fonction du besoin le repérage de ces éléments peut etre utile pour les traitements suivants ou les analyses qui s'appuient sur ces résultats.

%% Cell type:markdown id: tags:

### 4.4 Expérimentations et comparaison

%% Cell type:markdown id: tags:

* Charger l'article `Beaufort` (volume 2, numéro 1365) disponible dans le dossier `data` :

%% Cell type:code id: tags:

``` python
beaufort = load_txt('data/volume02-1365.txt')
beaufort = load_txt('data/edda-volume02-1365.txt')

print(beaufort)
```

%% Output

    * Beaufort, (Géog.) ville de Savoie, sur la riviere
    d'Oron. Long. 24. 18. lat. 45. 40.

%% Cell type:markdown id: tags:

* Perdido

%% Cell type:code id: tags:

``` python
beaufort_perdido = geoparser(beaufort)
displacy.render(beaufort_perdido.to_spacy_doc(), style="ent", jupyter=True)
displacy.render(beaufort_perdido.to_spacy_doc(), style="span", jupyter=True)
```

%% Output



%% Cell type:markdown id: tags:

* spaCy

%% Cell type:code id: tags:

``` python
beaufort_spacy = spacy_parser(beaufort)
displacy.render(beaufort_spacy, style="ent", jupyter=True)
```

%% Output


%% Cell type:markdown id: tags:

Le retour à la ligne entre `riviere` et `d'Oron` est due à la largeur de la colonne dans l'œuvre originale.
Ce retour semble perturber spaCy qui ne reconnait pas `Oron` comme une entité nommée.

![CNRS](img/beaufort_originale.png)

Pour vérifier cette hypothèse, modifions le texte en supprimant ce saut de ligne pour voir s'il est possible d'améliorer la reconnaissance.

%% Cell type:code id: tags:

``` python
!conda list
```

%% Output

    # packages in environment at /usr/local/Caskroom/miniforge/base/envs/tdm-geoparsing-py39:
    #
    # Name                    Version                   Build  Channel
    appnope                   0.1.3                    pypi_0    pypi
    argon2-cffi               21.3.0                   pypi_0    pypi
    argon2-cffi-bindings      21.2.0                   pypi_0    pypi
    asttokens                 2.0.8                    pypi_0    pypi
    attrs                     22.1.0                   pypi_0    pypi
    backcall                  0.2.0                    pypi_0    pypi
    beautifulsoup4            4.11.1                   pypi_0    pypi
    bleach                    5.0.1                    pypi_0    pypi
    branca                    0.5.0                    pypi_0    pypi
    brotlipy                  0.7.0           py39h63b48b0_1004    conda-forge
    bzip2                     1.0.8                h0d85af4_4    conda-forge
    ca-certificates           2022.9.24            h033912b_0    conda-forge
    catalogue                 2.0.8            py39h6e9494a_0    conda-forge
    certifi                   2022.9.14                pypi_0    pypi
    cffi                      1.15.1           py39hae9ecf2_0    conda-forge
    charset-normalizer        2.1.1              pyhd8ed1ab_0    conda-forge
    click                     8.1.3            py39h6e9494a_0    conda-forge
    click-plugins             1.1.1                    pypi_0    pypi
    cligj                     0.7.2                    pypi_0    pypi
    colorama                  0.4.5              pyhd8ed1ab_0    conda-forge
    contourpy                 1.0.5                    pypi_0    pypi
    cryptography              37.0.4           py39h9c2a9ce_0    conda-forge
    cycler                    0.11.0                   pypi_0    pypi
    cymem                     2.0.6            py39hfd1d529_3    conda-forge
    cython-blis               0.7.8            py39h15b18c7_0    conda-forge
    dataclasses               0.8                pyhc8e2a94_3    conda-forge
    debugpy                   1.6.3                    pypi_0    pypi
    decorator                 5.1.1                    pypi_0    pypi
    defusedxml                0.7.1                    pypi_0    pypi
    entrypoints               0.4                      pypi_0    pypi
    executing                 1.0.0                    pypi_0    pypi
    fastjsonschema            2.16.2                   pypi_0    pypi
    fiona                     1.8.21                   pypi_0    pypi
    folium                    0.12.1.post1             pypi_0    pypi
    fonttools                 4.37.3                   pypi_0    pypi
    fr-core-news-sm           3.3.0                    pypi_0    pypi
    geojson                   2.5.0                    pypi_0    pypi
    geopandas                 0.11.1                   pypi_0    pypi
    gpxpy                     1.5.0                    pypi_0    pypi
    idna                      3.4                pyhd8ed1ab_0    conda-forge
    importlib-metadata        4.12.0                   pypi_0    pypi
    ipykernel                 6.15.3                   pypi_0    pypi
    ipython                   8.5.0                    pypi_0    pypi
    ipython-genutils          0.2.0                    pypi_0    pypi
    ipywidgets                8.0.2                    pypi_0    pypi
    jedi                      0.18.1                   pypi_0    pypi
    jinja2                    3.1.2              pyhd8ed1ab_1    conda-forge
    joblib                    1.2.0                    pypi_0    pypi
    jsonschema                4.16.0                   pypi_0    pypi
    jupyter                   1.0.0                    pypi_0    pypi
    jupyter-client            7.3.5                    pypi_0    pypi
    jupyter-console           6.4.4                    pypi_0    pypi
    jupyter-core              4.11.1                   pypi_0    pypi
    jupyterlab-pygments       0.2.2                    pypi_0    pypi
    jupyterlab-widgets        3.0.3                    pypi_0    pypi
    kiwisolver                1.4.4                    pypi_0    pypi
    langcodes                 3.3.0              pyhd8ed1ab_0    conda-forge
    libblas                   3.9.0           16_osx64_openblas    conda-forge
    libcblas                  3.9.0           16_osx64_openblas    conda-forge
    libcxx                    14.0.6               hccf4f1f_0    conda-forge
    libffi                    3.4.2                h0d85af4_5    conda-forge
    libgfortran               5.0.0           10_4_0_h97931a8_25    conda-forge
    libgfortran5              11.3.0              h082f757_25    conda-forge
    liblapack                 3.9.0           16_osx64_openblas    conda-forge
    libopenblas               0.3.21          openmp_h429af6e_3    conda-forge
    libsqlite                 3.39.3               ha978bb4_0    conda-forge
    libzlib                   1.2.12               hfd90126_3    conda-forge
    llvm-openmp               14.0.4               ha654fa7_0    conda-forge
    lxml                      4.9.1                    pypi_0    pypi
    markupsafe                2.1.1            py39h63b48b0_1    conda-forge
    matplotlib                3.6.0                    pypi_0    pypi
    matplotlib-inline         0.1.6                    pypi_0    pypi
    mistune                   2.0.4                    pypi_0    pypi
    munch                     2.5.0                    pypi_0    pypi
    murmurhash                1.0.8            py39hd91caee_0    conda-forge
    nbclient                  0.6.8                    pypi_0    pypi
    nbconvert                 7.0.0                    pypi_0    pypi
    nbformat                  5.5.0                    pypi_0    pypi
    ncurses                   6.3                  h96cf925_1    conda-forge
    nest-asyncio              1.5.5                    pypi_0    pypi
    notebook                  6.4.12                   pypi_0    pypi
    numpy                     1.23.3           py39h34843a6_0    conda-forge
    openssl                   1.1.1q               hfe4f2af_0    conda-forge
    packaging                 21.3               pyhd8ed1ab_0    conda-forge
    pandas                    1.5.0                    pypi_0    pypi
    pandocfilters             1.5.0                    pypi_0    pypi
    parso                     0.8.3                    pypi_0    pypi
    pathy                     0.6.2              pyhd8ed1ab_0    conda-forge
    perdido                   0.1.27                   pypi_0    pypi
    pexpect                   4.8.0                    pypi_0    pypi
    pickleshare               0.7.5                    pypi_0    pypi
    pillow                    9.2.0                    pypi_0    pypi
    pip                       22.2.2             pyhd8ed1ab_0    conda-forge
    preshed                   3.0.7            py39hd91caee_0    conda-forge
    prometheus-client         0.14.1                   pypi_0    pypi
    prompt-toolkit            3.0.31                   pypi_0    pypi
    protobuf                  4.21.6                   pypi_0    pypi
    psutil                    5.9.2                    pypi_0    pypi
    ptyprocess                0.7.0                    pypi_0    pypi
    pure-eval                 0.2.2                    pypi_0    pypi
    pycparser                 2.21               pyhd8ed1ab_0    conda-forge
    pydantic                  1.8.2                    pypi_0    pypi
    pygments                  2.13.0                   pypi_0    pypi
    pyopenssl                 22.0.0             pyhd8ed1ab_1    conda-forge
    pyparsing                 3.0.9              pyhd8ed1ab_0    conda-forge
    pyproj                    3.4.0                    pypi_0    pypi
    pyrsistent                0.18.1                   pypi_0    pypi
    pysocks                   1.7.1              pyha2e5f31_6    conda-forge
    python                    3.9.13          h57e37ff_0_cpython    conda-forge
    python-dateutil           2.8.2                    pypi_0    pypi
    python_abi                3.9                      2_cp39    conda-forge
    pytz                      2022.2.1                 pypi_0    pypi
    pyzmq                     24.0.1                   pypi_0    pypi
    qtconsole                 5.3.2                    pypi_0    pypi
    qtpy                      2.2.0                    pypi_0    pypi
    readline                  8.1.2                h3899abd_0    conda-forge
    requests                  2.28.1             pyhd8ed1ab_1    conda-forge
    scikit-learn              1.1.2                    pypi_0    pypi
    scipy                     1.9.1                    pypi_0    pypi
    send2trash                1.8.0                    pypi_0    pypi
    setuptools                65.3.0             pyhd8ed1ab_1    conda-forge
    shapely                   1.8.4                    pypi_0    pypi
    shellingham               1.5.0              pyhd8ed1ab_0    conda-forge
    six                       1.16.0                   pypi_0    pypi
    smart_open                5.2.1              pyhd8ed1ab_0    conda-forge
    soupsieve                 2.3.2.post1              pypi_0    pypi
    spacy                     3.3.1                    pypi_0    pypi
    spacy-legacy              3.0.10             pyhd8ed1ab_0    conda-forge
    spacy-loggers             1.0.3              pyhd8ed1ab_0    conda-forge
    sqlite                    3.39.3               h9ae0607_0    conda-forge
    srsly                     2.4.4            py39hd408605_0    conda-forge
    stack-data                0.5.0                    pypi_0    pypi
    stanza                    1.2.3                    pypi_0    pypi
    terminado                 0.15.0                   pypi_0    pypi
    thinc                     8.0.17                   pypi_0    pypi
    threadpoolctl             3.1.0                    pypi_0    pypi
    tinycss2                  1.1.1                    pypi_0    pypi
    tk                        8.6.12               h5dbffcc_0    conda-forge
    torch                     1.12.1                   pypi_0    pypi
    tornado                   6.2                      pypi_0    pypi
    tqdm                      4.64.1             pyhd8ed1ab_0    conda-forge
    traitlets                 5.4.0                    pypi_0    pypi
    typer                     0.4.2              pyhd8ed1ab_0    conda-forge
    typing-extensions         4.3.0                hd8ed1ab_0    conda-forge
    typing_extensions         4.3.0              pyha770c72_0    conda-forge
    tzdata                    2022c                h191b570_0    conda-forge
    urllib3                   1.26.12                  pypi_0    pypi
    wasabi                    0.10.1                   pypi_0    pypi
    wcwidth                   0.2.5                    pypi_0    pypi
    webencodings              0.5.1                    pypi_0    pypi
    wheel                     0.37.1             pyhd8ed1ab_0    conda-forge
    widgetsnbextension        4.0.3                    pypi_0    pypi
    xz                        5.2.6                h775f41a_0    conda-forge
    zipp                      3.8.1                    pypi_0    pypi
Dans cet exemple, `spaCy` repère le mot `Oron` comme une entité de personne alors que `Perdido` le repère comme un lieu.
On observe qu'il manque l'accent au mot «rivière». Corrigeons le texte pour voir s'il est possible d'améliorer la reconnaissance.

%% Cell type:code id: tags:

``` python
normalized_beaufort = beaufort.replace('\n', '')
normalized_beaufort = beaufort.replace('riviere', 'rivière')

normalized_beaufort_spacy = spacy_parser(normalized_beaufort)

displacy.render(normalized_beaufort_spacy, style="ent", jupyter=True)
```

%% Output
%% Cell type:markdown id: tags:


%% Cell type:markdown id: tags:
Ce changement ne corrige pas l'erreur d'annotation, au contraire l'entité n'est même plus repérée. Cependant, on observe également un saut de ligne entre les mots «rivière» et «d'Oron».
Ce retour à la ligne est due à la largeur de la colonne dans l'œuvre originale.

Apparemment ça n'améliore rien, mais il manque encore l'accent à «rivière».

![CNRS](img/beaufort_originale.png)


Pour vérifier l'hypothèse que ce retour perturbe le repérage par `spaCy`, corrigeons une nouvelle fois le texte.

%% Cell type:code id: tags:

``` python
normalized_beaufort = normalized_beaufort.replace('riviere', 'rivière')
normalized_beaufort = normalized_beaufort.replace('\n', '')

normalized_beaufort_spacy = spacy_parser(normalized_beaufort)

displacy.render(normalized_beaufort_spacy, style="ent", jupyter=True)
```

%% Cell type:markdown id: tags:

Cette fois l'entité étendue incluant le nom commun «rivière» a été reconnu par SpaCy, qui a pu ainsi corriger le type de l'entité nommée et se rendre compte que l'Oron était un endroit et pas une personne.
Cette fois l'entité étendue incluant le nom commun «rivière» a été reconnu par `spaCy`, qui a pu ainsi corriger le type de l'entité nommée et se rendre compte que l'Oron était un lieu et pas une personne.

Essayons maintenant avec Stanza.
Essayons maintenant avec `Stanza`.

%% Cell type:markdown id: tags:

- Stanza

%% Cell type:code id: tags:

``` python
beaufort_stanza = stanza_parser(beaufort)
show_ents(beaufort_stanza)
```

%% Cell type:markdown id: tags:

Stanza a directement repéré que l'Oron était un lieu mais veut, comme SpaCy, annoter «Géog» qui ne devrait pas l'être.

Regardons maintenant ce que l'on dit sur la même ville de Beaufort un peu plus d'un siècle plus tard, fin XIXème siecle, dans [La Grande Encyclopédie](https://www.collexpersee.eu/projet/disco-lge/) (LGE).

%% Cell type:code id: tags:

``` python
lge_beaufort = load('data/beaufort.txt')
lge_beaufort = load_txt('data/lge-beaufort.txt')
print(lge_beaufort)
```

%% Cell type:markdown id: tags:

Cette fois l'article est un peu plus long et comporte des césures de lignes importantes, définissons donc une fonction pour recoller les morceaux:
Cette fois l'article est un peu plus long et comporte des césures de lignes importantes, définissons donc une fonction pour recoller les morceaux :

%% Cell type:code id: tags:

``` python
def join_lines(s):
    return s.replace('¬\n', '').replace('-\n', '').replace('\n', ' ')
```

%% Cell type:code id: tags:

``` python
lge_beaufort_perdido = geoparser(join_lines(lge_beaufort))
normalized_lge_beaufort = join_lines(lge_beaufort)
normalized_lge_beaufort
```

%% Cell type:markdown id: tags:

* Perdido

%% Cell type:code id: tags:

``` python
lge_beaufort_perdido = geoparser(normalized_lge_beaufort)
displacy.render(lge_beaufort_perdido.to_spacy_doc(), style="span", jupyter=True)
```

%% Cell type:code id: tags:
%% Cell type:markdown id: tags:

``` python
lge_beaufort_spacy = spacy_parser(join_lines(lge_beaufort))
```
* spaCy

%% Cell type:code id: tags:

``` python
lge_beaufort_spacy = spacy_parser(normalized_lge_beaufort)
displacy.render(lge_beaufort_spacy, style="ent", jupyter=True)
```

%% Cell type:markdown id: tags:

* Stanza

%% Cell type:code id: tags:

``` python
lge_beaufort_stanza = stanza_parser(lge_beaufort)
lge_beaufort_stanza = stanza_parser(normalized_lge_beaufort)
show_ents(lge_beaufort_stanza)
```

%% Cell type:markdown id: tags:

L'analyse prend plus de temps avec Stanza mais les résultats ont l'air un peu plus précis sur cet exemple. Il y a également une meilleure couverture: Henri IV et 1841 sont annotés, comme avec Perdido, jusqu'à Saint-Maxime-de-Bf.aufort qui a été identifié malgré l'erreur d'OCR, bien que mal classé.
Quelques observations :
1. Seul Perdido repère la date (1841).
2. spaCy ne classe pas correctement Albertville (Personne) contrairement à Perdido et Stanza (Lieu), spaCy ne repère pas l'entité Heni IV contrairement à Perdido et Stanza.
3. Stanza repère et classe correctement l'entité "Saint-Maximede-Bf.aufort", Perdido la repère mais ne sait pas la classer et spaCy ne la repère pas.

%% Cell type:markdown id: tags:

## 6. Geoparsing / Geocoding

En complément de la tâche de reconnaissance des entités nommées la librairie `Perdido` propose également celle de résolution des toponymes, on parle alors de *Geoparsing*. Cette tâche consiste a associer à un nom de lieu des coordonnées géographiques non ambigus. De manière classique elle s'appuie sur le repérage des entités spatiales identifées lors de la reconnaissance des entités nommées et fait appel à des ressources externes de type *gazetier* (ou dictionnaires topographique) pour localiser les lieux.

%% Cell type:markdown id: tags:

### 6.1 Perdido Geoparser

%% Cell type:markdown id: tags:

* Revenons à l'article `ARQUES`

%% Cell type:code id: tags:

``` python
print(arques)
displacy.render(arques_perdido.to_spacy_doc(), style="ent", jupyter=True)
```

%% Cell type:markdown id: tags:

* En plus de pouvoir afficher la liste des entités nommées comme nous l'avons fait précédemmment, nous pouvons directement afficher la carte des lieux localisés

%% Cell type:code id: tags:

``` python
# afficher la carte des lieux localisés
arques_perdido.get_folium_map()
```

%% Cell type:code id: tags:

``` python
displacy.render(doc.to_spacy_doc(), style="ent", jupyter=True)
```

%% Cell type:markdown id: tags:

Par défaut, lors de l'instanciation du `Geoparser()`, seul [OpenStreetMap](https://www.openstreetmap.org/) est utilisé pour le geocoding et au maximum un résultat est retourné pour chaque lieu (nous verrons dans la suite comment paramétrer le geocoding).

On a déjà ici un aperçu de la difficulté de la tâche de résolution des toponymes. En effet, un grand nombre d'ambiguïtés existent tels que plusieurs lieux ayant le même nom, plusieurs noms pour un même lieu ou encore le fait qu'un lieu ne soit pas référencé dans les ressources que l'on interroge.

%% Cell type:markdown id: tags:

### 6.2 Perdido Geocoder

En complément du `Geoparser` qui prend en paramètre un texte et qui fait la reconnaissance d'entités nommées en amont de l'étape de geocoding, `Perdido`propose également une fonction de geocoding disctincte prenant en paramètre directement un nom de lieu (ou une liste de noms de lieux).
En complément du `Geoparser` qui prend en paramètre un texte et qui fait la reconnaissance d'entités nommées en amont de l'étape de geocoding, `Perdido` propose également une fonction de geocoding disctincte prenant en paramètre directement un nom de lieu (ou une liste de noms de lieux).

%% Cell type:code id: tags:

``` python
geocoder = Geocoder()
doc = geocoder(['Arques', 'France', 'Normandie', 'Caux'])

# afficher la carte des lieux localisés
doc.get_folium_map()
```

%% Cell type:markdown id: tags:

### 6.2 Résolution de toponymes / désambiguïsation


#### 6.2.1 Exemple : Arques

* Cherchons à localiser la ville `Arques`

%% Cell type:code id: tags:

``` python
geocoder = Geocoder()
doc = geocoder('Arques')
doc.get_folium_map()
```

%% Cell type:markdown id: tags:

On remarque que par défaut, la localisation retournée pour le nom de lieu `Arques` n'est pas celle que l'on recherche. En effet, le texte indique qu'il s'agit d'une ville de Normandie hors ici la localisation proposée est située dans le Pas-de-Calais !
On remarque que par défaut, la localisation retournée pour le nom de lieu `Arques` n'est pas celle que l'on recherche. En effet, le texte indique qu'il s'agit d'une ville de Normandie, or ici la localisation proposée est située dans le Pas-de-Calais !

Changeons les paramètres du `Geocoder` (ces paramètres sont similaires pour le `Geoparser`) pour essayer de retrouver la bonne localisation.

* Augmenter le nombre de résultats retournés par les gazetiers interrogés

%% Cell type:code id: tags:

``` python
geocoder = Geocoder(max_rows=10)
doc = geocoder('Arques')
doc.get_folium_map()
```

%% Cell type:markdown id: tags:

On observe parmi les 10 localisations retournées par OpenStreetMap (gazetier par défaut) qu'aucune ne se situe en Normandie.

* Remplacer OpenStreetMap par l'IGN

%% Cell type:code id: tags:

``` python
geocoder = Geocoder(sources=['ign'])
doc = geocoder('Arques')
doc.get_folium_map()
```

%% Cell type:markdown id: tags:

On observe que le premier résultat retourné par l'IGN ne se situe ni en Normandie (comme attendu), ni dans le Pas-de-Calais comme le premier résultat retourné par OpenStreetMap.

* Augmenter le nombre de résultats retournés par l'IGN

%% Cell type:code id: tags:

``` python
geocoder = Geocoder(sources=['ign'], max_rows=10)
doc = geocoder('Arques')
doc.get_folium_map()
```

%% Cell type:markdown id: tags:

Cette fois-ci on retrouve bien une localisation en Normandie au sud de Dieppe avec pour nom `Arques-la-Bataille'. On peut faire l'hypotèse que le nom a évolué car cette localisation se situe bien dans le Pays de Caux (voir illustration ci-dessous, source [Wikipedia](https://fr.wikipedia.org/wiki/Pays_de_Caux)) comme l'indique le texte de l'article.

![Pays de Caux](https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Carte_pays_Caux1.png/497px-Carte_pays_Caux1.png)

%% Cell type:markdown id: tags:

Il reste néanmoins le problème de retrouver cette localisation de manière automatique.
Plusieurs approches existent dans la littérature mais ne sont pas encore implémentées dans `Perdido`.

Cet exemple illustre bien la difficulté de la problématique de désambiguïsation des toponymes avec notamment la gestion des natures de lieux différentes (pays, régions, communes, lieux-dits, lac, rivières, etc.) associés à un même nom, l'homonymie, la non exaustivité des ressources, l'évolution des noms au cours du temps ou encore les erreurs d'orthographe.

%% Cell type:markdown id: tags:

* Afficher la carte obtenue après le geoparsing avec l'IGN et 10 résultats max par nom de lieu

%% Cell type:code id: tags:

``` python
geoparser = Geoparser(sources=['ign'], max_rows=10)
doc = geoparser(content)
doc = geoparser(arques)
doc.get_folium_map()
```

%% Cell type:markdown id: tags:


### 6.3 Le cas des descriptions de randonnées

Prenons maintenant l'exemple du geoparsing de descriptions de randonnées. Certaines solutions de désambiguisation ont pu être développées et intégrées au sein de la librairie `Perdido` (d'autres sont en cours d'intégration). Les solutions décrites dans la suite de cette partie ont été développées dans le cadre des projets [Perdido](http://erig.univ-pau.fr/PERDIDO/) (2012-2015) et [ANR CHOUCAS](http://choucas.ign.fr) (2017-2022).

> Ludovic Moncla, Walter Renteria-Agualimpia, Javier Nogueras-Iso and Mauro Gaio (2014). "Geocoding for texts with fine-grain toponyms: an experiment on a geoparsed hiking descriptions corpus". In Proceedings of the 22nd ACM SIGSPATIAL International Conference on Advances in Geographic Information Systems, pp 183-192.

> Mauro Gaio and Ludovic Moncla (2019). “Geoparsing and geocoding places in a dynamic space context.“ In The Semantics of Dynamic Space in French: Descriptive, experimental and formal studies on motion expression, 66, 353.


Nous avons choisi un exemple pour illustrer les différentes phases du processus que nous avons mis en place dans le cadre du geoparsing de descriptions de randonnées :
1. filtrer les résultats en fonction du pays
2. filtrer les résultats en fonction d'une zone géographique définie
3. regrouper les résultats en utilisant un algorithme de clustering spatial (DBSCAN, *density-based spatial clustering of applications with noise*)
4. selectionner le cluster qui contient le plus d'entités distinctes

La librairie Perdido utilise la méthode DBSCAN implémentée dans la librairie [Scikit-Learn](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html).
Cette stratégie est adaptée pour une description d'itinéraire où les différents lieux cités sont supposés être localisés à proximité les uns des autres.

%% Cell type:markdown id: tags:

* Charger le jeu de données CHOUCAS de descriptions de randonnées fourni par `Perdido`

%% Cell type:code id: tags:

``` python
dataset_choucas = load_choucas_perdido()
data_choucas = dataset_choucas['data']


data_choucas.to_dataframe().head()
```

%% Cell type:code id: tags:

``` python
len(data_choucas)
```

%% Cell type:markdown id: tags:

* Sélectionner une randonnée (parmi les 30)

%% Cell type:code id: tags:

``` python
id_rando = 2
doc = data_choucas[id_rando]
```

%% Cell type:code id: tags:

``` python
doc.text
```

%% Cell type:code id: tags:

``` python
displacy.render(doc.to_spacy_doc(), style="ent", jupyter=True)
```

%% Cell type:code id: tags:

``` python
doc.get_folium_map()
```

%% Cell type:markdown id: tags:

On observe ici le résultat déjà pré-traité par `Perdido`. Nous allons maintenant illustrer le processus de désambiguïsation.

%% Cell type:markdown id: tags:

On recommence le processus de geoparsing en entier à partir du texte de la randonnées choisie.

%% Cell type:code id: tags:

``` python
geoparser = Geoparser()
doc_geoparsed = geoparser(doc.text)
```

%% Cell type:code id: tags:

``` python
doc_geoparsed.get_folium_map()
```

%% Cell type:markdown id: tags:

On voit clairement la différence par rapport au résultat précédent. Nous allons alors essayer de retrouver le même résultat en déroulant les différentes étapes pour désambiguïser avec `Perdido`.

Pour gagner un peu de temps lors des prochaines executions nous allons faire directement appel à la fonction de geocoding à partir de la liste des noms de lieux.

* Récuperer la liste des noms de lieux (sans doublon)

%% Cell type:code id: tags:

``` python
places_list = list(set([ent.text for ent in doc_geoparsed.ne_place]))
print(places_list)
```

%% Cell type:markdown id: tags:

#### 6.3.1 Ajout d'un filtre "code pays"

%% Cell type:code id: tags:

``` python
# instancier le geocoder avec le code pays
geocoder = Geocoder(country_code = 'fr')
doc_geocoded = geocoder(places_list)

# ajouter la trace GPS
doc_geocoded.geometry_layer = doc.geometry_layer

doc_geocoded.get_folium_map()
```

%% Cell type:markdown id: tags:

#### 6.3.2 Ajout d'un filtre "bounding box"

%% Cell type:code id: tags:

``` python
bbox = [5.62216508714297, 45.051683489057, 7.18563279407213, 45.9384576816403] # zone d'intervention du PGHM Isère

# instancier le geocoder avec le code pays et une bounding box
geocoder = Geocoder(country_code = 'fr', bbox = bbox)
doc_geocoded = geocoder(places_list)

# ajouter la trace GPS
doc_geocoded.geometry_layer = doc.geometry_layer

# affiche la carte
doc_geocoded.get_folium_map()
```

%% Cell type:markdown id: tags:

#### 6.3.3 Clustering par densité spatiale

%% Cell type:code id: tags:

``` python
# appliquer la désambiguïsation
doc_geocoded.cluster_disambiguation()
doc_geocoded.get_folium_map()
```

%% Cell type:code id: tags:

``` python
```

%% Cell type:code id: tags:

``` python
```

%% Cell type:code id: tags:

``` python
```

%% Cell type:markdown id: tags:
Utilisation du contexte (autres entités nommées repérées dans le texte, relations spatiales, etc...). Développées dans le cadre du projet [Perdido]() (add ref 2014 et 2016) mais pas encore intégré à la librairie Python Perdido. Cette librairie est toujours en cours de développement et d'amélioration. Vos remarques et retours seront les bienvenues.