как параллельно обрабатывать эту вложенную l oop на python - PullRequest
0 голосов
/ 16 апреля 2020

Я пытаюсь сократить список имен, и для этого я использую библиотеку 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

Если таковые могут помочь мне выяснить, что здесь происходит, и как это решить я буду так благодарен. Заранее спасибо.

edit:

теперь у меня есть еще одна функция, которая работает с результатом последней

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

Ответы [ 2 ]

1 голос
/ 16 апреля 2020

Вы разделяете данные перед вводом дважды вложенного l oop, поэтому вы не сравниваете все комбинации.

Вы можете реорганизовать код для разделения первого имени, но все же проверить все вторые имена против этого. Следующая модификация работала для меня на ваших тестовых данных, хотя она не нашла дубликатов.

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()
0 голосов
/ 17 апреля 2020

Когда вы узнаете, что ваш алгоритм медленный, многопроцессорная обработка - это способ ускорить его. Однако вам, вероятно, следует сначала попытаться ускорить алгоритм. При использовании 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
...