Как сделать эту функцию более (временной) эффективной?

У меня есть ряд данных, которые содержат предложения. (некоторые из них довольно длинные)

У меня также есть 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:

  1. 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]
  1. Вам не нужно звонить .keys() on dicts.

Это эквивалентно:

word in weights_W.keys()
word in weights_W
  1. 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
Другие вопросы по тегам