2016-01-20 126 views
1

我有一个文件,该文件是含N克699MB,我曾尝试下面的代码然而,计数每一个n-gram的频率时我运行它,我收到了存储器错误或程序崩溃。Python的计数的ngram频率

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

尝试遍历c,而不是在内存中字符串化吧:

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

甚至 - '''为K,V在c.items():outgrams.write( “{}:{}” 的格式(K ,v))''' – wwii

+1

考虑到在任何字符串化之前ngram是唯一的,除非_most_ ngrams是唯一的,'str'的内存开销不会那么高(并且它会比较在所有情况下都是“计数器”本身)。这是一个可能的问题/解决方案,但不是最有可能的问题/解决方案(只有在您已经非常接近系统内存限制的情况下才会发生,并且独特的ngram非常常见)。 – ShadowRanger

+0

@wwii:或者当我们要进行荒谬的优化时,我的答案中的版本,“从itertools导入starmap”,“outmap.writelines(starmap('{}:{} \ n'.format,c.items ))'',它把所有的工作推到C层上运行得更快(我添加了一个新行,因为缺少一个会使计数运行到下面的ngrams中)。 – ShadowRanger

1

心理调试:您输入文件实际上是单行线,包含所有的n-gram。所以,当你这样做:

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

它实际上读取整个文件作为一个单一的“线”,然后将其拆分成所有的n-gram的list。问题是,这意味着存储所有的n-gram的个别副本暂时他们在Python获得去重复-ED在Counter(三个字母的ASCII str前3.5 x64的使用〜52个字节,加上参考其他8个字节的巨大存储成本在list;如果你读了一行699 MB的三字母字符串,每个字符串之间有一个空格,然后分割它,你将产生约1.83亿个字符串,这意味着一个粗略的下界内存使用量为183000000 * 60或大约10GB内存,32位机器的成本较低,但不超过50%(可能更少);在32位机器上,您没有足够的虚拟机以存储5 GB内存地址空间(大多数32台机器被限制为2 GB)。

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

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

类似的方法可以与发现许多文本编辑器中使用/替换。

如果这不是一种选择,你要通过明确块读取块,而不是行由行,最后空间之前处理所有的东西,节省剩下,然后读取另一个块。

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 

这应该限制的最大存储器使用是相称的独特n元语法的数量,而不是在总线,非唯一n元语法的数目。

如果你有相对较少的独特的n-gram,那么这就足够了。如果你有很多独特的n-gram的虽然字符串化的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()))