首页 > 解决方案 > 汇总海量数据

问题描述

我有一个我无法解决的问题。我有 4 个.txt文件,每个文件在 30-70GB 之间。每个文件都包含 n-gram 条目,如下所示:

blabla1/blabla2/blabla3
word1/word2/word3
...

我要做的是计算每个项目出现的次数,并将这些数据保存到一个新文件中,例如:

blabla1/blabla2/blabla3  : 1
word1/word2/word3        : 3
...

到目前为止,我的尝试只是将所有条目保存在字典中并计算它们,即

entry_count_dict = defaultdict(int)
with open(file) as f:
    for line in f:
        entry_count_dict[line] += 1

但是,使用这种方法会遇到内存错误(我有 8GB RAM 可用)。数据遵循 zipfian 分布,例如,大多数项目只出现一次或两次。条目的总数尚不清楚,但(非常)粗略的估计是总共有大约 15,000,000 个条目。

除此之外,我尝试h5py将所有条目保存为包含数组的 h5py 数据集[1],然后对其进行更新,例如:

import h5py
import numpy as np

entry_count_dict = h5py.File(filename)
with open(file) as f:
    for line in f:
        if line in entry_count_dict:
            entry_count_file[line][0] += 1
        else:
            entry_count_file.create_dataset(line, 
                                            data=np.array([1]),
                                            compression="lzf")

但是,这种方法速度很慢。写入速度越来越慢。因此,除非可以提高写入速度,否则这种方法是不可信的。此外,以块的形式处理数据并为每个块打开/关闭 h5py 文件并没有显示出处理速度的任何显着差异。

我一直在考虑将以某些字母开头的条目保存在单独的文件中,即所有以开头的条目a都保存在 中a.txt,依此类推(这应该可以使用defaultdic(int))。但是,要做到这一点,文件必须为每个字母迭代一次,考虑到文件大小(最大 = 69GB),这是不可信的。也许在遍历文件时,可以打开泡菜并将条目保存在字典中,然后关闭泡菜。但是,由于打开、加载和关闭 pickle 文件需要时间,因此对每个项目执行此操作会大大减慢该过程。

解决此问题的一种方法是在一次通过期间对所有条目进行排序,然后遍历排序的文件并按字母顺序计算条目。但是,即使使用 linux 命令对文件进行排序也会非常缓慢:

sort file.txt > sorted_file.txt

而且,我真的不知道如何使用 python 解决这个问题,因为将整个文件加载到内存中进行排序会导致内存错误。我对不同的排序算法有一些肤浅的了解,但是它们似乎都要求将要排序的整个对象加载到内存中。

任何有关如何解决此问题的提示将不胜感激。

标签: pythonpython-3.xnlphdf5large-data

解决方案


我一直在考虑在单独的文件中保存以某些字母开头的条目,即所有以 a 开头的条目都保存在 a.txt 中,依此类推(这应该可以使用 defaultdic(int))。但是,要做到这一点,文件必须为每个字母迭代一次,考虑到文件大小(最大 = 69GB),这是不可信的。

有了这种思路,你几乎就到了。您要做的是根据前缀拆分文件 - 您不必为每个字母迭代一次。这在 awk 中是微不足道的。假设您的输入文件位于名为的目录中input

mkdir output
awk '/./ {print $0 > ( "output/"  substr($0,0,1))}` input/*

这会将每一行附加到以该行的第一个字符命名的文件中(请注意,如果您的行可以以空格开头,这将很奇怪;因为这些是 ngram,我认为这不相关)。您也可以在 Python 中执行此操作,但管理文件的打开和关闭有点乏味。

因为文件已被拆分,所以它们现在应该小得多。您可以对它们进行排序,但实际上没有必要 - 您可以单独读取文件并使用如下代码获取计数:

from collections import Counter

ngrams = Counter()
for line in open(filename):
    ngrams[line.strip()] += 1
for key, val in ngrams.items():
    print(key, val, sep='\t')

如果文件仍然太大,您可以增加用于存储行的前缀的长度,直到文件足够小。


推荐阅读