Sulci

Workshop @fabelier, 15 février 2012

logo-fabelier.png logo-liberation.png

Presenter Notes

Qui sommes-je?

Presenter Notes

Un exemple pour commencer

>>> from sulci.textmining import SemanticalTagger
>>> text = u"""«La Russie et la Chine finiront par regretter leur décision qui
... les a vues s’aligner sur un dictateur en fin de vie et qui les a mises en
... porte-à-faux avec le peuple syrien.»"""
>>> s = SemanticalTagger(text)
>>> s.descriptors
[(<Descriptor: Russie>, 100.0),
(<Descriptor: Chine>, 100.0),
(<Descriptor: diplomatie>, 14.798308089447328),
(<Descriptor: Dmitri Medvedev>, 10.337552742616033)]

En d'autres termes, en entrée, on fournit un texte, en sortie on reçoit des descripteurs, i.e. des mots-clés ou expressions censés décrire le sens du texte.

Presenter Notes

Contexte

Pourquoi ce projet

  • version propriétaire et fermée (Windows) achetée en 2007 et jamais jugée utilisable

  • outil demandé depuis lors par le service qui gère nos archives

  • documentation:
    • catégoriser les archives
    • retrouver les articles des archives
    • construire des dossiers thématiques pour les journalistes
  • L'intérêt d'un tel outil pour eux est d'avoir automatiquement les descripteurs les plus évidents, pour se concentrer sur les plus délicats

  • le projet est né un peu par hasard, comme exercice pédagogique

  • l'objectif est que l'outil soit utilisé aussi par les journalistes cette année

Presenter Notes

Les grandes lignes

  • développé en python
  • s'appuie sur le framework Django (utilisé à Libération)
  • les principaux algorithmes demandent un apprentissage
  • l'apprentissage sémantique est dépendant du corpus utilisé, il faut donc choisir le corpus d'apprentissage en fonction du futur corpus d'utilisation
  • cet apprentissage prend la forme de données SQL ; une db est donc nécessaire pour pouvoir utiliser sulci
  • le code est actuellement utilisé en production à Libération, mais il faut le considérer comme un «prototype opérationnel»

Comment ça se prononce

soule-tchi

Pourquoi ça s'appelle comme ça

Ça vient de la région du Sulcis, au sud de la Sardaigne (Italie). Mon grand-père y était mineur. Et il y a laissé la peau.

Presenter Notes

Les algos et traitements

Presenter Notes

Prétraitement du texte brut

C'est une bête fonction python (sulci.textutils.normalize_text), qui:

  • transforme les entités (&amp;eacute; => é)
  • supprime les balises
  • normalise certains caractères (les apostrophes, les guillemets par exemple)
  • transforme par exemple les "dit-il" en "dit - il"
>>> from sulci.textutils import normalize_text
>>> text = """<p>«C’est le petit monde politico-médiatique qui m’a prise en
...        grippe, pas l’opinion publique.»</p> <p><strong>Eva Joly </strong>
...        candidate Europe Ecologie-les Verts à l’Elysée dans l’hebdomadaire
...        <em>Politis </em> paru hier</p>"""
>>> print normalize_text(text)
«C'est le petit monde politico-médiatique qui m'a prise en grippe, pas
l'opinion publique.» Eva Joly candidate Europe Ecologie - les Verts à l'Elysée
dans l'hebdomadaire Politis paru hier

Pour aller voir le code:

https://github.com/yohanboniface/sulci/blob/master/sulci/textutils.py#L30

Presenter Notes

Tokenisation

  • le texte brut est découpé et "pythonnisé"
  • deux niveaux de découpages: la phrase et les éléments de la phrase ("mots", ponctuation...)
  • chacun de ces éléments devient un objet python: une phrase est une instance de Sample; un mot, une instance de Token
>>> text = u"Les insultes sont la seule raison des pauvres d'esprit"
>>> st = StemmedText(text)
>>> sample = st.samples[0]  # Première phrase
>>> token = sample[0]
>>> token.original
u'Les'
>>> token.lemme
u'le'

Note

<https://github.com/yohanboniface/sulci/blob/master/sulci/textutils.py#L50> <https://github.com/yohanboniface/sulci/blob/master/sulci/base.py#L105> <https://github.com/yohanboniface/sulci/blob/master/sulci/base.py#L217>

Presenter Notes

Catégorisation syntaxique

On détermine le "rôle" d'un Token dans la phrase: nom commun, adjectif, ponctuation, etc.

Pourquoi ?

  • distinguer les homonymes ("les poules du couvent couvent")
  • pouvoir pondérer / filtrer selon le type: par exemple donner plus de poids aux noms et adjectifs et aucun aux déterminants
>>> for t in sample:
...     print t.original, t.tag
Les DTN:pl
insultes SBC:pl
sont ECJ:pl
la DTN:sg
seule ADJ:sg
raison SBC:sg
des DTC:pl
pauvres SBC:pl
d' PREP
esprit SBC:sg

Presenter Notes

Lemmatisation

On détermine la version "neutre" d'un mot, pour essayer de gommer les aspérités de son utilisation dans le contexte:

  • l'infinitif pour un verbe
  • le singulier pour un nom
  • le masculin singulier pour un adjectif
  • etc.

Par exemple, la phrase:

Les insultes sont la seule raison des pauvres d'esprit

devient:

Le insulte être le seul raison de un pauvre de esprit

Presenter Notes

Lemmatisation

>>> from sulci.textmining import StemmedText
>>> text = u"Les insultes sont la seule raison des pauvres d'esprit"
>>> st = StemmedText(text)
>>> print st.samples[0]  # la première phrase
le insulte être le seul raison des pauvre de esprit

Presenter Notes

Lemmatisation

Et la stemmatisation ?

Pros:

  • ne demande pas d'apprentissage

Cons:

  • algorithme strictement morphologique (ne prend pas en compte contexte, POS) ; donc par exemple ne sait pas distinguer "couvent" de "couvent" dans le classique: «Les poules du couvent couvent.»
  • l'algorithme le plus connu (Porter, implémenté notamment par le projet Snowball) est réputé pas très adapté au français

Pour tester quand même (package pystemmer à installer):

>>> from Stemmer import Stemmer
>>> stemmer = Stemmer("french")
>>> stemmer.stemWord("chevaux")  # résultat attendu
'cheval'
>>> stemmer.stemWord("genoux")  # résultat considéré comme une erreur
'genoux'

Presenter Notes

Extraction des entités clés

Extraire les éléments du discours qui représentent le sens du texte.

Une entité clé, c'est quoi ?

  • collocations (~ expression dont le sens vaut plus que la somme des sens qui la composent), n-grams (~ expression dont les composantes avaient statistiquement plus de chances d'apparaître ensemble que séparemment)
  • noms propres
  • mots dont la fréquence est supérieure à la fréquence moyenne des mots dans le texte
  • le maire de Paris Bertrand Delanoë

Presenter Notes

Extraction des entités clés

Pointwise mutual information

Compare la fréquence d'apparition d'une expression avec celle des éléments de cette expression

ngram_possible = len(self.text) - len(self) + 1
members_probability = product([1.0 * s.count/len(self.text) for s in self])
s_m_i = math.log(1.0 * self.count / ngram_possible / members_probability)

Pondération des entités

statistical_mutual_information * nrelative_frequency * POS score

En d'autres termes:

  • le score de l'expression en tant que collocation
  • multiplié par la fréquence relative à la taille de l'expression
  • multiplié par un score issu de la catégorie lexicales des éléments de l'expression

Presenter Notes

Extraction des entités clés

>>> from sulci.textmining import SemanticalTagger
>>> text = u"""A l’occasion d’un attroupement lors de sa visite à Fessenheim,
... hier, Nicolas Sarkozy nous convie à faire quelques pas avec lui devant les
... caméras. Il tient manifestement à s’adresser directement aux lecteurs de
... Libération. Le président-candidat : «[François Hollande] se coupe des
... ouvriers en faisant cela [en fermant Fessenheim]. C’est une erreur. François
... Mitterrand ne l’aurait jamais fait. Les gens ont bien compris que cette
... décision était purement électoraliste. Ils ne sont pas bêtes.»
... Le journaliste de Libération : «Mais cela va coûter beaucoup d’argent de
... remettre à niveau la centrale.»
... Le président-candidat : «Le rapport de la Cour des comptes a été très clair.
... Ça coûterait encore plus cher d’investir dans le photovoltaïque.» On entre
... dans un nouveau bâtiment. Encore le président-candidat : «Sans moi, vous
... allez vous embêter. Qu’est-ce que Libération va devenir ? Combien de pages
... vous faites sur moi par jour ?»"""
>>> s = SemanticalTagger(text)
>>> s.deduplicate_keyentities()
>>> for ke in s.keyentities:
... print ke
François Hollande
Nicolas Sarkozy
François Mitterrand
aller
président-candidat
Libération
cela
Fessenheim
Qu'est
Cour

Presenter Notes

Catégorisation sémantique

  • les catégories sémantiques sont appelées "descripteurs"
  • sulci est un moteur d'inférence (il va trouver qu'un texte parle du Parti socialiste même si la chaîne "Parti socialiste" n'apparaît jamais dans le texte, mais par exemple seulement "François Hollande", "Martine Aubry"...)
  • MAIS il ne connaît que les descripteurs qu'on lui a fait apprendre
  • un peu plus de 7000 descripteurs dans l'apprentissage Libération
  • les descripteurs sont déduits des entités clés ("déclencheurs") extraites lors de l'étape précédente via les relations déclencheurs=>descripteurs créées lors de l'apprentissage
>>> for d in s.descriptors[:10]:
... print d
(<Descriptor: Nicolas Sarkozy>, 111.5773664718986)
(<Descriptor: élection présidentielle>, 99.607961983161587)
(<Descriptor: 2012>, 70.391150257770533)
(<Descriptor: François Mitterrand>, 65.768314429804633)
(<Descriptor: chef de l'Etat>, 64.654728257089261)
(<Descriptor: Libération (journal)>, 58.484979899632719)
(<Descriptor: primaire électorale>, 43.289428857194572)
(<Descriptor: Parti socialiste>, 42.946056049664115)
(<Descriptor: candidature>, 39.57865973263668)
(<Descriptor: déclaration>, 36.031019330252548)

Presenter Notes

L'apprentissage

Presenter Notes

Généralités

  • chaque apprentissage s'appuie sur un corpus

  • qu'est-ce qu'un corpus ?

    Un ensemble de textes sur lesquels sont déjà posés les descripteurs qu'on souhaite faire apprendre

Presenter Notes

Catégorisation syntaxique

  • cet apprentissage est propre à une langue
  • c'est un algorithme de Brill à peine revisité
  • error driven => il apprend de ses erreurs

A quoi ressemble le corpus

La/DTN:sg liberté/SBC:sg de/PREP la/DTN:sg presse/SBC:sg n'/ADV aura/ACJ:sg donc/ADV vécu/PAR:sg que/SUB vingt/CAR et/COO un/CAR ans/SBC:pl en/PREP Hongrie/SBP:sg ./.
  • actuellement, le corpus pour la catégorisation syntaxique contient 30666 mots

Presenter Notes

L'algo de Brill

  • en entrée, on fournit:
    • un corpus tagué proprement
    • un lexique des mots les plus courants, déjà tagués
    • des templates de règles
  • le corpus est dupliqué dans une version sans les tags

  • un premier tag issu de règles basiques est appliqué à chaque mot du corpus

  • les deux corpus sont alors comparés

  • dès qu'une erreur est trouvée, l'algorithme infère des règles pouvant expliquer cette erreur en utilisant les templates fournis

  • chacune de ces règles est testée sur tout le corpus de travail; est retenue celle qui a le meilleur bilan erreurs corrigées / erreurs créées

  • on passe à l'erreur suivante, et ainsi de suite tant qu'il reste des erreurs

Presenter Notes

L'algo de Brill

  • Toute cette séquence est répétée deux fois:

    • une fois pour les règles lexicales (s'appuyant sur la forme des mots)
    • une fois pour les règles contextuelles (s'appuyant sur les mots et tags voisins)

Presenter Notes

L'algo de Brill

Exemples de règles

Règles lexicales:

> SBC:sg des fgoodright SBC:pl

> SBC:sg ait fhassuf 3 VCJ:sg

> SBC:sg les fgoodright SBC:pl

> SBC:sg ces fhassuf 3 SBC:pl

> SBC:sg aient fhassuf 5 VCJ:pl

> SBC:sg rer fhassuf 3 VNCFF

Règles contextuelles:

> DTN:sg PRV:sg WDNEXTTAG leur VNCFF

> SBC:sg PAR:sg PREVBIGRAM a été

> DTN:pl PRO:pl WDAND2TAGBFR PRV:pl tous

Presenter Notes

Lemmatisation

  • c'est un dérivé de l'algorithme de Brill
  • sortaient / portaient, galère galère

A quoi ressemble le corpus

> La/DTN:sg/le liberté/SBC:sg de/PREP la/DTN:sg/le presse/SBC:sg n'/ADV/ne > aura/ACJ:sg/avoir donc/ADV vécu/PAR:sg/vivre que/SUB vingt/CAR et/COO un/CAR > ans/SBC:pl/an en/PREP Hongrie/SBP:sg ./.

  • extension du format utilisé pour la catégorisation syntaxique
  • la lemmatisation a besoin de la catégorie syntaxique

Presenter Notes

Exemples de règles

> COO MAKELOWER 0.678643

> ECJ:sg FORCELEMME être 0.660030

> PAR:sg CHANGESUFFIX "enu" "enir" 0.471148

> VNCNT CHANGESUFFIX "ssant" "sser" 0.270237

> ADJ:pl CHANGESUFFIX "aines" "ain" 0.422129

> ADJ:pl CHANGESUFFIX "ales" "al" 0.465632

> ADJ:sg CHANGESUFFIX "uelle" "uel" 0.422129

Presenter Notes

Catégorisation sémantique

  • c'est le plus lourd
  • il est totalement modelé par le corpus utilisé
  • 42000 textes dans le corpus utilisé pour l'apprentissage Libération
  • les textes du corpus sont donc déjà catégorisés
  • plus de 36 heures de traitement
  • avec zeromq, on peut paralléliser le traitement

Presenter Notes

Catégorisation sémantique

  • chaque texte est processé, jusqu'à en extraire les entités clés
  • par hypothèse, chaque entité clé est liée à chaque descripteur du texte
  • ce lien est surpondéré à chaque apparition de cette relation
  • en fin de traitement, les relations très peu pondérées sont nettoyées pour alléger les temps de traitement

Presenter Notes

Catégorisation sémantique

Dans l'apprentissage Libération actuel:

  • un peu plus de 7000 descripteurs entraînés
  • 196000 déclencheurs (entités clés)
  • 359034 relations déclencheur=>descripteurs après nettoyage

Presenter Notes

Catégorisation sémantique

>>> trigger = Trigger.objects.get(original="Nicolas Sarkozy")
>>> for relation in trigger:
...     print relation, relation.pondered_weight
Nicolas Sarkozy =[952.000000]=> Nicolas Sarkozy 1.0
Nicolas Sarkozy =[530.000000]=> chef de l'Etat 0.556722689076
Nicolas Sarkozy =[346.000000]=> UMP 0.207169853114
Nicolas Sarkozy =[306.000000]=> élection présidentielle 0.292729591837
Nicolas Sarkozy =[269.000000]=> gouvernement 0.128829582681
Nicolas Sarkozy =[263.000000]=> réforme 0.205825814745
Nicolas Sarkozy =[260.000000]=> déclaration 0.244856563315
Nicolas Sarkozy =[249.000000]=> 2012 0.23511588751
Nicolas Sarkozy =[234.000000]=> polémique 0.156721544204
Nicolas Sarkozy =[226.000000]=> France 0.060967341482
Nicolas Sarkozy =[212.000000]=> Parti socialiste 0.0744638549426

>>> descriptor = Descriptor.objects.get(name="Parti socialiste")
>>> for relation in descriptor.triggertodescriptor_set.all()[:10]:
...     print relation, relation.pondered_weight
PS =[634.000000]=> Parti socialiste 1.0
Martine Aubry =[374.000000]=> Parti socialiste 0.589905362776
France =[255.000000]=> Parti socialiste 0.116548967594
UMP =[219.000000]=> Parti socialiste 0.124626466201
Nicolas Sarkozy =[212.000000]=> Parti socialiste 0.0744638549426
Parti =[212.000000]=> Parti socialiste 0.322225408661
Ségolène Royal =[211.000000]=> Parti socialiste 0.332807570978
Français =[193.000000]=> Parti socialiste 0.169804525811
François Hollande =[177.000000]=> Parti socialiste 0.279179810726
Dominique Strauss-Kahn =[172.000000]=> Parti socialiste 0.192820084991

Presenter Notes

Catégorisation sémantique

pondered_weight

  • La relation entre un déclencheur et un descripteur est pondérée de la sorte:

> weight de la relation courante

> / weight de la relation max du déclencheur

> / weight de la relation max du descripteur

Presenter Notes

Idées d'utilisation future

  • News catégorisée, transparent et open source
  • processer wikipedia
  • serveur centralisé avec webservice pour utilisation facilitée

Presenter Notes

Pour aider

  • accélérer l'apprentissage sémantique en utilisant un base en RAM (voire tester d'autres modèles de données)
  • inverse document frequency
  • toute review de code, tous retours d'utilisation sont bons à prendre :)
  • tests unitaires
  • étendre le corpus pour l'apprentissage de la catégorisation sémantique et de la lemmatisation

Presenter Notes

En savoir plus

Presenter Notes

Remerciements

Libé, bien sûr, laboratoire permanent

Jérôme Petazzoni, optimisateur garanti sans gaz à effet de serre

Presenter Notes

Liens

<http://snowball.tartarus.org/> - algo Porter dans le projet snowball

<http://www.fabienpoulard.info/post/2008/02/21/Lalgorithme-de-Porter> - algo Porter par Fabien Poulard

<http://en.wikipedia.org/wiki/Pointwise_mutual_information> - pointwise mutual information

<http://archimede.bibl.ulaval.ca/archimede/fichiers/22225/22225.html> - adaptation du catégoriseur de Brill au français et modification de l'approche

Presenter Notes