Как ускорить этот алгоритм поиска кортежей слов? - PullRequest
0 голосов
/ 25 мая 2019

Я пытаюсь создать простую модель для предсказания следующего слова в предложении. У меня есть большой файл .txt, который содержит предложения, разделенные '\ n'. У меня также есть файл словаря, в котором перечислены все уникальные слова в моем файле .txt и уникальный идентификатор. Я использовал файл словаря, чтобы преобразовать слова в моем корпусе в соответствующие им идентификаторы. Теперь я хочу сделать простую модель, которая считывает идентификаторы из текстового файла и находит пары слов и сколько раз эти упомянутые пары слов были замечены в корпусе. Мне удалось написать код ниже:

tuples = [[]] #array for word tuples to be stored in
data = []   #array for tuple frequencies to be stored in

data.append(0) #tuples array starts with an empty element at the beginning for some reason.
            # Adding zero to the beginning of the frequency array levels the indexes of the two arrays

with open("markovData.txt") as f:
    contentData = f.readlines()
    contentData = [x.strip() for x in contentData]
    lineIndex = 0
    for line in contentData:
        tmpArray = line.split() #split line to array of words
        tupleIndex = 0
        tmpArrayIndex = 0
        for tmpArrayIndex in range(len(tmpArray) - 1): #do this for every word except the last one since the last word has no word after it.
            if [tmpArray[tmpArrayIndex], tmpArray[tmpArrayIndex + 1]] in tuples: #if the word pair is was seen before
                data[tuples.index([tmpArray[tmpArrayIndex], tmpArray[tmpArrayIndex + 1]])] += 1 #increment the frequency of said pair
            else:
                tuples.append([tmpArray[tmpArrayIndex], tmpArray[tmpArrayIndex + 1]]) #if the word pair is never seen before
                data.append(1)                                                        #add the pair to list and set frequency to 1.

        #print every 1000th line to check the progress
        lineIndex += 1
        if ((lineIndex % 1000) == 0):
            print(lineIndex)

with open("markovWindowSize1.txt", 'a', encoding="utf8") as markovWindowSize1File:
    #write tuples to txt file
    for tuple in tuples:
        if (len(tuple) > 0): # if tuple is not epmty
            markovWindowSize1File.write(str(element[0]) + "," + str(element[1]) + " ")

    markovWindowSize1File.write("\n")
    markovWindowSize1File.write("\n")
    #blank spaces between two data

    #write frequencies of the tuples to txt file
    for element in data:
        markovWindowSize1File.write(str(element) + " ")

    markovWindowSize1File.write("\n")
    markovWindowSize1File.write("\n")

Этот код, кажется, работает хорошо для первых нескольких тысяч строк. Затем все становится медленнее, потому что список кортежей продолжает увеличиваться, и мне приходится искать по всему списку кортежей, чтобы проверить, была ли следующая пара слов видна раньше или нет. Мне удалось получить данные по 50 тыс. Строк за 30 минут, но у меня гораздо больше корпусов с миллионами строк. Есть ли способ хранить и искать пары слов более эффективным способом? Матрицы, вероятно, будут работать намного быстрее, но мой уникальный счетчик слов составляет около 300 000 слов. Это означает, что я должен создать матрицу 300k * 300k с целыми числами в качестве типа данных. Даже после использования симметричных матриц, потребовалось бы намного больше памяти, чем у меня.

Я пытался использовать memmap из numpy для хранения матрицы на диске, а не в памяти, но для этого требовалось около 500 ГБ свободного дискового пространства.

Затем я изучил разреженные матрицы и обнаружил, что могу просто хранить ненулевые значения и соответствующие им номера строк и столбцов. Что я и сделал в своем коде.

В настоящее время эта модель работает, но она очень плохо угадывает следующее слово (вероятность успеха около 8%). Мне нужно тренироваться с большими корпусами, чтобы получить лучшие результаты. Что я могу сделать, чтобы сделать этот код поиска пары слов более эффективным?

Спасибо.


Редактировать: Благодаря всем ответившим, теперь я могу обработать мой корпус из ~ 500k строк примерно за 15 секунд. Я добавляю финальную версию кода ниже для людей с похожими проблемами:

import numpy as np
import time

start = time.time()
myDict = {} # empty dict

with open("markovData.txt") as f:
    contentData = f.readlines()
    contentData = [x.strip() for x in contentData]
    lineIndex = 0
    for line in contentData:
        tmpArray = line.split() #split line to array of words
        tmpArrayIndex = 0
        for tmpArrayIndex in range(len(tmpArray) - 1): #do this for every word except the last one since the last word has no word after it.
            if (tmpArray[tmpArrayIndex], tmpArray[tmpArrayIndex + 1]) in myDict: #if the word pair is was seen before
                myDict[tmpArray[tmpArrayIndex], tmpArray[tmpArrayIndex + 1]] += 1  #increment the frequency of said pair
        else:
            myDict[tmpArray[tmpArrayIndex], tmpArray[tmpArrayIndex + 1]] = 1 #if the word pair is never seen before
                                                                              #add the pair to list and set frequency to 1.

    #print every 1000th line to check the progress
    lineIndex += 1
    if ((lineIndex % 1000) == 0):
        print(lineIndex)


end = time.time()
print(end - start)

keyText= ""
valueText = ""

for key1,key2 in myDict:
    keyText += (str(key1) + "," + str(key2) + " ")
    valueText += (str(myDict[key1,key2]) + " ")


with open("markovPairs.txt", 'a', encoding="utf8") as markovPairsFile:
    markovPairsFile.write(keyText)

with open("markovFrequency.txt", 'a', encoding="utf8") as markovFrequencyFile:
    markovFrequencyFile.write(valueText)

Ответы [ 2 ]

1 голос
/ 25 мая 2019

Я бы также посмотрел на collections.Counter, структуру данных, созданную для вашей задачи.Объект Counter подобен словарю, но подсчитывает вхождения ключевой записи.Вы можете использовать это, просто увеличивая пару слов, когда встречаете это:

from collections import Counter

word_counts = Counter()
with open("markovData.txt", "r") as f:
    # iterate over word pairs
    word_counts[(word1, word2)] += 1

В качестве альтернативы, вы можете создать список кортежей, как у вас есть, и просто передать его в Counter как объект для вычисления частот вконец:

word_counts = Counter(word_tuple_list)
1 голос
/ 25 мая 2019

Насколько я понимаю, вы пытаетесь построить скрытую марковскую модель, используя частоты n-грамм (словесные наборы длины n). Может быть, просто попробуйте более эффективную структуру данных для поиска, например, вложенный словарь. Это может быть в форме

{ID_word1:{ID_word1:x1,... ID_wordk:y1}, ...ID_wordk:{ID_word1:xn, ...ID_wordk:yn}}.

Это будет означать, что у вас есть только k ** 2 словарных статей для кортежей из 2 слов (Google использует до 5 для автоматического перевода), где k - это мощность V, вашего (конечного) словаря. Это должно повысить вашу производительность, так как вам не нужно искать растущий список кортежей. x и y являются репрезентативными для количества вхождений, которое вы должны увеличивать при обнаружении кортежа. (Никогда не используйте встроенную функцию count ()!)

...