python中的快速/优化N-gram实现

Posted

技术标签:

【中文标题】python中的快速/优化N-gram实现【英文标题】:Fast/Optimize N-gram implementations in python 【发布时间】:2014-03-19 22:30:59 【问题描述】:

哪个 ngram 实现在 python 中最快?

我尝试分析 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 中生成 ngram 有更快的实现吗?

【问题讨论】:

您可以为n 的不同值设置单独的函数吗?在zipngram 中对其进行硬编码并删除列表表达式在一些粗略的实验中提供了 1.5-2 倍的加速。 当然,任何方法,只要它更快并且达到相同的输出 =)。愿意分享代码和一些分析吗? 在 Cython 或 C 中通过 cffi 实现是否算数?如果字母表是 unicode 而不是 ACSII,那么这些将是最快的,尽管不是微不足道的。如果是后者,SSE 大会可能会大打出手。此外,如果文本足够长,您可能希望将工作分散到各个核心。 当然,只要能从python调用脚本,越快越好。 如果你已经使用 spacy 并且你的文本已经被转换成 spacy doc,你可以试试 textacy 的 ngram 实现:chartbeat-labs.github.io/textacy/getting_started/… 【参考方案1】:

一些分析的尝试。我认为使用发电机可以提高这里的速度。但与原版的轻微修改相比,改进并不明显。但是,如果您不需要同时使用完整列表,则生成器函数应该更快。

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 似乎是最快的。

【讨论】:

【参考方案2】:

扩展M4rtini's code,我用硬编码的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 在我的测试中是最快的。如果在整个过程中使用迭代器(至少对于这个参数值),硬编码和使用迭代器似乎确实有一点好处。我们看到迭代器的好处在于zipngram2zipngram3 对于n=2 之间的更大差异。

我也尝试从使用 PyPy 中获得提升,但它似乎实际上让事情变得更慢(这包括尝试通过在进行计时测试之前调用函数 10k 次来预热 JIT)。尽管如此,我对 PyPy 还是很陌生,所以我可能做错了什么。可能使用 Pyrex 或 Cython 可以实现更大的加速。

【讨论】:

在python 3中直接使用zip代替izip,不需要导入任何东西。【参考方案3】:

扩展M4rtini's Code

使用 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

Timeit 结果

【讨论】:

以上是关于python中的快速/优化N-gram实现的主要内容,如果未能解决你的问题,请参考以下文章

Python中N-Gram、tf-idf和余弦相似度的简单实现

Python中N-Gram、tf-idf和余弦相似度的简单实现

从python中的推文中提取n-gram

来自python中的文本的n-gram

机器学习新手项目之N-gram分词

1.3 n-gram平滑算法:Good-Turning拉普拉斯平滑