Python在大文件中计算ngram频率

时间:2016-01-20 18:27:53

标签: python string count frequency n-gram

我有一个包含N-Grams的699Mb文件,我已经尝试了以下代码来计算每个N-Gram的频率但是当我运行它时我得到一个内存错误或程序崩溃。

from collections import Counter
import sys

c = Counter()
with open('Output.txt') as myfile:
    for line in myfile:
        c.update(line.split())

print(c)

with open('NGramCountOutput.txt', 'w') as outgrams:
    outgrams.write(str(c))

任何人都可以建议更优雅的解决方案来解决这个问题或提出另一种方法。

2 个答案:

答案 0 :(得分:2)

尝试迭代c而不是在内存中将其字符串化:

for k in c:
    outgrams.write("{0}: {1}".format(k, c[k]))

答案 1 :(得分:1)

心理调试:您的输入文件实际上是一行,包含所有ngrams。所以当你这样做时:

for line in myfile:
    c.update(line.split())

它实际上将整个文件读作单个"行",然后将其拆分为所有ngrams中的list。问题是,这意味着在Counter(Python 3.5中的三字母ASCII str中对所有ngrams进行重复数据删除之前,会立即存储所有ngrams的单个副本的巨大内存成本x64使用~52个字节,加上另外8个字节用于在结果list中对它的引用;如果你在一行中读取了699 MB的三个字母字符串,每个字母之间有一个空格,那么分开它,你将产生大约1.83亿这些字符串,这意味着内存使用的粗略下限将是183000000 * 60,或大约10 GB的内存。32位机器上的成本会降低,但不会超过50%(可能更少);在32位计算机上,您没有足够的虚拟内存地址空间来存储5 GB(大多数32位计算机限制为2 GB)。

最简单的解决方法是将文件拆分为将每个ngram放在自己的行上(或将每行的ngram数限制为合理的数字)。例如,对于tr(在类UNIX机器上),转换很简单:

tr ' ' '\n' < Output.txt > OutputNewlines.txt

类似的方法可以在许多文本编辑器中使用find / replace。

如果这不是一个选项,你会想要逐行显式读取,而不是逐行处理,在最后一个空格之前处理所有内容,保存剩余的内容,然后读取另一个块

from functools import partial

c = Counter()
with open('Output.txt') as myfile:
    remainder = ''
    # Read file by fixed size blocks, not lines, assuming no ngram is larger than 8192
    for block in iter(partial(myfile.read, 8192), ''):
        # Split off the last whitespace separated piece (might span to next block)
        parts = (remainder + block).rsplit(None, 1)
        # Handle block with and without whitespace identically; no whitespace means
        # probably handling EOF, just process what we've got and set remainder empty
        toprocess, remainder = (parts + [''])[:2]
        c.update(toprocess.split())
    c.update(remainder.split())  # Add whatever was left over

这应该限制最大内存使用量与唯一ngrams的数量成比例,而不是一行上总的非唯一ngrams的数量。

如果你的ncharms相对较少,那就足够了。如果你有很多独特的ngrams,那么对Counter进行字符串化也会花费大量内存(尽管Counter本身会使用更多,str只会是一根稻草。这打破了骆驼的背部)。每行打印一个计数的简单方法是:

from itertools import starmap

with open('NGramCountOutput.txt', 'w') as outgrams:
    # On Python 2, use .iteritems() instead of .items() to avoid large temp list
    # If a temp list is okay, and you want sorted output by count,
    # use .most_common() over .items()
    outgrams.writelines(starmap('{} {}\n'.format, c.items()))