在Python中快速/优化N-gram实现

16

哪个Python中的ngram实现最快?

我尝试过评估nltk和scott的zip(http://locallyoptimal.com/blog/2013/01/20/elegant-n-gram-generation-in-python/):

from nltk.util import ngrams as nltkngram
import this, time

def zipngram(text,n=2):
  return zip(*[text.split()[i:] for i in range(n)])

text = this.s

start = time.time()
nltkngram(text.split(), n=2)
print time.time() - start

start = time.time()
zipngram(text, n=2)
print time.time() - start

[出]

0.000213146209717
6.50882720947e-05

有没有更快的Python实现用于生成ngrams?


你介意为不同的 n 值编写单独的函数吗?在 zipngram 中硬编码并删除列表表达式可以在一些粗略的实验中提供1.5-2倍的加速。 - dmcc
当然,只要它更快并实现相同的输出,任何方法都可以。介意分享代码和一些分析吗? - alvas
1
Cython或通过cffi实现的C代码算不算?虽然如果字母表是Unicode而不是ACSII,则这些实现并不容易。如果是后者,SSE汇编可能会更快。此外,如果文本足够长,您可能希望将工作分配到多个核心上。 - Dima Tisnek
当然可以,只要脚本可以从Python中调用,速度越快越好。 - alvas
如果您已经使用了Spacy,并且您的文本已经转换为Spacy的“doc”,那么您可以尝试使用textacy的ngram实现:https://chartbeat-labs.github.io/textacy/getting_started/quickstart.html#analyze-a-doc - Suzana
3个回答

13

尝试了一些性能分析。我认为使用生成器可能会提高速度。但与对原始代码进行轻微修改相比,改进并不明显。但是,如果您不需要同时查看完整列表,则生成器函数应该更快。

import timeit
from itertools import tee, izip, islice

def isplit(source, sep):
    sepsize = len(sep)
    start = 0
    while True:
        idx = source.find(sep, start)
        if idx == -1:
            yield source[start:]
            return
        yield source[start:idx]
        start = idx + sepsize

def pairwise(iterable, n=2):
    return izip(*(islice(it, pos, None) for pos, it in enumerate(tee(iterable, n))))

def zipngram(text, n=2):
    return zip(*[text.split()[i:] for i in range(n)])

def zipngram2(text, n=2):
    words = text.split()
    return pairwise(words, n)


def zipngram3(text, n=2):
    words = text.split()
    return zip(*[words[i:] for i in range(n)])

def zipngram4(text, n=2):
    words = isplit(text, ' ')
    return pairwise(words, n)


s = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
s = s * 10 ** 3

res = []
for n in range(15):

    a = timeit.timeit('zipngram(s, n)', 'from __main__ import zipngram, s, n', number=100)
    b = timeit.timeit('list(zipngram2(s, n))', 'from __main__ import zipngram2, s, n', number=100)
    c = timeit.timeit('zipngram3(s, n)', 'from __main__ import zipngram3, s, n', number=100)
    d = timeit.timeit('list(zipngram4(s, n))', 'from __main__ import zipngram4, s, n', number=100)

    res.append((a, b, c, d))

a, b, c, d = zip(*res)

import matplotlib.pyplot as plt

plt.plot(a, label="zipngram")
plt.plot(b, label="zipngram2")
plt.plot(c, label="zipngram3")
plt.plot(d, label="zipngram4")
plt.legend(loc=0)
plt.show()

对于这个测试数据,zipngram2和zipngram3似乎是速度最快的。

在此输入图像描述


3

扩展M4rtini的代码

使用Python3.6.5,nltk == 3.3

from nltk import ngrams
def get_n_gramlist(text,n=2):        
    nngramlist=[]
    for s in ngrams(text.split(),n=n):        
        nngramlist.append(s)                
    return nngramlist

时间测试结果 enter image description here


2

在延续 M4rtini 的代码基础上,我创建了三个带有硬编码参数n=2的额外版本:

def bigram1(text):
    words = iter(text.split())
    last = words.next()
    for piece in words:
        yield (last, piece)
        last = piece

def bigram2(text):
    words = text.split()
    return zip(words, islice(words, 1, None))

def bigram3(text):
    words = text.split()
    return izip(words, islice(words, 1, None))

使用timeit,我得到了以下结果:

zipngram(s, 2):        3.854871988296509
list(zipngram2(s, 2)): 2.0733611583709717
zipngram3(s, 2):       2.6574149131774902
list(zipngram4(s, 2)): 4.668303966522217
list(bigram1(s)):      2.2748169898986816
bigram2(s):            1.979405164718628
list(bigram3(s)):      1.891601800918579
bigram3 在我的测试中速度最快。如果在整个过程中使用迭代器(至少对于这个参数值),手动编码和使用迭代器似乎都有一定的好处。我们可以从 n=2zipngram2zipngram3 之间的更大差异中看到迭代器的好处。
我还尝试过使用 PyPy 来提高性能,但实际上似乎会使事情变得更慢(这包括在进行计时测试之前调用它 10k 次函数来预热 JIT)。不过,我对 PyPy 还很陌生,所以可能做错了什么。也许使用 Pyrex 或 Cython 可以实现更大的加速。

1
在Python 3中直接使用zip而非izip,无需导入任何模块。 - Bowen Xu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接