<h2>模糊模糊</h2>
<p>既然您提到了使用FuzzyWuzzy作为距离度量,我将集中讨论实现FuzzyWuzzy使用的<code>fuzz.ratio</code>算法的有效方法。fuzzyfuzzy为<code>fuzz.ratio</code>提供了以下两种实现:</p>
<ol>
<li>difflib,完全用Python实现</li>
<li>python Levenshtein使用加权Levenshtein距离和权重2进行替换(替换是删除+插入)。Python Levenshtein是用C实现的,比纯Python实现快得多</李>
</ol>
<h2>python Levenshtein中的实现</h2>
<p><code>python-Levenshtein</code>的实现使用以下实现:</p>
<ol>
<li>删除两个字符串的公共前缀和后缀,因为它们对最终结果没有任何影响。这可以在线性时间内完成,因此匹配类似字符串的速度非常快</李>
<li>修剪字符串之间的Levenshtein距离是通过二次运行时和线性内存使用实现的</李>
</ol>
<h2>RapidFuzz</h2>
<p>我是库<a href="https://github.com/maxbachmann/RapidFuzz" rel="nofollow noreferrer">RapidFuzz</a>的作者,该库以更高效的方式实现FuzzyWuzzy使用的算法。RapidFuzz对<code>fuzz.ratio</code>使用以下接口:</p>
<pre class="lang-py prettyprint-override"><code>def ratio(s1, s2, processor = None, score_cutoff = 0)
</code></pre>
<p>附加的<code>score_cutoff</code>参数可用于提供分数阈值,作为介于0和100之间的浮点值。对于比率<;而是返回分数0。在某些情况下,实现可以使用这一点来使用更优化的实现。在下文中,我将根据输入参数描述RapidFuzz使用的优化。在下文中,{<cd6>}指的是在不低于分数阈值的情况下可能达到的最大距离</p>
<h2>最大距离==0</h2>
<p>可通过直接比较计算相似度,
因为字符串之间不允许有差异。时间复杂性
这个算法是<code>O(N)</code></p>
<h2>最大距离==1且len(s1)==len(s2)</h2>
<p>也可以使用直接比较来计算相似性,因为替换会导致编辑距离高于最大距离。该算法的时间复杂度为<code>O(N)</code></p>
<h2>删除公共前缀</h2>
<p>两个比较字符串的公共前缀/后缀不影响
Levenshtein距离,因此在计算相似度之前移除词缀。此步骤针对以下任一算法执行</p>
<h2>最大距离<;=四,</h2>
<p>采用mbleven算法。该算法
检查下所有可能的编辑操作
阈值<code>max distance</code>。原始算法的描述可以在<a href="http://ceptord.net/fastcomp/index.html" rel="nofollow noreferrer">here</a>中找到。我修改了这个算法,以支持替换的2的重量。作为与正常Levenshtein距离的差异,该算法在这里甚至可以使用高达4的阈值,因为更高的替换权重减少了可能的编辑操作量。该算法的时间复杂度为<code>O(N)</code></p>
<h2>len(短字符串)<;=64拆下普通固定件后</h2>
<p>使用的是BitPAl算法,该算法在
平行的该算法在<a href="https://www.researchgate.net/publication/264639812_BitPAl_A_Bit-Parallel_General_Integer-Scoring_Sequence_Alignment_Algorithm" rel="nofollow noreferrer">here</a>中进行了描述,并在支持下进行了扩展
对于此实现中的UTF32。该算法的时间复杂度为<code>O(N)</code></p>
<h2>长度为>;六十四</h2>
<p>Levenshtein距离是使用
Wagner Fischer和Ukkonens优化。该算法的时间复杂度为<code>O(N * M)</code>。
这可以在将来用BitPal的分块实现来代替</p>
<h2>对处理器的改进</h2>
<p>fuzzyfuzzy提供了多个处理器,如<code>process.extractOne</code>,用于计算查询和多项选择之间的相似性。在C++中实现这一点也允许两个更重要的优化:</p>
<ol>
<> L> > P>当在C++中使用的记分器也可以直接调用记分器的C++实现,不必在Python和C++之间来回切换,WHich提供了巨大的加速</p>
</li>
<li><p>我们可以根据使用的记分器对查询进行预处理。例如<code>fuzz.ratio</code>用作记分器时,只需将查询存储到BitPal使用的64位块中一次,这在计算Levenshtein距离时节省了大约50%的运行时间</p>
</li>
</ol>
<p>到目前为止,只有<code>extractOne</code>和<code>extract_iter</code>在Python中实现,而您将使用的<code>extract</code>仍然在Python中实现并使用<code>extract_iter</code>。因此,它已经可以使用2。优化,但仍然需要在Python和C++之间切换很多,这不是最优的(这也可能在V1.0.0中添加)。p>
<h2>基准</h2>
<p>在开发过程中,我为<code>extractOne</code>和单个记分员执行了基准测试,显示了RapidFuzzy和FuzzyFuzzy之间的性能差异。请记住,您的案例(所有字符串长度均为20)的性能可能不太好,因为数据集中使用的许多字符串都非常小</p>
<p>可复制科学数据的来源:</p>
<ul>
<li><a href="https://github.com/maxbachmann/RapidFuzz/blob/1.0.0/bench/data/words.txt" rel="nofollow noreferrer">^{<cd20>}</a>(包含99171个单词的数据集)</li>
</ul>
<p>运行图形化基准的硬件(规格):</p>
<ul>
<li>CPU:i7-8550U的单核</li>
<li>内存:8GB</li>
<li>操作系统:软呢帽32</li>
</ul>
<h3>基准得分者</h3>
<p>这个基准的代码可以在<a href="https://github.com/maxbachmann/RapidFuzz/blob/1.0.0/bench/benchmark_scorer.py" rel="nofollow noreferrer">here</a>中找到
<a href="https://i.stack.imgur.com/LSgUH.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/LSgUH.png" alt="Benchmark Scorers"/></a></p>
<h3>基准提取一号</h3>
<p>对于这个基准测试,<code>process.extractOne</code>的代码稍微更改,以删除<code>score_cutoff</code>参数。之所以这样做,是因为在<code>extractOne</code>中,每当找到更好的匹配时<code>score_cutoff</code>就会增加(一旦找到完美匹配,它就会退出)。在将来,对不具有这种行为的基准{^ <CD25> }的意义更大(基准是使用^ {CD13}}执行的,因为^ {CD25}}还没有完全在C++中实现)。基准代码可以在<a href="https://github.com/maxbachmann/RapidFuzz/blob/1.0.0/bench/benchmark_extractOne.py" rel="nofollow noreferrer">here</a>中找到
<a href="https://i.stack.imgur.com/CBAIP.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/CBAIP.png" alt="Benchmark extractOne"/></a></p>
<p>这表明,在可能的情况下,记分器不应该直接使用,而应该通过处理器使用,这样可以执行更多的优化</p>
<h2>另类</h2>
作为一种选择,你可以使用C++实现。库RAPIDFULF可用于C++ <a href="https://github.com/maxbachmann/rapidfuzz-cpp" rel="nofollow noreferrer">here</a>。C++中的实现也比较简单,</p>
<pre class="lang-cpp prettyprint-override"><code>// function to load words into vector
std::vector<std::string> choices = load("words.txt");
std::string query = choices[0];
std::vector<double> results;
results.reserve(choices.size());
rapidfuzz::fuzz::CachedRatio<decltype(query)> scorer(query);
for (const auto& choice : choices)
{
results.push_back(scorer.ratio(choice));
}
</code></pre>
<p>或者使用open-mp并行</p>
<pre class="lang-cpp prettyprint-override"><code>// function to load words into vector
std::vector<std::string> choices = load("words.txt");
std::string query = choices[0];
std::vector<double> results;
results.reserve(choices.size());
rapidfuzz::fuzz::CachedRatio<decltype(query)> scorer(query);
#pragma omp parallel for
for (const auto& choice : choices)
{
results.push_back(scorer.ratio(choice));
}
</code></pre>
<p>在我的机器上(参见上面的基准测试),并行版本的计算速度为4300万字/秒和1230万字/秒。这是Python实现的1.5倍(因为Python和C++类型之间的转换)。但是C++版本的主要优点是,无论你想用什么方法,你都相对自由地组合,而在Python版本中,你被迫使用C++中实现的^ {<CD28>}函数来实现良好的性能。p>