我有两个文本文件,都包含大约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个条目。)
完成文件中所有字对的计算需要几十天。
对此有什么明智的解决方法?
非常感谢你的帮助。
答案 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.product
或counts_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]