如何在 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
我正在尝试减少名称列表,为了执行此操作,我正在使用 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