• Keine Ergebnisse gefunden

Text Matching : calcul des Edit distances et des ratios de matching entre deux séquences de caractères de matching entre deux séquences de caractères

Exemple 3: Concordance avec un mot provenant d'un fichier texte

3.15. Text Matching : calcul des Edit distances et des ratios de matching entre deux séquences de caractères de matching entre deux séquences de caractères

Il existe actuellement plusieurs modules pour le text matching sous python. Les plus courants sont difflib,Levenshtein, Distance ainsi que le module fuzzywuzzy. Dans cette section, nous allons présenter chacun de ces méthodes à travers quelques exemples d’application.

3 .15.1. Le module difflib

175 Le module difflib permet la comparaison de deux séquences de chaines de caractères. Il est conçu à partir de l'algorithme de Ratcliff/Obershelp. Les principales fonctions

sont :SequenceMatcher(), differ(), ndiff(), unified_diff, context_diff(), HtmlDiff()

3 .15.1.1. Utilisation de la fonction SequenceMatcher()

La fonction SequenceMatcher() permet de calculer le taux de matching entre deux séquences de valeurs qui peuvent être de n’importe quels types (numériques et/ou alphabétiques). En indiquant la fonction ratio, on peut obtient la valeur du ratio de matching entre les deux séquences.

Exemple 1 : Comparaison entre deux chaînes et calcul du ratio de matching import difflib

mymatch = difflib.SequenceMatcher(None, "NEW YORK METS", "NEW YORK MEATS")

mymatch.ratio() # Renvoie le ratio de matching. Ici 0.962 Exemple2 :Soient les deux textes text1 et text2 défini comme suit :

text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Integer

eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convallis. Nam sed sem vitae odio

pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris

urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus."""

text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Integer

eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convallis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris

urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc. """

Calculons le taux matching entre ces deux textes. On a : import difflib

mymatch = difflib.SequenceMatcher(None, text1, text2) mymatch.ratio() # renvoie 0.869

176

3 .15.1.2. Utilisation des fonctions get_matching_blocks() et get_opcodes() après SequenceMatcher()

Après l’exécution de la fonction SequenceMatcher, on peut utiliser des fonctions supplémentaires comme get_matching_blocks() et get_opcodes() .

La fonction get_matching_blocks() permet de renvoyer les positions où il y a matching entre les deux séquences. Quant à la fonction get_opcodes() permet de savoir comment changer la première séquence pour obtenir la seconde séquence. Exemples :

import difflib

# Execution du SequenceMatcher

s = difflib.SequenceMatcher(None, "private Thread currentThread;",

"private volatile Thread currentThread;")

# Exécution de get_matching_blocks

print(s.get_matching_blocks()) # renvoie [(0, 0, 6), (6, 15, 23), (29, 38, 0)] (exécuter 2 fois)

for block in s.get_matching_blocks():

print("Text1[%d] and Text2[%d] match for %d elements" % block)

# Exécution de get_matching_blocks

print(s.get_opcodes()) # [('equal', 0, 6, 0, 6), ('insert', 6, 6, 6, 15), ('equal', 6, 29, 15, 38)]

for opcode in s.get_opcodes():

print("%6s Text1[%d:%d] Text2[%d:%d]" % opcode)

NB : Il faut noter que le dernier tuple renvoyé par .get_matching_blocks () est toujours un dummy, (len (Text1), len (Text2), 0), et c'est le seul cas où le dernier élément de tuple (nombre d'éléments appariés) est 0.

3 .15.1.3. Utilisation de la fonction differ()

La fonction differ() de difflib permet de faire ressortir la dissemblance entre deux séquences.

La sortie produite se décrit comme suit :

Les lignes précédées de - indiquent celles qui se trouve dans la première séquence, mais pas la seconde.

Les lignes précédées de + sont celles qui sont dans la deuxième séquence, mais pas la première.

Si une ligne a une différence incrémentale (caractère de plus ou de moins) entre les versions, une ligne supplémentaire préfixée par « ? » est utilisé pour mettre en évidence le changement dans la nouvelle version.

Si une ligne n'a pas changé, elle est imprimée avec un espace vide supplémentaire sur la colonne de gauche afin qu'il soit aligné avec les autres lignes qui peuvent avoir des différences.

Pour comparer deux textes, il est recommandé de le découper en une séquence de lignes individuelles et passer ces séquences à la fonction comparer ().

177 Exemple: Soient les deux textes text1 et text2 définis précédemment.

Découpons ces textes en lignes comme suit:

text1_lines = text1.splitlines() text2_lines = text2.splitlines()

Et appliquons la fonction compare() sur ces deux listes de lignes. On a : import difflib

diff = difflib.Differ().compare(text1_lines, text2_lines) print ('\n'.join(diff))

3 .15.1.4. Utilisation de la fonction ndiff()

La fonction ndiff () produit essentiellement la même sortie que differ.compare (). Mais, la fonction est spécifiquement adaptée pour le traitement des données en texte et d'éliminer le

«bruit» dans les inputs.

Exemple: Soient les deux listes de lignes de textes définies précédemment text1_lines et text2_lines. La fonction ndiff() s’applique comme suit :

import difflib

diff = difflib.ndiff(text1_lines, text2_lines) print ('\n'.join(list(diff)))

3 .15.1.5. Utilisation de la fonction unified_diff()

Alors que la classe Differ() affiche toutes les lignes d'entrée, la fonction unified_diff n'inclut que des lignes modifiées avec un peu de contexte. Exemple :

import difflib

diff = difflib.unified_diff(text1_lines, text2_lines, lineterm='') print ('\n'.join(list(diff)))

3 .15.1.6. Utilisation de la fonction context_diff()

Cette fonction produit le même output proche de celui de unified_diff(). Exemple : import difflib

diff = difflib.context_diff(text1_lines, text2_lines, lineterm='') print ('\n'.join(list(diff)))

3 .15.1.7. Utilisation de la fonction Htmldiff()

La fonction HtmlDiff() produit une sortie en format HTML avec les mêmes informations que celles renvoyée par Diff(). Exemple :

import difflib

d = difflib.HtmlDiff()

print (d.make_table(text1_lines, text2_lines)) # produit la table de sortie

178

3 .15.2. Le module Levenshtein

Le module Levenshtein permet de calculer l’edit distance entre deux séquences tout en renvoyant le ratio de matching. Ce module est basé sur l'algorithme de matching Levenshtein.

Levenshtein a quelques points communs avec la fonction SequenceMatcher de Difflib.

Toutefois, il ne prend en charge que les chaînes de caractères, pas les types de séquence arbitraire contrairement à SequenceMatcher. Cependant il reste beaucoup plus rapide. Ci-dessous quelques exemples d’application.

Exemple 1 :

import Levenshtein

Levenshtein.ratio('hello world', 'hello') # renvoie 0.625 Exemple 2 : Soient les deux textes:

text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Integer

eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convallis. Nam sed sem vitae odio

pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris

urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus."""

text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Integer

eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convallis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris

urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc. """

Calcul du ratio sur les textes brut

Levenshtein.ratio(text1, text2) # renvoie 0.9365928189457601 Calcul des ratio sur le texte découpé en lignes et converties en textes

Levenshtein.ratio(str(text1_lines), str(text2_lines)) # renvoie 0.9380014587892049

179 NB: Levenshtein ne s'applique que sur les string. Alors quand on dispose d'une liste, il vaut mieux faire join d'abord et récupérer le texte.

3.15.3. Le module distance

Ce module fournit des fonctions qui permettent de calculer les edit distance entre deux séquences et renvoie le ratio de matching. Les principales fonctions disponibles dans le module distances sont Levenshtein() qui calcule la distance de Levenshtein, Hamming() qui calcule la distance de Hamming, Jaccard() et Sorensen() qui calculent respectivement la distance de Jaccard et de Sorensen. Ci-dessous les exemples d’application de ces fonctions.

3.15.3.1. Distance de Levenshtein

import distance

distance.levenshtein("this is a test", "this is a test!") # renvoie 1 renvoie la distance entre les deux argument (distance en nombre absolu)

NB la distance levenshtein peut être normalisée en utilisant l'option normalized=True. On obtient alors les ratios. Cela permet la comparabilité avec les autres mesures de distance. Ex : distance.levenshtein("this is a test", "this is a test!", normalized=True) # renvoie 0.06666666666666667

La distance normalisée de levenshtein peut aussi se calculer avec la fonction nlevenshtein().

Cependant, deux stratégies sont disponibles pour le calcul: soit la longueur de l'alignement le plus court entre les séquences est prise comme facteur ou bien la longueur du plus long est prise comme facteur. Ex :

distance.nlevenshtein("this is a test", "this is a test!", method=1)

# alignement plus court comme facteur. renvoie 0.06666666666666667

distance.nlevenshtein("this is a test", "this is a test!", method=2)

# alignement plus long comme facteur. Renvoie 0.06666666666666667

3.15.3.2. Distance de Hamming

import distance

distance.hamming("this is a test?", "this is a test!") # exige que les textes soient de meme longueur. renvoie 1 (nombre absolu); distance normalisable

distance.hamming("this is a test?", "this is a test!", normalized=True)

3.15.3.3.Distance de Sorensen

import distance

180 distance.sorensen("this is a test", "this is a test!") # renvoie directement la distance en ratio. ici 0.06666666666666665 . Pour avoir le ratio de matching, on fait 1-distance

3.15.3.4.Distance de jaccard

import distance

distance.jaccard("this is a test", "this is a test!")# renvoie directement la distance en ratio. ici 0.125 . Pour avoir le ratio de matching, on fait 1-distance

NB: Contrairement au module Levenshtein, le module distance peut s'appliquer sur une liste de string.

3.15.4. Le module FuzzyWuzzy

Le module FuzzyWuzzy est un module développé à partir des modules de bases. Il se propose d’apporter quelques améliorations à ceux-ci en intégrant quelques options supplémentaires.

Une présentation détaillée de ce module se trouve sur le lien suivant : http://chairnerd.seatgeek.com/fuzzywuzzy-fuzzy-string-matching-in-python/

Les principales fonctions proposées par FuzzyWuzzy

sont :ratio(),token_sort_ratio(),token_set_ratio ou process(). Le choix de la méthode de matching dependra de la formulation du problème par l’utilisateur.

Exemple d'application

from fuzzywuzzy import fuzz, process

# Simple ratio

fuzz.ratio("this is a test", "this is a test!") # Simple Ratio 97

# partial ratio

fuzz.partial_ratio("this is a test", "this is a test!") # Partial Ratio 100

# token_sort_ratio

fuzz.ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear") # Token Sort Ratio 91

fuzz.token_sort_ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear") # 100 Jets', 100), ('New York Giants', 78)]

process.extractOne("cowboys", choices) # ("Dallas Cowboys", 90)

181

3.15.5. Comparaison des différentes méthodes de matching

Soient les deux textes suivantes:

text1 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Integer

eu lacus accumsan arcu fermentum euismod. Donec pulvinar porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convallis. Nam sed sem vitae odio

pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris

urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Suspendisse eu lectus. In nunc. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus."""

text2 = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

Integer

eu lacus accumsan arcu fermentum euismod. Donec pulvinar, porttitor tellus. Aliquam venenatis. Donec facilisis pharetra tortor. In nec mauris eget magna consequat convallis. Nam cras vitae mi vitae odio pellentesque interdum. Sed consequat viverra nisl. Suspendisse arcu metus, blandit quis, rhoncus ac, pharetra eget, velit. Mauris

urna. Morbi nonummy molestie orci. Praesent nisi elit, fringilla ac, suscipit non, tristique vel, mauris. Curabitur vel lorem id nisl porta adipiscing. Duis vulputate tristique enim. Donec quis lectus a justo imperdiet tempus. Suspendisse eu lectus. In nunc. """

Calculons les ratios avec différentes méthodes import difflib,Levenshtein,distance from fuzzywuzzy import fuzz

diffl = difflib.SequenceMatcher(None, text1, text2).ratio() # ratio Ratcliff-Obershelp

lev1 = Levenshtein.ratio(text1, text2) # ratio Levenshtein calculé par le module Levenshtein

lev2 = 1-distance.levenshtein(text1, text2, normalized=True) # ratio Levenshtein calculé par le module distance

#ham = 1-distance.hamming(text1, text2, normalized=True) # ratio de matching de hamming (utilisable uniquement quand les deux séquences ont même longueur).

fuz=fuzz.partial_ratio(text1, text1) # partial ratio fuzzywuzzy print("Ratcliff-Obershelp ratio: ",round(diffl,3) )

182 print("Levenshtein ratio 1: ",round(lev1,3))

print("Levenshtein ratio 2: ",round(lev2,3)) print("Sorensen ratio: ",round(sorens,3)) print("Jaccard ratio: ",round(jac,3) )

print("fuzzywuzzy partial ratio: ",round(fuz,3))

NB :Les fonction présentées ci-dessous permettent de tester uniquement le matching. Elles ne permettent pas de tester "semantic similarity". Ce test nécessite l'utilisation d'autres modules.

3.16. Text Clustering : regroupement des termes d’un texte