【问题标题】:Is there a way to boost matching performance when doing string matching in Python?在 Python 中进行字符串匹配时,有没有办法提高匹配性能?
【发布时间】:2020-09-14 14:57:00
【问题描述】:

我有一个非常大的字典,其中存储了大量的英语句子及其西班牙语翻译。当给定一个随机的英语句子时,我打算使用 Python 的fuzzywuzzy 库在字典中找到它最接近的匹配项。我的代码:

from fuzzywuzzy import process
sentencePairs = {'How are you?':'¿Cómo estás?', 'Good morning!':'¡Buenos días!'}
query= 'How old are you?'
match = process.extractOne(query, sentencePairs.keys())[0]
print(match, sentencePairs[match], sep='\n')

在现实生活场景中,sentencePairs 字典会非常大,至少存储一百万个项目。因此,即使安装了 python-Levenshtein 以提供加速,使用fuzzywuzzy 也需要很长时间才能得到结果。 那么有没有更好的方法来获得更好的性能呢?我的目标是在几秒钟内甚至实时获得结果。

【问题讨论】:

  • 注意,你真的没有在这里使用dict,你还不如使用一个元组列表,或者两个单独的列表

标签: python performance fuzzywuzzy


【解决方案1】:

提高性能的方法

使用 Levenshtein 距离的模糊匹配永远不会超快,但您的代码中有几件事可以优化:

  1. 将字符串和列表传递给 process.extractOne 时,它​​将通过小写这些字符串、删除非字母数字字符和修剪空格来预处理这些字符串。由于您每次都重复使用相同的英语:西班牙语映射,因此您应该提前进行一次此预处理。

  2. 即使使用 python-Levenshtein FuzzyWuzzy 在很多地方都没有真正优化。您应该将其替换为 RapidFuzz,它实现了具有相似接口的相同算法,但主要用 C++ 实现,并附带一些额外的算法改进,使其速度更快。

  3. 默认情况下,process.extractOne 内部使用fuzz.WRatio 比较字符串。这是多种字符串匹配算法的组合。因此,通过传递例如选择更快的算法scorer=fuzz.ratio 到 process.extractOne 提高了性能。但是请记住,这会改变比较字符串的方式,因此根据您的数据,您可能不想这样做。

利用1和2的实现

from rapidfuzz import process, utils
# english sentences are already lower cased
# and without special characters like question marks
sentencePairs = {'how are you':'¿Cómo estás?', 'good morning':'¡Buenos días!'}
query= 'How old are you?'
match, _ = process.extractOne(
   utils.default_process(query),
   sentencePairs.keys(),
   processor=None)
print(match, sentencePairs[match], sep='\n')

利用1、2、3实现

from rapidfuzz import process, utils, fuzz
# english sentences are already lower cased
# and without special characters like question marks
sentencePairs = {'how are you':'¿Cómo estás?', 'good morning':'¡Buenos días!'}
query= 'How old are you?'
match, _ = process.extractOne(
   utils.default_process(query),
   sentencePairs.keys(),
   processor=None,
   scorer=fuzz.ratio)
print(match, sentencePairs[match], sep='\n')

基准测试

为了提供一些时间比较,我生成了一百万个句子:

import string
import random
random.seed(18)
sentencePairs = {
    ''.join(random.choice(string.ascii_lowercase + string.digits)
       for _ in range(15)
    ): "spanish text"
    for s in range(1000000)
}
query= 'How old are you?'

下表显示了不同解决方案在我的计算机上需要多长时间

| Implementation                           | Runtime        |
|------------------------------------------|----------------|
| Your current implementation              | 18.98 seconds  |
| Implementation making use of 1 and 2     | 1.4 seconds    |
| Implementation making use of 1, 2 and 3  | 0.4 seconds    |

【讨论】:

  • 我在您的代码中找不到fuzz.ratiofuzz.WRatio。您的意思是使用它们循环所有 sentenPairs 键而不是 process.extractOne() 并且使用 fuzz.ratio 需要 0.4 秒?
  • prcess.extractOne 支持 scorer 参数来更改它正在使用的字符串匹配算法。我更新了答案以使这一点更清楚。
  • 关于utils.default_processprocessor,我可以用match, _ = process.extractOne(query, sentencePairs.keys(), scorer=fuzz.ratio)代替match, _ = process.extractOne(utils.default_process(query), sentencePairs.keys(), processor=None, scorer=fuzz.ratio)吗?这两行代码似乎没有区别,根据我的测试,执行时间基本相同。我也查了github.com/maxbachmann/rapidfuzz/blob/master/docs/usage/…,还是没发现什么区别。
  • utils.default_process 正在预处理您的字符串(小写,删除非字母数字字符,例如问号并修剪空格)。因此,当您不需要/想要这个时,您可以将其省略(例如,字符串“你多大了?”转换为“你多大了”)
【解决方案2】:

可能有更好的解决方案,但我首先想到的是分区。

您可以创建 26 个不同的词典,每个词典代表一个英文字母。然后您可以使用所有以相应字母开头的键加载所有这些字典。 例如。 adict, bdict... zdict 等 所以。 hdict 将包含以 h 开头的 Key 的 Key 值。比如 key="你好吗?"

这样,您只需要查询与起始字母匹配的字典。

【讨论】:

  • 使用此解决方案,您将失去对第一个字符的“模糊”搜索。不过,它仍然是一个有效的选择。
猜你喜欢
  • 2021-09-20
  • 1970-01-01
  • 2018-01-09
  • 2021-01-04
  • 2021-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
相关资源
最近更新 更多