Как можно ускорить обработку текста в Python
У меня есть *.csv файлик, в котором около 40000 наименований товаров. Из них 2-3k уникальных названий имеют по 2-3 схожих (слегка видоизмененных) наименования, но не полностью похожи. Я их сравниваю при помощи библиотеки difflib, проходя циклами по наименованиям следующим образом:
from transliterate import translit
import difflib
import pandas as pd
def similarity(s1, s2):
normalized1 = s1.lower()
normalized2 = s2.lower()
matcher = difflib.SequenceMatcher(None, normalized1, normalized2)
return matcher.ratio()
names = pd.read_csv(r'path to file with names')
similar = []
a = 1
df = pd.DataFrame()
for a_name in names['names']:
print(a)
a += 1
for b_name in names['names']:
if similarity(translit(a_name, "ru", reversed=True).lower(),translit(b_name, "ru", reversed=True).lower()) >= 0.7:
similar.append([a_name, b_name])
df = df.append([a_name, b_name])
print(similar)
df.to_csv('similar.csv')
Цикл для a_name отрабатывает за 3-5 секунд, а это значит, что для выполнения всего скрипта потребуется около двух дней беспрерывной работы, что достаточно долго. Потому возникает вопрос: нет ли способа ускорить как-то выполнение скрипта?
Ответы (2 шт):
Советы по ускорению:
- Надеюсь, имена у вас уже отобраны уникальные, а если нет, то нужно взять из списка имён уникальные имена, без повторений
- Закешируйте работу функции
translit(плюсlower()), либо самостоятельно записывая аргументы и результат в словарь, либо с помощью декоратораlru_cache. Если непонятно, объясню потом подробнее. - Закешируйте работу функции
similarity, но тут декоратором не обойтись, потому что нужно будет выдавать одинаковый результат для аргументов(a, b)и(b, a), так что тут видимо самостоятельно через словарь надо будет сделать. - Никогда не конкатенируйте датафрейм внутри цикла! Это очень долго. Тем более, что вы же параллельно собираете ту же самую инфу в список. Просто уберите
df.append, просто после окончания цикла создайте датафрейм из спискаsimilar-pd.DataFrame(similar), это будет то же самое, но быстро.
Всё это вместе должно значительно ускорить работу кода.
Итоговый код будет примерно такой:
from transliterate import translit
import difflib
import pandas as pd
from functools import lru_cache
from tqdm.auto import tqdm
names = pd.read_csv(r'path to file with names')
words = names['names'].unique()
@lru_cache(None)
def trans(word):
return translit(word, "ru", reversed=True).lower()
sim_cache = {}
def similarity(s1, s2):
if s1 == s2:
return 1
if (s1,s2) in sim_cache:
return sim_cache[(s1,s2)]
normalized1, normalized2 = trans(a_name), trans(b_name)
matcher = difflib.SequenceMatcher(None, normalized1, normalized2)
r = matcher.ratio()
sim_cache[(s1,s2)] = r
sim_cache[(s2,s1)] = r
return r
similar = []
df = pd.DataFrame()
for a_name in tqdm(words):
for b_name in words:
if similarity(a_name,b_name) >= 0.7:
similar.append([a_name, b_name])
print(similar)
df = pd.DataFrame(similar)
df.to_csv('similar.csv')
Если же у вас именно уникальных слов 40000, то тогда нужно использовать совсем другие метрики, например, косинусное расстояние, для его быстрого и массового вычисления есть специальные библиотеки, я как-то об этом уже писал, если нужно будет, поищу. difflib с таким объёмом уже никак не сможет справиться.
в дополнение
я бы еще максимально облегчил цикл, вынес из него все что можно векторизовать, вот что-то вроде псевдокода (не тестировался по понятным причинам, просто мысли)
на выходе должны получить датафрейм, где в колонке 'names' наименования, в колонке 'simil' список с похожими на него наименованиями:
def similarity(s1, s2):
matcher = difflib.SequenceMatcher(None, s1, s2)
return matcher.ratio()
names = pd.read_csv(r'path to file with names').drop_duplicates()
transl = lambda x: translit(x, "ru", reversed=True)
names['transl'] = names['names'].str.lower().map(transl)
names['simil'] = names['transl'].map(lambda x: [names.loc[i,'names'] for i,y in names['transl'].items() if similarity(x,y)>=0.7])