什么在减慢这段python代码?

时间:2017-04-21 22:21:42

标签: python-2.7 nlp

我一直在尝试实现Stupid Backoff语言模型(描述可用here,但我相信细节与问题无关)。

问题是,代码正在工作并产生预期的结果,但工作速度比我预期的要慢。我想出了减速的部分就在这里(而不是在训练部分):

def compute_score(self, sentence):
    length = len(sentence)
    assert length <= self.n
    if length == 1:
        word = tuple(sentence)
        return float(self.ngrams[length][word]) / self.total_words
    else:
        words = tuple(sentence[::-1])
        count = self.ngrams[length][words]
        if count == 0:
            return self.alpha * self.compute_score(sentence[1:])
        else:
            return float(count) / self.ngrams[length - 1][words[:-1]]

def score(self, sentence):
""" Takes a list of strings as argument and returns the log-probability of the 
    sentence using your language model. Use whatever data you computed in train() here.
"""
    output = 0.0
    length = len(sentence)
    for idx in range(length):
        if idx < self.n - 1:
            current_score = self.compute_score(sentence[:idx+1])
        else:
            current_score = self.compute_score(sentence[idx-self.n+1:idx+1])
        output += math.log(current_score)
    return output

self.ngrams是一个嵌套字典,有n个条目。这些条目中的每一个都是形式字典(word_i,word_i-1,word_i-2 ...... word_i-n):这种组合的计数。

self.alpha是一个常数,定义了去n-1的惩罚。

self.n是程序在字典self.ngrams中查找的元组的最大长度。它设置为3(虽然设置为2或甚至1不是任何东西)。这很奇怪,因为Unigram和Bigram模型在几分之一秒内工作得很好。

我正在寻找的答案不是我自己的代码的重构版本,而是一个提示,其中一部分是计算上最昂贵的(因此我可以弄清楚自己如何重写它并获得最多教育从解决这个问题中获益)。

请耐心等待,我只是一个初学者(进入编程世界两个月)。感谢。

UPD: 我使用time.time():

使用相同的数据计算运行时间

Unigram = 1.9

Bigram = 3.2

愚蠢的退避(n = 2)= 15.3

愚蠢的退避(n = 3)= 21.6

(由于time.time的精确度不高,它在一些比原来更大的数据上。)

1 个答案:

答案 0 :(得分:0)

如果句子很长,那么实际运行的大部分代码都在这里:

def score(self, sentence):
    for idx in range(len(sentence)): # should use xrange in Python 2!
        self.compute_score(sentence[idx-self.n+1:idx+1])

def compute_score(self, sentence):
    words = tuple(sentence[::-1])
    count = self.ngrams[len(sentence)][words]
    if count == 0:
        self.compute_score(sentence[1:])
    else:
        self.ngrams[len(sentence) - 1][words[:-1]]

这并不意味着工作代码 - 它只是删除了不重要的部分。

关键路径中的流量因此是:

  • 对于句子中的每个单词:
    • 对该字词加上compute_score()加上以下内容2.这会创建一个长度为3的新列表。您可以使用itertools.islice()来避免这种情况。
      • 构造一个带有反转字样的3元组。这会创建一个新的元组。您可以通过在此函数外部创建切片时传递-1步骤参数来避免这种情况。
      • self.ngrams中查找,这是一个嵌套的dict,第一个键是一个数字(如果这个级别是一个列表可能会更快;反正只有三个键?),第二个是元组创建。
      • 删除第一个单词,即制作新的元组(sentence[2], sentence[1]),或
      • self.ngrams中执行另一次查找,隐式创建另一个新元组(words[:-1])

总之,我认为你遇到的最大问题是列表和元组的重复和嵌套创建和销毁。