web-dev-qa-db-fra.com

Métriques de similitude des chaînes dans Python

Je veux trouver une similitude de chaîne entre deux chaînes. This page contient des exemples de certains d'entre eux. Python a une implémentation de algorithme Levenshtein . Existe-t-il un meilleur algorithme, (et avec un peu de chance une python), sous ces contraintes) .

  1. Je veux faire des correspondances floues entre les chaînes. Par exemple, les correspondances ('Bonjour, vous tous', 'bonjour, tout le monde') doivent renvoyer True
  2. Les faux négatifs sont acceptables, les faux positifs, sauf dans des cas extrêmement rares, ne le sont pas.
  3. Cela se fait dans un cadre non temps réel, donc la vitesse n'est pas (beaucoup) préoccupante.
  4. [Modifier] Je compare plusieurs chaînes Word.

Est-ce que quelque chose d'autre que la distance de Levenshtein (ou le rapport de Levenshtein) serait un meilleur algorithme pour mon cas?

42
agiliq

Il existe une excellente ressource pour les mesures de similitude des chaînes à l'Université de Sheffield. Il a une liste de diverses métriques (au-delà de Levenshtein) et en a des implémentations open-source. Il semble que beaucoup d'entre eux devraient être faciles à adapter en Python.

http://web.archive.org/web/20081224234350/http://www.dcs.shef.ac.uk/~sam/stringmetrics.html

Voici un peu de la liste:

  • Distance de Hamming
  • Distance Levenshtein
  • Distance Needleman-Wunch ou algorithme de vendeurs
  • et beaucoup plus...
20
ariddell

Je me rends compte que ce n'est pas la même chose, mais c'est assez proche:

>>> import difflib
>>> a = 'Hello, All you people'
>>> b = 'hello, all You peopl'
>>> seq=difflib.SequenceMatcher(a=a.lower(), b=b.lower())
>>> seq.ratio()
0.97560975609756095

Vous pouvez faire cela en fonction

def similar(seq1, seq2):
    return difflib.SequenceMatcher(a=seq1.lower(), b=seq2.lower()).ratio() > 0.9

>>> similar(a, b)
True
>>> similar('Hello, world', 'Hi, world')
False
82
Nadia Alramli

Cet extrait calculera les valeurs de similitude difflib, Levenshtein, Sørensen et Jaccard pour deux chaînes. Dans l'extrait ci-dessous, je parcourais un tsv dans lequel les chaînes d'intérêt occupaient des colonnes [3] et [4] du tsv. (pip install python-Levenshtein et pip install distance):

import codecs, difflib, Levenshtein, distance

with codecs.open("titles.tsv","r","utf-8") as f:
    title_list = f.read().split("\n")[:-1]

    for row in title_list:

        sr      = row.lower().split("\t")

        diffl   = difflib.SequenceMatcher(None, sr[3], sr[4]).ratio()
        lev     = Levenshtein.ratio(sr[3], sr[4]) 
        sor     = 1 - distance.sorensen(sr[3], sr[4])
        jac     = 1 - distance.jaccard(sr[3], sr[4])

        print diffl, lev, sor, jac
13
duhaime

J'utiliserais la distance de Levenshtein, ou la distance dite de Damerau (qui prend en compte les transpositions) plutôt que la substance difflib pour deux raisons (1) "assez rapide" (algo de programmation dynamique) et "whoooosh" (bas-bithing) C le code est disponible et (2) un comportement bien compris, par exemple Levenshtein satisfait l'inégalité du triangle et peut donc être utilisé par ex. un arbre Burkhard-Keller.

Seuil: vous devez traiter comme "positif" uniquement les cas où la distance <(1 - X) * max (len (string1), len (string2)) et ajuster X (le facteur de similitude) selon vos besoins. Une façon de choisir X consiste à obtenir un échantillon de correspondances, à calculer X pour chacune, à ignorer les cas où X <par exemple 0,8 ou 0,9, puis à trier le reste par ordre décroissant de X et à les observer et à insérer le résultat correct et à calculer certains mesure du coût des erreurs pour différents niveaux de X.

N.B. Votre exemple de singe/Apple a une distance de 2, donc X est de 0,6 ... Je n'utiliserais qu'un seuil aussi bas que 0,75 si je cherchais désespérément quelque chose et que ma pénalité en faux négatif était élevée

7
John Machin

C'est ça que tu veux dire?

>>> get_close_matches('appel', ['ape', 'Apple', 'Peach', 'puppy'])
['Apple', 'ape']
>>> import keyword
>>> get_close_matches('wheel', keyword.kwlist)
['while']
>>> get_close_matches('Apple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']

regardez http://docs.python.org/library/difflib.html#difflib.get_close_matches

5
Tzury Bar Yochay

Je sais que ce n'est pas la même chose, mais vous pouvez ajuster le rapport pour filtrer les chaînes qui ne sont pas assez similaires et renvoyer la correspondance la plus proche à la chaîne que vous recherchez.

Peut-être seriez-vous plus intéressé par les métriques de similitude sémantique.

https://www.google.com/search?client=ubuntu&channel=fs&q=semantic+similarity+string+match&ie=utf-8&oe=utf-8

Je sais que vous avez dit que la vitesse n'est pas un problème, mais si vous traitez beaucoup de chaînes pour votre algorithme, ce qui suit est très utile.

def spellcheck(self, sentence):
    #return ' '.join([difflib.get_close_matches(Word, wordlist,1 , 0)[0] for Word in sentence.split()])
    return ' '.join( [ sorted( { Levenshtein.ratio(x, Word):x for x in wordlist }.items(), reverse=True)[0][1] for Word in sentence.split() ] )

C'est environ 20 fois plus rapide que difflib.

https://pypi.python.org/pypi/python-Levenshtein/

importer Levenshtein

2
John