Требуется предложение — улучшение производительности кода Python

Нужен совет по улучшению производительности моего кода.

У меня есть два файла (Keyword.txt, description.txt). Keyword.txt состоит из списка ключевых слов (11 000+, если быть точным), а descriptions.txt состоит из очень больших текстовых описаний (9 000+).

Я пытаюсь читать ключевые слова из keyword.txt по одному и проверять, существует ли ключевое слово в описании. Если ключевое слово существует, я записываю его в новый файл. Так что это похоже на отношение многие-ко-многим (11 000 * 9 000).

Примеры ключевых слов:

Xerox
VMWARE CLOUD

Описание образца (оно огромное):

Planning and implementing entire IT Infrastructure. Cyberoam firewall implementation and administration in head office and branch office. Report generation and analysis. Including band width conception, internet traffic and application performance. Windows 2003/2008 Server Domain controller implementation and managing. VERITAS Backup for Clients backup, Daily backup of applications and database. Verify the backed up database for data integrity. Send backup tapes to remote location for safe storage Installing and configuring various network devices; Routers, Modems, Access Points, Wireless ADSL+ modems / Routers Monitoring, managing & optimizing Network. Maintaining Network Infrastructure for various clients. Creating Users and maintaining the Linux Proxy servers for clients. Trouble shooting, diagnosing, isolating & resolving Windows / Network Problems. Configuring CCTV camera, Biometrics attendance machine, Access Control System Kaspersky Internet Security / ESET NOD32

Ниже приведен код, который я написал:

import csv
import nltk
import re
wr = open(OUTPUTFILENAME,'w')
def match():
    c = 0
    ft = open('DESCRIPTION.TXT','r')
    ky2 = open('KEYWORD.TXT','r')
    reader = csv.reader(ft)
    keywords = []
    keyword_reader2 = csv.reader(ky2)
    for x in keyword_reader2: # Storing all the keywords to a list
        keywords.append(x[1].lower())

    string = ' '
    c = 0
    for row in reader:
        sentence = row[1].lower()
        id = row[0]
        for word in keywords:
            if re.search(r'\b{}\b'.format(re.escape(word.lower())),sentence):
                    string = string + id+'$'+word.lower()+'$'+sentence+ '\n'
                    c = c + 1
        if c > 5000:  # I am writing 5000 lines at a time.
            print("Batch printed")
            c = 0
            wr.write(string)
            string = ' '
    wr.write(string)
    ky2.close()
    ft.close()
    wr.close()

match()

Теперь этот код занимает около 120 минут. Я попробовал несколько способов улучшить скорость.

  1. Сначала я писал по одной строке, затем я изменил его на 5000 строк за раз, так как это небольшой файл, и я могу позволить себе разместить все в памяти. Особого улучшения не увидел.
  2. Я отправил все на стандартный вывод и использовал конвейер из консоли, чтобы добавить все в файл. Это было еще медленнее.

Я хочу знать, есть ли лучший способ сделать это, так как я мог сделать что-то не так в коде.

Характеристики моего ПК: Оперативная память: 15 ГБ Процессор: i7 4-го поколения


person Siddarth    schedule 19.02.2015    source источник
comment
Одна вещь, которую вы можете сделать, это прекратить использование регулярных выражений (что значительно замедляет процесс). Вы можете просто проверить if word in sentence:   -  person Nir Alfasi    schedule 19.02.2015
comment
это или это показывает несколько основных способов регулярного выражения больших файлов с указатель на линию вида прыгает. Это, вероятно, должно помочь вам. Кроме того, вероятно, гораздо лучше написать регулярное выражение, которое определяет нижний или верхний регистр (я думаю, скомпилировать с i) и возвращает true, чем строчные буквы ваших строк (неизменяемые значения необходимо копировать каждый раз, и это избавляет вас от предложения)   -  person ljetibo    schedule 19.02.2015
comment
@alfasin Это будет искать последовательность символов в предложении, а не конкретные слова. Например, если у меня есть ключевое слово конвейер, а в описании есть что-то вроде ~ я помогу установить конвейер данных ~, даже тогда он вернет true. Именно по этой причине я перешел на регулярное выражение.   -  person Siddarth    schedule 19.02.2015
comment
@Siddarth, поэтому фильтруйте результаты с помощью регулярных выражений. Так точно будет быстрее ;)   -  person Nir Alfasi    schedule 19.02.2015
comment
@ljetibo я посмотрю на это, спасибо :)   -  person Siddarth    schedule 19.02.2015
comment
@Siddarth Из того, что я видел, просто прыгайте прямо в mmap. Кажется, это именно то, что вы ищете.   -  person ljetibo    schedule 19.02.2015
comment
@ljetibo data = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ) могу ли я перебирать данные построчно?   -  person Siddarth    schedule 19.02.2015
comment
@ Сиддарт Да. Прочитайте это. Отображенные в память файловые объекты ведут себя и как строки, и как файловые объекты. это означает, что вы действуете с ними так же, как с файлами, включая for line in, я думаю;). Также поддерживаются Seek, нотация нарезки и т. д....   -  person ljetibo    schedule 19.02.2015


Ответы (2)


Если все ваши поисковые фразы состоят из целых слов (начинаются/заканчиваются на границе слова), то параллельная индексация в дерево слов будет настолько эффективной, насколько это возможно.

Что-то типа

# keep lowercase characters and digits
# keep apostrophes for contractions (isn't, couldn't, etc)
# convert uppercase characters to lowercase
# replace all other printable symbols with spaces
TO_ALPHANUM_LOWER = str.maketrans(
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ'!#$%&()*+,-./:;<=>?@[]^_`{|}~ \t\n\r\x0b\x0c\"\\",
    "abcdefghijklmnopqrstuvwxyz'                                     "
)

def clean(s):
    """
    Convert string `s` to canonical form for searching
    """
    return s.translate(TO_ALPHANUM_LOWER)

class WordTree:
    __slots__ = ["children", "terminal"]

    def __init__(self, phrases=None):
        self.children = {}   # {"word": WordTrie}
        self.terminal = ''   # if end of search phrase, full phrase is stored here
        # preload tree
        if phrases:
            for phrase in phrases:
                self.add_phrase(phrase)

    def add_phrase(self, phrase):
        tree  = self
        words = clean(phrase).split()
        for word in words:
            ch = tree.children
            if word in ch:
                tree = ch[word]
            else:
                tree = ch[word] = WordTree()
        tree.terminal = " ".join(words)

    def inc_search(self, word):
        """
        Search one level deeper into the tree

        Returns
          (None,    ''    )  if word not found
          (subtree, ''    )  if word found but not terminal
          (subtree, phrase)  if word found and completes a search phrase
        """
        ch = self.children
        if word in ch:
            wt = ch[word]
            return wt, wt.terminal
        else:
            return (None, '')

    def parallel_search(self, text):
        """
        Return all search phrases found in text
        """
        found  = []
        fd = found.append
        partials = []
        for word in clean(text).split():
            new_partials = []
            np = new_partials.append
            # new search from root
            wt, phrase = self.inc_search(word)
            if wt:     np(wt)
            if phrase: fd(phrase)
            # continue existing partial matches
            for partial in partials:
                wt, phrase = partial.inc_search(word)
                if wt:     np(wt)
                if phrase: fd(phrase)
            partials = new_partials
        return found

    def tree_repr(self, depth=0, indent="  ", terminal=" *"):
        for word,tree in self.children.items():
            yield indent * depth + word + (terminal if tree.terminal else '')
            yield from tree.tree_repr(depth + 1, indent, terminal)

    def __repr__(self):
        return "\n".join(self.tree_repr())

тогда ваша программа становится

import csv

SEARCH_PHRASES = "keywords.csv"
SEARCH_INTO    = "descriptions.csv"
RESULTS        = "results.txt"

# get search phrases, build WordTree
with open(SEARCH_PHRASES) as inf:
    wt = WordTree(*(phrase for _,phrase in csv.reader(inf)))

with open(SEARCH_INTO) as inf, open(RESULTS, "w") as outf:
    # bound methods (save some look-ups)
    find_phrases = wt.parallel_search
    fmt          = "{}${}${}\n".format
    write        = outf.write
    # sentences to search
    for id,sentence in csv.reader(inf):
        # search phrases found
        for found in find_phrases(sentence):
            # store each result
            write(fmt(id, found, sentence))

что должно быть примерно в тысячу раз быстрее.

person Hugh Bothwell    schedule 20.02.2015
comment
Спасибо @hugh, я просмотрю ваш код и попытаюсь понять, что вы сделали. - person Siddarth; 04.03.2015

Я предполагаю, что вы хотите ускорить поиск. В этом случае, если вас не волнует частота ключевых слов в описании, а только то, что они существуют, вы можете попробовать следующее:

Для каждого файла описания разделите текст на отдельные слова и создайте набор уникальных слов.

Затем для каждого ключевого слова в вашем списке ключевых слов проверьте, содержит ли набор ключевое слово, и запишите в файл, если оно верно.

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

PS: мой подход предполагает, что вы отфильтровываете пунктуацию.

person Gorton Fishman    schedule 19.02.2015
comment
спасибо @gorton fishman Сначала я думал об этом подходе. Но некоторые из моих ключевых слов содержат два или три слова, например облако vmware. Если я токенизирую описание, то оно будет токенизировано в ['vmware','cloud']. Я хочу посмотреть, существует ли «облако vmware», как в описании. - person Siddarth; 19.02.2015
comment
В этом случае вы можете попытаться сгенерировать n-граммы из текста, это, конечно, потребует дополнительной предварительной обработки, но сам поиск не должен быть затронут en.wikipedia.org/wiki/N-gram - person Gorton Fishman; 19.02.2015
comment
Хм, да! предоставляет ли nltk токенизатор n-грамм? - person Siddarth; 19.02.2015
comment
Я не думаю, что есть токенизатор, который генерирует граммы, вам придется перебирать его самостоятельно, но вы также можете попробовать API словосочетаний. Конечно, нет 100% гарантии, что выбранные им кандидаты будут соответствовать вашим ключевым словам, однако весьма вероятно, что это произойдет, если у вас нет сверхсложных ключевых слов. nltk.org/howto/collocations.html - person Gorton Fishman; 19.02.2015