Python计算大型文件中的ngram频率

Python计算大型文件中的ngram频率,python,string,count,frequency,n-gram,Python,String,Count,Frequency,N Gram,我有一个699Mb的文件,包含N-Gram,我尝试了下面的代码来计算每个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('NGramCoun

我有一个699Mb的文件,包含N-Gram,我尝试了下面的代码来计算每个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))
有人能提出一个更优雅的解决方案来解决这个问题,或者提出另一种方法吗

尝试在c上迭代,而不是在内存中字符串化它:

for k in c:
    outgrams.write("{0}: {1}".format(k, c[k]))
尝试在c上迭代,而不是在内存中字符串化:

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

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

for line in myfile:
    c.update(line.split())
它实际上是将整个文件作为一行读取,然后将其拆分为所有ngram的列表。问题是,这意味着在计数器中消除重复数据之前瞬间存储所有NGRAM的单个副本需要巨大的内存成本。Python 3.5 x64中的三个字母ASCII str使用约52个字节,再加上8个字节作为结果列表中的引用;如果你在一行中读取699 MB的三个字母字符串,每个字符串之间都有一个空格,然后将其拆分,你将产生约1.83亿个字符串,这意味着内存使用的粗略下限将是183000000*60,或者大约10 GB内存。在32位机器上,成本会更低,但不会超过50%,而且可能更低;在32位计算机上,您没有足够的虚拟内存地址空间来存储5 GB大多数32位计算机限制为2 GB

最简单的修复方法是将文件拆分,将每个ngram放在自己的行上,或者将每行的ngram数量限制在合理的范围内。例如,在类UNIX机器上使用tr时,转换非常简单:

tr ' ' '\n' < Output.txt > OutputNewlines.txt
这将限制最大内存使用量,使其与唯一NGRAM的数量成比例,而不是一条线路上非唯一NGRAM的总数

如果你有相对较少的独特NGRAM,那就足够了。如果你有很多独特的Ngram,那么串接计数器也会消耗大量内存,虽然计数器本身会使用更多的内存,但是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()))

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

for line in myfile:
    c.update(line.split())
它实际上是将整个文件作为一行读取,然后将其拆分为所有ngram的列表。问题是,这意味着在计数器中消除重复数据之前瞬间存储所有NGRAM的单个副本需要巨大的内存成本。Python 3.5 x64中的三个字母ASCII str使用约52个字节,再加上8个字节作为结果列表中的引用;如果你在一行中读取699 MB的三个字母字符串,每个字符串之间都有一个空格,然后将其拆分,你将产生约1.83亿个字符串,这意味着内存使用的粗略下限将是183000000*60,或者大约10 GB内存。在32位机器上,成本会更低,但不会超过50%,而且可能更低;在32位计算机上,您没有足够的虚拟内存地址空间来存储5 GB大多数32位计算机限制为2 GB

最简单的修复方法是将文件拆分,将每个ngram放在自己的行上,或者将每行的ngram数量限制在合理的范围内。例如,在类UNIX机器上使用tr时,转换非常简单:

tr ' ' '\n' < Output.txt > OutputNewlines.txt
这将限制最大内存使用量,使其与唯一NGRAM的数量成比例,而不是一条线路上非唯一NGRAM的总数

如果你有相对较少的独特NGRAM,那就足够了。如果你有很多独特的Ngram,那么串接计数器也会消耗大量内存,虽然计数器本身会使用更多的内存,但是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()))

甚至-对于c.items:outgrams.write{}:{}.formatk,v中的k,v,考虑到ngram在任何字符串化之前都是未序列化的,除非大多数ngram是唯一的,否则str的内存开销不会那么高,并且在所有情况下都比计数器本身的开销要小。这是一个可能的问题/解决方案,但不是最有可能的问题/解决方案。只有当您已经非常接近系统内存限制,并且独特的Ngram非常常见时,才会发生这种情况。@wwii:或者当我们进行荒谬的优化时,我的答案中的版本,来自itertools import starmap,outgrams.writelinesstarmap'{}:{}\n'。格式,c.items,它将所有工作推到c层以加快运行速度我添加了一个新行,因为缺少一行会使计数进入以下Ngram。@ShadowRanger-我不打算进行任何优化,我只是更喜欢该表单,它更易于阅读。@wwii:没错,您的基本上是正确的,而不是主要的优化。避免不必要的查找更有效,但在实际使用中,它确实是正确命名键和值的方法,您通常不会使用k/v使代码更可读,从而使代码更好。甚至-对于c中的k,v.items:outgrams.wr
ite{}:{}.formatk,v由于在任何字符串化之前,NGRAM都是非Q化的,除非大多数NGRAM是唯一的,否则str的内存成本不会那么高,并且在所有情况下,它与计数器本身的成本相比都会显得苍白。这是一个可能的问题/解决方案,但不是最有可能的问题/解决方案。只有当您已经非常接近系统内存限制,并且独特的Ngram非常常见时,才会发生这种情况。@wwii:或者当我们进行荒谬的优化时,我的答案中的版本,来自itertools import starmap,outgrams.writelinesstarmap'{}:{}\n'。格式,c.items,它将所有工作推到c层以加快运行速度我添加了一个新行,因为缺少一行会使计数进入以下Ngram。@ShadowRanger-我不打算进行任何优化,我只是更喜欢该表单,它更易于阅读。@wwii:没错,您的基本上是正确的,而不是主要的优化。避免不必要的查找更有效,但在实际使用中,它确实是正确命名键和值的方法,通常不会使用k/v,这会使代码更具可读性。