Python集合具有大数据的计数器

时间:2013-10-23 20:53:16

标签: python collections counter

我有两个文本文件,都包含大约700,000行。

第二个文件包含对第一个文件中对应行的语句的响应。

我需要为匹配线上出现的每个单词对计算Fisher的精确分数。

例如,如果文件中的第n行是

how are you

fine thanx

然后我需要计算费舍尔的分数(如何,罚款),(如何,比如何),(是,罚款),(是,否,),(你,罚款),(你,不是)。

为了计算Fisher的精确得分,我使用了收集模块的计数器来计算每个单词的出现次数,以及它们在两个文件中的共同出现,如

with open("finalsrc.txt") as f1, open("finaltgt.txt") as f2:
    for line1, line2 in itertools.izip(f1, f2):
        words1 = list(set(list(find_words(line1))))
        words2 = list(set(list(find_words(line2))))
        counts1.update(words1)
        counts2.update(words2)
        counts_pair.update(list(set(list(itertools.product(words1, words2)))))

然后我使用scipy模块计算每对的Fisher精确分数

from scipy import stats
def calculateFisher(s, t):
    sa = counts1[s]
    ta = counts2[t]
    st = counts_pair[s, t]
    snt = sa - st
    nst = ta - st
    nsnt = n - sa - ta + st
    oddsratio, pvalue = stats.fisher_exact([[st, snt], [nst, nsnt]])
    return pvalue

这适用于小型文本文件, 但由于我的文件每行包含700,000行,我认为Counter太大而无法快速检索值,而且变得非常慢。

(假设每个句子有10个单词,则counts_pair将有(10 ^ 2)* 700,000 = 70,000,000个条目。)

完成文件中所有字对的计算需要几十天。

对此有什么明智的解决方法?

非常感谢你的帮助。

3 个答案:

答案 0 :(得分:4)

你究竟如何调用calculateFisher函数?你的counts_pair有7000万个条目:很多单词对会被多次看到,所以七千万是他们的计数之和,而不是键的数量。您应该只计算同时发生的对的精确测试,找到它们的最佳位置是counts_pair。但这意味着你可以迭代它;如果你这样做,你永远不必在counts_pair中查看任何

for (s, t), count in counts_pair.iteritems():
    sa = counts1[s]
    ta = counts2[t]
    st = count
    # Continue with Fisher's exact calculation

为了清晰起见,我已将calculate_fisher功能考虑在内;我希望你明白这个主意。因此,如果字典查找减慢了你的速度,这将为你节省很多。如果没有,...做一些分析,让我们知道真正发生了什么。

但请注意,只需在一本巨大的字典中查找键,就不应该减慢太多的速度。但是,如果您的程序必须将大部分数据交换到磁盘,则“快速检索值”将很困难。您的计算机中是否有足够的内存来同时容纳三个计数器?第一个循环是否在合理的时间内完成?因此找到瓶颈,你就会知道更多需要修复的东西。

编辑:从您的评论中,您可能会在后续的文字处理过程中反复计算Fisher的精确分数。为什么这样?通过两个步骤分解您的程序:首先,按照我的描述计算所有单词对分数。编写每对并在计算时将其记分到文件中。完成后,使用一个单独的脚本将它们读回来(现在内存中除了这一对大型词典和Fisher的精确分数之外别无其他),然后重写。无论如何你应该这样做:如果只需要十天时间来获得分数(并且你还没有向我们提供有关什么是缓慢的,以及为什么的详细信息),请在十天内开始你将永远拥有它们,随时随地使用。

我做了一个快速实验,一个包含一百万个((word, word), count)元组列表的python进程只需300MB(在OS X上,但数据结构在Windows上的大小应该相同)。如果你有1000万个不同的单词对,你可以期望它需要大约2.5 GB的RAM。我怀疑你甚至会有这么多单词对(但请检查!)。所以,如果你有4GB的内存并且你没有做错任何你没有告诉过我们的事情,你应该没问题。否则,YMMV。

答案 1 :(得分:3)

我认为你的瓶颈在于你如何操纵除计数器之外的数据结构。

words1 = list(set(list(find_words(line1))))find_words的结果列表中创建一个列表。这些操作中的每一个都需要分配内存来保存所有对象和复制。更糟糕的是,如果find_words返回的类型不包含__len__方法,则生成的列表必须增长并在迭代时重新复制。

我假设你所需要的只是一个可迭代的独特单词,以便更新你的计数器,set就足够了。

for line1, line2 in itertools.izip(f1, f2):
    words1 = set(find_words(line1)) # words1 now has list of unique words from line1
    words2 = set(find_words(line2)) # words2 now has list of unique words from line2
    counts1.update(words1)          # counts1 increments words from line1 (once per word)
    counts2.update(words2)          # counts2 increments words from line2 (once per word)
    counts_pair.update(itertools.product(words1, words2)

请注意,由于itertools.productcounts_pair中没有重复的元素,因此您无需更改传递给words1的{​​{1}}的输出,因此笛卡儿积不会有任何重复的元素。

答案 2 :(得分:3)

听起来你需要懒洋洋地生成交叉产品 - 拥有7000万个元素的Counter将占用大量内存并且几乎在每次访问时都会遇到缓存未命中。

那么如何保存将“文件1”字映射到相应“文件2”字组列表的字典呢?

初​​始:

word_to_sets = collections.defaultdict(list)

替换:

   counts_pair.update(list(set(list(itertools.product(words1, words2)))))

使用:

   for w1 in words1:
       word_to_sets[w1].append(words2)

然后在你的Fisher函数中,替换它:

st = counts_pair[s, t]

使用:

    st = sum(t in w2set for w2set in word_to_sets.get(s, []))

这就像我一样懒惰 - 交叉产品根本就没有计算过; - )

编辑或将“列表1”字词映射到自己的Counter

初​​始:

word_to_counter = collections.defaultdict(collections.Counter)

替换:

   counts_pair.update(list(set(list(itertools.product(words1, words2)))))

使用:

   for w1 in words1:
       word_to_counter[w1].update(words2)

费舍尔功能:

    st = word_to_counter[s][t]