Как сделать эту функцию более (временной) эффективной?
У меня есть ряд данных, которые содержат предложения. (некоторые из них довольно длинные)
У меня также есть 2 словаря, которые содержат слова в качестве ключей и целых чисел.
Не все слова из строк присутствуют в обоих словарях. Некоторые только в одном, некоторые ни в одном.
Длина кадра данных составляет 124011 единиц. функция берет меня около 0,4 за строку. что очень долго.
W - просто справочное значение для словаря (weights = {}, weights[W] = {})
вот функция:
def match_share(string, W, weights, rel_weight):
words = string.split()
words_counts = Counter(words)
ratios = []
for word in words:
if ((word in weights[W].keys())&(word in rel_weight[W].keys())):
if (weights[W][word]!=0):
ratios.append(words_counts[word]*rel_weight[W][word]/weights[W][word])
else:
ratios.append(0)
if len(words)>0:
ratios = np.divide(ratios, float(len(words)))
ratio = np.sum(ratios)
return ratio
Спасибо
3 ответа
Давайте немного очистим это:
def match_share(string, W, weights, rel_weight):
words = string.split()
words_counts = Counter(words)
words = string.split()
words_counts = Counter(words)
Это избыточно! Заменить 4 утверждения на 2:
def match_share(string, W, weights, rel_weight):
words = string.split()
words_counts = Counter(words)
Следующий:
ratios = []
for word in words:
if ((word in weights[W].keys())&(word in rel_weight[W].keys())):
if (weights[W][word]!=0):
ratios.append(words_counts[word]*rel_weight[W][word]/weights[W][word])
else:
ratios.append(0)
Я не знаю, что вы думаете, этот код делает. Я надеюсь, что ты не хитрый. Но .keys
возвращает итерацию, и X in <iterable>
Путь медленнее, чем X in <dict>
, Также обратите внимание: вы ничего не добавляете, если самое внутреннее (weights[W][word] != 0
) условие не выполняется. Это может быть ошибкой, поскольку вы пытаетесь добавить 0 в другом условии. (Я не знаю, что вы делаете, поэтому просто указываю на это.) И это Python, а не Perl, C или Java. Таким образом, вокруг if <test>:
Пойдем с этим:
ratios = []
for word in words:
if word in weights[W] and word in rel_weight[W]:
if weights[W][word] != 0:
ratios.append(words_counts[word] * rel_weight[W][word] / weights[W][word])
else:
ratios.append(0)
Следующий:
if len(words)>0:
ratios = np.divide(ratios, float(len(words)))
Вы пытаетесь предотвратить деление на ноль. Но вы можете использовать достоверность списка, чтобы проверить это и избежать сравнения:
if words:
ratios = np.divide(ratios, float(len(words)))
В остальном все в порядке, но вам не нужна переменная.
ratio = np.sum(ratios)
return ratio
С этими модами ваша функция выглядит так:
def match_share(string, W, weights, rel_weight):
words = string.split()
words_counts = Counter(words)
ratios = []
for word in words:
if word in weights[W] and word in rel_weight[W]:
if weights[W][word] != 0:
ratios.append(words_counts[word] * rel_weight[W][word] / weights[W][word])
else:
ratios.append(0)
if words:
ratios = np.divide(ratios, float(len(words)))
ratio = np.sum(ratios)
return ratio
Глядя на это немного сложнее, я вижу, что вы делаете это:
word_counts = Counter(words)
for word in words:
append( word_counts[word] * ...)
По моему мнению, это означает, что если "яблоко" появляется 6 раз, вы собираетесь добавить 6*... в список, по одному разу для каждого слова. Таким образом, в вашем списке будет 6 различных случаев 6*... Вы уверены, что это то, что вы хотите? Или это должно быть for word in word_counts
просто перебрать отдельные слова?
Другая оптимизация заключается в удалении поисков из вашего цикла. Вы продолжаете смотреть weights[W]
а также rel_weight[W]
хотя значение W
никогда не меняется Давайте кешируем эти значения вне цикла. Также давайте кешируем указатель на ratios.append
метод.
def match_share(string, W, weights, rel_weight):
words = string.split()
words_counts = Counter(words)
ratios = []
# Cache these values for speed in loop
ratios_append = ratios.append
weights_W = weights[W]
rel_W = rel_weight[W]
for word in words:
if word in weights_W and word in rel_W:
if weights_W[word] != 0:
ratios_append(words_counts[word] * rel_W[word] / weights_W[word])
else:
ratios_append(0)
if words:
ratios = np.divide(ratios, float(len(words)))
ratio = np.sum(ratios)
return ratio
Попробуйте, посмотрите, как это работает. Пожалуйста, посмотрите на жирный шрифт выше и вопросы. Там могут быть ошибки, может быть больше способов ускорить.
Я думаю, что ваша неэффективность времени может объясняться тем, что вы используете Counter вместо dict. Некоторое обсуждение здесь предполагает, что класс dict имеет части, написанные на чистом c, в то время как counter написан на python.
Я предлагаю изменить ваш код на использование dict и test, чтобы увидеть, обеспечивает ли это более быстрое время
И почему этот код дублируется?:
words = string.split()
words_counts = Counter(words)
words = string.split()
words_counts = Counter(words)
ratios = []
It would be good if you had a profile of that function execution, but here are some generic ideas:
- You needlessly get some elements on every iteration. You can extract these before the loop
Например
weights_W = weights[W]
rel_weights_W = rel_weights[W]
- Вам не нужно звонить
.keys()
on dicts.
Это эквивалентно:
word in weights_W.keys()
word in weights_W
- Attempt to get values without looking them up first. This will save you one lookup.
Например вместо:
if ((word in weights[W].keys())&(word in rel_weight[W].keys())):
if (weights[W][word]!=0):
ты можешь сделать:
word_weight = weights_W.get(word)
if word_weight is not None:
word_rel_weight = rel_weights_W.get(word)
if word_rel_weight is not None:
if word_weight != 0: # lookup saved here