如何在 python 上并行处理这个嵌套循环

how to parallel procesing this nested loop on python

我正在尝试减少名称列表,为了执行此操作,我正在使用 fuzzywuzzy 库。

我对所有名称执行了两个 for 循环。如果两个名字的模糊匹配分数在 90 和 100 之间,那么我用名字重写第二个名字。

这是我的数据集的示例,data

                              nombre
0               VICTOR MORENO MORENO
1         SERGIO HERNANDEZ GUTIERREZ
2       FRANCISCO JAVIER MUÑOZ LOPEZ
3     JUAN RAYMUNDO MORALES MARTINEZ
4         IVAN ERNESTO SANCHEZ URROZ

这是我的函数:

def fuzz_analisis0(top_names):
    for name2 in top_names["nombre"]:    
        for name in top_names["nombre"]: 
            if fuzz.ratio(name, name2)>90 and fuzz.ratio(name, name2)<100:
                top_names[top_names["nombre"]==name] = name2

当我 运行 这与:

fuzz_analisis0(data)

一切正常。这是显示其工作原理的输出。

print(len(data))
# 1400

data = data.drop_duplicates()
print(len(data))
# 1256

但是现在,如果我尝试使用并行处理,它就不再像预期的那样工作了。这是并行化的代码:

cores = mp.cpu_count()
df_split = np.array_split(data, cores, axis=0)
pool = Pool(cores)
df_out = np.vstack(pool.map(fuzz_analisis0, df_split))
pool.close()
pool.join()
pool.clear()

函数结束的速度比预期的要快,并且没有找到任何重复项。

print(len(data))
# 1400

data = data.drop_duplicates()
print(len(data))
# 1400

如果有人能帮助我弄清楚这里发生了什么以及如何解决它,我将不胜感激。提前致谢。

编辑:

现在我有另一个函数可以处理上一个函数的结果

def fuzz_analisis(dataframe, top_names):  
    for index in top_names['nombre'].index:
        name2 = top_names.loc[index,'nombre']       
        for index2 in dataframe["nombre"].index:
            name = dataframe.loc[index2,'nombre']   

            if fuzz.ratio(name, name2)>90 and fuzz.ratio(name, name2)<100:
                    dataframe.loc[index,'nombre'] = name

数据框看起来像这样:

    nombre  foto1   foto2   sexo    fecha_hora_registro
folio                   
131     JUAN DOMINGO GONZALEZ DELGADO   131.jpg     131.jpg     MASCULINO   2008-08-07 15:42:25
132     FRANCISCO JAVIER VELA RAMIREZ   132.jpg     132.jpg     MASCULINO   2008-08-07 15:50:42
133     JUAN CARLOS PEREZ MEDINA    133.jpg     133.jpg     MASCULINO   2008-08-07 16:37:24
134     ARMANDO SALINAS SALINAS     134.jpg     134.jpg     MASCULINO   2008-08-07 17:18:12
135     JOSE FELIX ZAMBRANO AMEZQUITA   135.jpg     135.jpg     MASCULINO   2008-08-07 17:55:05

您在进入两次嵌套循环之前拆分数据,因此您没有比较所有组合。

您可以重新组织代码以拆分名字,但仍然针对它测试所有第二个名字。以下修改对我对你的测试数据有效,尽管它没有找到任何重复项。

from functools import partial
from fuzzywuzzy import fuzz
import multiprocessing as mp
import numpy as np

def fuzz_analisis0_partial(top_names, partial_top_names): 
    for name2 in top_names["nombre"]: 
        for name in partial_top_names["nombre"]:  
            if fuzz.ratio(name, name2)>90 and fuzz.ratio(name, name2)<100: 
                partial_top_names[partial_top_names["nombre"] == name] = name2 
    return partial_top_names

cores = mp.cpu_count() 
df_split = np.array_split(data, cores, axis=0) 

pool = mp.Pool(cores)
processed_parts = pool.map(partial(fuzz_analisis0_partial, data), df_split)
processed = np.vstack(list(processed_parts))

pool.close() 
pool.join()

当你意识到你的算法很慢时,多​​处理是一种加速它的方法。但是,您可能应该先尝试加速算法。当使用 fuzzywuzzy fuzz.ratio 时,将计算归一化的编辑距离,这是一个 O(N*M) 操作。因此,您应该尽量减少使用量。所以这里是mcskinner's多进程解决方案的优化方案:

from functools import partial
from fuzzywuzzy import fuzz
import multiprocessing as mp
import numpy as np

def length_ratio(s1, s2):
    s1_len = len(s1)
    s2_len = len(s2)
    distance = s1_len - s2_len if s1_len > s2_len else s2_len - s1_len
    lensum = s1_len + s2_len
    return 100 - 100 * distance / lensum

def fuzz_analisis0_partial(top_names, partial_top_names): 
    for name2 in top_names["nombre"]: 
        for name in partial_top_names["nombre"]:
            if length_ratio(name, name2) < 90:
              continue

            ratio = fuzz.ratio(name, name2)
            if ratio>90 and ratio<100: 
                partial_top_names[partial_top_names["nombre"] == name] = name2 
    return partial_top_names

cores = mp.cpu_count() 
df_split = np.array_split(data, cores, axis=0) 

pool = mp.Pool(cores)
processed_parts = pool.map(partial(fuzz_analisis0_partial, data), df_split)
processed = np.vstack(list(processed_parts))

pool.close() 
pool.join()

首先,此解决方案只执行 fuzz.ratio 一次而不是两次,因为这是花费大部分时间的事情,这应该会给你大约 50% 的运行时间改进。然后作为第二个改进,它预先检查基于长度的比率。这个基于长度的比率总是至少和 fuzz.ratio 一样大,但可以在常数时间内计算。因此,可以更快地处理所有具有较大长度差异的名称。除此之外,请确保将 fuzzywuzzy 与 python-Levenshtein 一起使用,因为这比使用 difflib 的版本快得多。作为更快的替代方法,您可以使用 RapidFuzz(我是 RapidFuzz 的作者)。 RapidFuzz 在给它传递截止分数 fuzz.ratio(name, name2, score_cutoff=90) 时已经计算了长度比,因此使用它时不需要 lenght_ratio 函数。

使用 RapidFuzz 等效函数 fuzz_analisis0_partial 可以按以下方式编程:

from rapidfuzz import fuzz

def fuzz_analisis0_partial(top_names, partial_top_names): 
    for name2 in top_names["nombre"]: 
        for name in partial_top_names["nombre"]:
            ratio = fuzz.ratio(name, name2, score_cutoff=90)
            if ratio > 90 and ratio < 100: 
                partial_top_names[partial_top_names["nombre"] == name] = name2 
    return partial_top_names