Python 作者姓名的近似字符串匹配-模块和策略

Python 作者姓名的近似字符串匹配-模块和策略,python,python-2.7,difflib,Python,Python 2.7,Difflib,我创建了一个小程序,用于检查作者数据库中是否存在作者。我还没有找到解决这个问题的任何特定模块,所以我使用模块进行近似字符串匹配,从头开始编写 该数据库包含约6000名作者,格式非常糟糕(许多打字错误、变体、标题如“Dr.”等)。查询作者列表通常在500-1000之间(我有很多这样的列表),这使得速度非常重要 我的总体策略是尽可能地修剪和过滤数据库,并寻找精确的匹配项。如果没有找到匹配项,我将转到近似字符串匹配 我目前正在使用内置的difflib.get\u close\u matches,这正是

我创建了一个小程序,用于检查作者数据库中是否存在作者。我还没有找到解决这个问题的任何特定模块,所以我使用模块进行近似字符串匹配,从头开始编写

该数据库包含约6000名作者,格式非常糟糕(许多打字错误、变体、标题如“Dr.”等)。查询作者列表通常在500-1000之间(我有很多这样的列表),这使得速度非常重要

我的总体策略是尽可能地修剪和过滤数据库,并寻找精确的匹配项。如果没有找到匹配项,我将转到近似字符串匹配

我目前正在使用内置的
difflib.get\u close\u matches
,这正是我想要的,但是速度非常慢(几分钟)。因此,我正在寻找其他选择:

  • 在给定查询字符串的数据库中,哪一个最快的模块可以返回最好的(例如,超过某个阈值的3个匹配)
  • 比较两个字符串的最快模块是什么

我发现的唯一一个是fuzzy-wuzzy,它甚至比difflib慢。

Python的自然语言工具包(nltk)可能有一些额外的资源,您可以尝试一下,这似乎是一个很好的开始。只是一个想法。

尝试安装native-C库

我在我的电脑上运行了一个基准测试,在19k单词列表中找到了8个单词的最佳候选词(使用
pip install python\u Levenshtein-0.12.0-cp34-none-win\u amd64.whl
),我得到了以下时间:

  • 没有C后端:
    比较了48.591717004776秒中的151664个单词(0.00032039058052521366秒/搜索)
  • 已安装C后端:
    在13.034106969833374秒(8.5940677895198E-05秒/搜索)中比较了151664个单词
这比我想象的快了4倍(但没有我想象的那么多)

结果如下:

0 of 8: Compared 'Lemaire' --> `[('L.', 90), ('Le', 90), ('A', 90), ('Re', 90), ('Em', 90)]`
1 of 8: Compared 'Peil' --> `[('L.', 90), ('E.', 90), ('Pfeil', 89), ('Gampel', 76), ('Jo-pei', 76)]`
2 of 8: Compared 'Singleton' --> `[('Eto', 90), ('Ng', 90), ('Le', 90), ('to', 90), ('On', 90)]`
3 of 8: Compared 'Tagoe' --> `[('Go', 90), ('A', 90), ('T', 90), ('E.', 90), ('Sagoe', 80)]`
4 of 8: Compared 'Jgoun' --> `[('Go', 90), ('Gon', 75), ('Journo', 73), ('Jaguin', 73), ('Gounaris', 72)]`
5 of 8: Compared 'Ben' --> `[('Benfer', 90), ('Bence', 90), ('Ben-Amotz', 90), ('Beniaminov', 90), ('Benczak', 90)]`
6 of 8: Compared 'Porte' --> `[('Porter', 91), ('Portet', 91), ('Porten', 91), ('Po', 90), ('Gould-Porter', 90)]`
7 of 8: Compared 'Nyla' --> `[('L.', 90), ('A', 90), ('Sirichanya', 76), ('Neyland', 73), ('Greenleaf', 67)]`
下面是基准测试的python代码:

import os
import zipfile
from urllib import request as urlrequest
from fuzzywuzzy import process as fzproc
import time
import random

download_url = 'http://www.outpost9.com/files/wordlists/actor-surname.zip'
zip_name = os.path.basename(download_url)
fname, _ = os.path.splitext(zip_name)

def fuzzy_match(dictionary, search):
    nsearch = len(search)
    for i, s in enumerate(search):
        best = fzproc.extractBests(s, dictionary)
        print("%i of %i: Compared '%s' --> `%s`" % (i, nsearch, s, best))

def benchmark_fuzzy_match(wordslist, dict_split_ratio=0.9996):
    """ Shuffle and split words-list into `dictionary` and `search-words`. """
    rnd = random.Random(0)
    rnd.shuffle(wordslist)
    nwords = len(wordslist)
    ndictionary = int(dict_split_ratio * nwords)

    dictionary = wordslist[:ndictionary]
    search = wordslist[ndictionary:]
    fuzzy_match(dictionary, search)

    return ndictionary, (nwords - ndictionary)

def run_benchmark():
    if not os.path.exists(zip_name):
        urlrequest.urlretrieve(download_url, filename=zip_name)

    with zipfile.ZipFile(zip_name, 'r') as zfile:
        with zfile.open(fname) as words_file:
            blines = words_file.readlines()
            wordslist = [line.decode('ascii').strip() for line in blines]
            wordslist = wordslist[4:]  # Skip header.

            t_start = time.time()
            ndict, nsearch = benchmark_fuzzy_match(wordslist)
            t_finish = time.time()

            t_elapsed = t_finish - t_start
            ncomparisons = ndict * nsearch
            sec_per_search = t_elapsed / ncomparisons
            msg = "Compared %s words in %s sec (%s sec/search)."
            print(msg % (ncomparisons, t_elapsed, sec_per_search))

if __name__ == '__main__':
    run_benchmark()

你有没有试过和/或?它们是的实现。这听起来像是Levenshtein距离问题,但是difflib实现很可能已经使用了这个或类似的算法。您可以对所有条目使用散列,并且只比较散列足够接近的条目,但这取决于找到一个好的散列算法(如果有!)。也许所有字母的ASCII值之和可以作为一个非常简单的值来使用。我使用Python中的Levenshtein包将单个名称与大约10k个名称进行匹配的经验是,它相当快。你能概括一下需要几分钟的时间吗?它是否将500-1000个“未知”名称与6000个“已知”名称相匹配?或者只是一个“未知”的名字与“已知”的名字相对?@hochl-我认为你是对的,一个好的策略是对庞大的列表进行初步的粗略排序。我一直在研究简单地首先使用SequenceMatcher.real\u quick\u ratio过滤掉最差的匹配@Mike Sandford-数据库列表包含大约6000个姓名。查询名称列表通常包含500-1000个名称。对于查询列表中的每个名称,我希望在数据库列表中找到最匹配的名称(高于某个阈值)。我将研究Levenshtein包,看看它是否更快。也许您还可以进行其他类型的优化:“查询名称列表”是否包含重复项(不一定在同一个文件中)?在这种情况下,您可以使用某种缓存。为了对6000个作者姓名的列表进行排序/筛选,您能否做出一些假设(例如,查询名称中的第一个字符是正确的)?您能否改进作者姓名列表(例如,删除标题/替换非ascii字符?),因此我终于回到解决此问题的方法。最后,我使用了水母模块,它很好地实现了jaro winkler距离,它产生的结果与get_close_匹配一样好,而且速度更快。然后,我使用pprocess与数据库并行进行比较,现在大约10秒左右将5000名作者与数据库进行比较!