Более быстрый способ удаления стоп-слов в Python

Я пытаюсь удалить стоп-слова из строки текста:

from nltk.corpus import stopwords
text = 'hello bye the the hi'
text = ' '.join([word for word in text.split() if word not in (stopwords.words('english'))])

Я обрабатываю 6 миллионов таких строк, поэтому важна скорость. Профилирование моего кода, самая медленная часть - строки выше, есть ли лучший способ сделать это? Я думаю об использовании чего-то вроде регулярных выражений re.sub, но я не знаю, как написать шаблон для набора слов. Кто-нибудь может мне помочь, и я также рад услышать другие, возможно, более быстрые методы.

Примечание: я пытался предложить кому-то обернуть stopwords.words('english') в set(), но это не имело никакого значения.

Спасибо.

33
задан 18.04.2020, 00:51

4 ответа

Попробуйте кэшировать объект стоп-слов, как показано ниже. Создание этого при каждом вызове функции кажется узким местом.

    from nltk.corpus import stopwords

    cachedStopWords = stopwords.words("english")

    def testFuncOld():
        text = 'hello bye the the hi'
        text = ' '.join([word for word in text.split() if word not in stopwords.words("english")])

    def testFuncNew():
        text = 'hello bye the the hi'
        text = ' '.join([word for word in text.split() if word not in cachedStopWords])

    if __name__ == "__main__":
        for i in xrange(10000):
            testFuncOld()
            testFuncNew()

Я запустил это через профилировщик: python -m cProfile -s cumulative test.py . Соответствующие строки размещены ниже.

Совокупное время nCalls

10000 7,723 words.py:7(testFuncOld)

10000 0,140 words.py:11(testFuncNew)

Итак, кэшируем экземпляр стоп-слов дает ускорение в ~ 70 раз.

78
ответ дан 18.04.2020, 00:52
  • 1
    eww. извините. в первый раз с помощью комментариев здесь, что форматирование ужасно. плохо вновь заявите путем редактирования вопроса. – themesandmodules 20.10.2019, 21:48
  • 2
    Идет. Повышения производительности прибывают из кэширования стоп-слов, не действительно в создании set. – mchangun 18.04.2020, 00:53

Во-первых, вы создаете стоп-слова для каждой строки. Создайте это один раз. Сет действительно был бы великолепен здесь.

forbidden_words = set(stopwords.words('english'))

Позже, избавьтесь от [] внутри join. Вместо этого используйте генератор.

' '.join([x for x in ['a', 'b', 'c']])

заменить на

' '.join(x for x in ['a', 'b', 'c'])

Следующее, с чем нужно иметь дело, это заставить .split() возвращать значения вместо возврата массива. Я считаю, что regex будет хорошей заменой здесь. См. Тист Хэд о том, почему s.split() действительно быстро.

Наконец, сделайте такую ​​работу параллельно (удаляя стоп-слова в 6-метровых строках). Это совсем другая тема.

4
ответ дан 18.04.2020, 00:52
  • 1
    Просто к вашему сведению в regexes были некоторые опечатки; отрицательные предвидения всегда (?!...), не (!?...). – Alan Moore 20.10.2019, 21:48
  • 2
    Я сомневаюсь относительно использования regexp собирающийся быть улучшением, видеть stackoverflow.com/questions/7501609/python-re-split-vs-split/… – alko 18.04.2020, 00:53
  • 3
    Найденный им сейчас также.:) – Krzysztof Szularz 18.04.2020, 00:53
  • 4
    Спасибо. Эти set сделанный, по крайней мере, 8x улучшение для ускорения. Почему делает использование справки генератора? RAM isn' t проблема для меня, потому что каждая часть текста является довольно маленькой, приблизительно 100-200 слов. – mchangun 18.04.2020, 00:53
  • 5
    На самом деле, I' ve, замеченные join, работают лучше с пониманием списка, чем эквивалентное выражение генератора. – Janne Karila 18.04.2020, 00:54

Используйте регулярное выражение, чтобы удалить все слова, которые не совпадают:

import re
pattern = re.compile(r'\b(' + r'|'.join(stopwords.words('english')) + r')\b\s*')
text = pattern.sub('', text)

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

Если последнее слово в тексте будет удалено из-за этого, у вас может быть завершающий пробел. Я предлагаю разобраться с этим отдельно.

14
ответ дан 18.04.2020, 00:53
  • 1
    @willieseabrook: Don' t думают так, только часть предвидения дважды отрицательна, таким образом, Вы can' t заменяют целое положительным – Andomar 20.10.2019, 21:49
  • 2
    Какая-либо идея, какова сложность этого была бы? Если w = количество слов в моем тексте и s = количество слов в стоп-листе, я думаю, что цикличное выполнение было бы на порядке w log s. В этом случае w приблизительно s так it' s w log w. Wouldn' t grep быть медленнее, так как это (примерно) должно соответствовать символу символом? – mchangun 18.04.2020, 00:53
  • 3
    На самом деле я думаю, что сложности в значении O (†¦) являются тем же. Оба O(w log s), да. , НО regexps реализованы на намного более низком уровне и оптимизированы в большой степени. Уже разделение слов приведет к копированию всего, создавая список строк и сам список, все, что занимает время. – Alfe 18.04.2020, 00:54

Извините за поздний ответ. Будет полезным для новых пользователей.

  • Создать словарь стоп-слов с использованием библиотеки коллекций
  • Использовать этот словарь для очень быстрого поиска (время = O (1)), а не делать это в списке (время = O (стоп-слова) ))

    from collections import Counter
    stop_words = stopwords.words('english')
    stopwords_dict = Collections.counter(stop_words)
    text = ' '.join([word for word in text.split() if stopwords_dict[word]==0])
    
1
ответ дан 18.04.2020, 00:54
  • 1
    i' d наличие проблемы с отрицательным предвидением и Вашим оператором " в этом position" то, что разъяснило то, что я делал неправильно. спасибо. – just mike 20.10.2019, 21:47