Programmation pour le TAL
Lectures
Les deux articles ci-dessous traitent d'analyse distributionnelle effectuée avec des modèles fréquentiels.
Il vous est demandé de lire essentiellement ce qui concerne l'extraction des relations de dépendances syntaxiques
et leur "normalisation" :
Étape 1
- Importez dans un projet Eclipse les sources Java syntacticDeps.jar (annexe C du document sur Eclipse) sans créer de nouveau package : cliquez seulement, dans le package explorer, sur le répertoire "src" de votre projet avant l'import
- Importez dans ce projet l'archive talismaneReader.jar (annexe D du document sur Eclipse) dont les sources ne sont pas fournies. Cliquez, dans le package explorer, sur le nom de votre projet avant l'import
- Observez les classes Token, Sentence, SyntacticDependency et IndirectDependency
- Créez une classe DependenciesBuilder avec une méthode main()
- Importez la classe TalismaneReader : ajoutez avant le début de définition de la classe (Class DependenciesBuilder) la ligne suivante :
import sajous.litl.readers.*;
- Créez une méthode lookForUnknownLemmas() qui cherche les tokens dont le lemme est inconnu de l'analyseur syntaxique
et affiche leurs formes de surface et leurs catégories syntaxiques
- Créez une méthode lookForNmodAdj() qui repère dans la phrase les triplets NC <-- mod -- Adj et les affiche (e.g. <NC:méthode; mod; Adj:efficace>,
<NC:système; mod; Adj:complexe>, etc.)
- Le code de la méthode main pourrait être le suivant :
TalismaneReader reader = new TalismaneReader();
reader.readSentence("/chemin/TAL_Samples/phrase1.tal");
// affichage de la phrase
Sentence sent = reader.getSentence();
sent.display();
// affichage des triplets NC-mod-Adj
DependenciesBuilder depBuilder = new DependenciesBuilder ();
depBuilder.setSentence (sent);
depBuilder.lookForNmodAdj ();
- Échantillons de phrases "talismanisées" extraites du corpus TALN : TAL_Samples.tar.bz2
- Aide pour la réalisation de l'étape 1
Étape 2 : traitement des fichiers comportant plusieurs phrases
Étape 3 : autres triplets
- Sur la base de votre méthode qui repère les structures N-mod-Adj,
ajoutez des méthodes qui repèrent les triplets V-suj-N et V-obj-N
- Ajoutez des méthodes qui repèrent les relations d'attribut du sujet ("l'exercice paraît difficile" → <NC:exerice; ATS;Adj:difficile>) et les relations de coordination ("l'exercice et la solution" → <NC:exerice; coord/et; NC:solution>).
Attention, ces relations reposent sur deux dépendances CoNLL données par Talismane
Note :
vous verrez que vous allez produire des portions de code récurrentes : pensez à factoriser !
Par exemple à chaque fois que vous voulez accéder au token qui gouverne un token donné,
pas la peine de vous poser la question à chaque fois "est-ce que je dois ajouter ou retrancher un à l'index de la phrase
pour avoir l'indice dans le tableau ?" : écrivez une méthode une fois pour toute.
De la même manière, évitez, à chaque fois que vous voulez tester si un token est un verbe,
d'écrire if (token.startsWith "c'est quoi déjà les étiquettes pour les verbes dans Talismane ?"),
mais écrivez une méthode public boolean isVerb () qui renvoie true si un token est un verbe et faux sinon.
Etc.
Étape 4 : stockage des données dans des listes
Jusqu'ici, vous affichiez dans la console les relations de dépendance que vous trouviez
au fur et à mesure. À partir de maintenant, il va falloir se donner les moyens de pouvoir manipuler
toutes les relations de dépendance d'une phrase, notamment pour la "distribution des relations"
(cf. articles de la section "lecture" ci-dessus et page objectif du projet).
Par exemple, dans la phrase "les puces et les patrons ponctuationnels constituent les marqueurs [...]",
vous n'aurez pas de mal à repérer le triplet <V:constituer; suj; NC:puce>.
Depuis l'étape 3; vous repérez également le triplet <NC:puce; coord/et; NC:patrons>.
Mais il faudra également repérer le triplet <V:constituer; suj; NC:patrons>.
Pour cela, vous allez :
- stocker la liste des dépendances
- dans la classe DependenciesBuilder, ajoutez un membre dependencies de type ArrayList<SyntacticDependencies>
- pour chaque triplet syntaxique que vous repérez, plutôt que de l'écrire dans la console,
vous allez créer un objet de type SyntacticDependency et l'ajouter à la liste dependencies
- une fois que vous aurez travaillé sur l'ensemble de vos relations, vous pourrez parcourir la liste des dépendances
et les afficher dans la console (à terme : dans un fichier)
- vous vous en rendrez compte : à chaque nouvelle phrase, il faudra vider la liste !

Note : dans ce diagramme, la représentation de la relation d'agrégation
est redondante avec la mention du membre dependencies dans la classe
DependenciesBuilder. En toute rigueur, il faudrait choisir de représenter l'une ou l'autre.
- chercher et stocker la liste des coordonnés
- à l'étape 3, vous avez repéré les relations de coordination.
Il faudra stocker ces relations, comme les autres, dans la liste dependencies de DependenciesBuilder
- en plus de cela, vous ajouterez dans la classe Token
un membre coordinates de type ArrayList<Token> destiné à stocker,
pour un token donné, l'ensemble des tokens qui lui sont coordonnés
- à chaque fois que vous trouverez une relation de coordination entre les tokens t1 et t2,
vous ajouterez t2 dans la liste des coordonnés de t1

Note : dans ce diagramme, seule la relation d'agrégation
réflexive sur Token est représentée :
on n'a pas de détail sur l'implémentation, en Java, de cette relation au sein de la classe
(e.g. tableau statique, liste, ou autre).
Ça n'est pas nécessaire pour réfléchir à l'architecture globale du système.
Ça n'est pas nécessaire non plus pour l'utilisateur de la classe.
Pour le concepteur de la classe Token en revanche,
cela peut avoir un intérêt.
Le diagramme ci-dessous (qui comporte des redondances) récapitule les deux précédents.
Aide : représentation sous forme de diagrammes d'objets
des structures de données lors du déroulement de la méthode lookForCoords().
Étape 5 : distribution des relations
Une fois que vous avez repéré et stocké les relations de dépendance syntaxique et que vous avez repéré, pour chaque token,
l'ensemble de ses coordonnés, vous pouvez vous lancer dans la "distribution des relations".
Ne cherchez pas à tout faire en même temps !&nbso;:
- Parcourez la liste des triplets (relations de dépendance que vous avez construites)
- Pour chaque triplet, parcourez la liste des coordonnés du gouverneur (i.e. la liste des tokens en relation de coordination avec le gouverneur)
- Pour chaque triplet, parcourez la liste des coordonnés du dépendant
- Quand il y a lieu, créez de nouvelles relations de dépendance et ajoutez-les à la liste initiale
C'est une bonne idée de s'aider de diagrammes d'objets :
pour une relation donnée et une paire de token coordonnés donnée,
représentez vos objets au stade initial, après repérage des coordonnés,
puis après distribution de la relation.
Aide : description des étapes et illustration.
Étape 6 : liste de fréquences des relations de dépendances
Jusqu'ici, vous écriviez dans la console les triplets extraits au fur et à mesure,
ou à la fin de l'extraction de toutes les dépendances d'une phrase.
C'est ce qu'il vous faut continuer à faire pour produire les sorties attendues dans
le format 1 décrit dans la page objectif du projet.
Pour obtenir le format 2, i.e. une table de fréquence des triplets pour l'ensemble du corpus,
il faut procéder autrement. En effet, si vous traitez un corpus très volumineux,
vous ne pouvez pas stocker l'ensemble des triplets (des objets Java les représentant)
sous peine de saturer la mémoire. Une solution peut être de produire une sortie au format 1
(c'est-à-dire un fichier potentiellement très volumineux)
et d'utiliser les outils Unix en ligne de commande pour générer une table de fréquence (sort |uniq -c|sort).
Mais nous allons réaliser l'ensemble des opérations en Java en utilisant un tableau associatif
(cf. cours correspondant) dont les clés seront les représentations textuelles
(String) des triplets et les valeurs les fréquences (Integer) de ces triplets.
- Pour chaque phrase, extraire les triplets et distribuer les relations sur les tokens coordonnés
- Pour chaque relation extraite, une instance de SyntacticDependency
est ajoutée au tableau dependencies
→ pour chaque phrase, à la fin de l'étape 5, l'ensemble des triplets extraits pour cette phrase
est contenu dans le tableau dependencies.
- Il faut donc, à la fin de l'étape 5, pour chaque phrase, itérer sur les triplets
référencés dans dependencies et, pour chacun, incrémenter sa fréquence
dans le tableau associatif que vous aurez créé dans DependenciesBuilder
- À la fin du programme, i.e. après avoir extrait les triplets de la dernière phrase de votre corpus,
il faut parcourir le tableau associatif et écrire les fréquences de chaque triplet
- dans la console pour commencer (en utilisant un petit corpus de quelques phrases)
- dans un fichier (cf. étape 7), toujours avec un petit corpus (pour que vous pouissiez vérifier manuellement les résultats)
- puis dans un fichier, avec un corpus plus volumineux
Étape 7 : écriture des résultats dans un fichier
Pour produire les fichiers de triplets,
il « suffit » d'écrire les résultats un fichier au lieu de les écrire dans la console.
Il n'y a rien de plus à implémenter dans les classes Token
ou SyntacticDependency : tout se joue dans DependenciesBuilder.
- Pour l'écriture de la table des fréquences (format 2), tout se fait à la fin :
- à la fin de l'étape 5, ouvrir le fichier de résultats
- parcourir le tableau assossiatif
- pour chaque couple clé/valeur (i.e. pour chaque couple tiplet/fréquence),
écrire ce couple dans une ligne du fichier
- fermer le fichier
- Pour le format 1, il faut écrire dans le fichier les triplets extraits pour chaque phrase.
Dans DependenciesBuilder, il faut donc :
- ouvrir le fichier en début d'exécution
- stocker quelque part une référence au flux ouvert
(pourquoi pas dans un membre de la classe DependenciesBuilder),
à moins que vous ne passiez en argument la référence vers ce flux, depuis votre méthode main(),
à toutes les méthodes qui en ont besoin
- à la fin de l'extraction des triplets de chaque phrase,
itérer sur l'ArrayList dependencies et écrire chaque triplet (sa représentation textuelle)
dans le fichier (dans le flux ouvert en début de programme)
- en fin d'exécution, fermer le fichier
Remarques
- Un programme qui fonctionne n'est pas seulement un programme qui ne plante pas !
Un programme qui fonctionne est un programme qui fait ce qu'on veut qu'il fasse.
Il ne faut pas vous contenter d'observer que votre programme,
appliqué à l'ensemble du corpus, tourne et produit un grand nombre d'affichages dans la console,
même si ces derniers ont l'« air de correspondre à quelque chose ».
Il faut s'assurer, ou se donner toutes les chances, que votre programme extraie seulement ce qu'il doit extraire,
et tout ce qu'il doit extraire. Difficile de le vérifier sur l'ensemble du corpus.
Mais, dès l'étape 1, vous devriez, pour chaque méthode que vous implémentez,
noter quelle relation votre extracteur doit ramener (avant même de lancer l'exécution)
pour une phrase donnée. Vous devriez, pour cela, soit repérer dans le corpus une phrase contenant
la relation à extraire, soit créer une phrase et l'analyser avec Talismane,
puis vérifier qu'en traitant le fichier correspondant, votre extracteur produise bien la sortie attendue.
- Ne présumez pas de la "logique" des sorties d'un outil automatique.
Si, pour extraire, par exemple, la relations "objet",
vous testez que le dépendant est un nom (ou un infinitif) et que l'étiquette de la dépendance est "obj",
ne pensez pas que l'étiquette du gouverneur sera nécessairement un verbe.
Testez aussi le POS du gouverneur : on n'est jamais à l'abri des surprises.
- À l'opposé, ne posez pas des contraintes trop fortes sur les dépendances.
Pour la relation "modifieur", vous pouvez chercher les noms modifiés seulement par des adjectifs.
Mais il peut être intéressant également d'extraire la relation modifieur dans "système optimisé"
comme on extrait "système efficace". Pourtant, le POS de "optimisé" n'est pas ADJ.
De la même manière, on veut pouvoir extraire la relation objet dans "l'analyseur préfère étiqueter" et pas seulement dans
"l'analyseur permet l'étiquetage" (i.e. l'objet n'est pas nécessairement un nom).
Un attribut du sujet n'est pas nécessairement placé avant la copule, etc.
[ Mention légale ]