我正在尝试使用dictonary类型的结构来构建单词的三元组索引。键是字符串,值是出现次数。
for t in arrayOfTrigrams:
if t in trigrams:
trigrams[t] += 1
else:
trigrams[t] = 1
但数据非常大 - 超过500 MB的原始文本,我不知道如何处理MemoryError。 与Python memoryerror creating large dictionary不同,我不会创建任何不相关的东西,每个三元组都很重要。
答案 0 :(得分:0)
进一步编辑 - 包含的代码如果您能够将arrayOfTrigrams
保留在内存中,请参阅底部的原始解决方案。但是,如果您无法创建arrayOfTrigrams
(并且我对您在数据大小方面已经达到了这一点有点怀疑),您仍然可以拍摄创建重复三元组的字典。重复的双字母总是包含重复的单词,重复的三元组包含重复的双字母。分阶段处理500 MB数据。首先创建一组重复的单词。使用它,创建一个重复的双字母词典。首先执行包含重复单词之一的双字母组的原始频率计数,然后丢弃其计数为1的任何单词。然后第三次处理数据,创建重复三元组的字典。再次,做一个包含重复二元组的三元组的原始频率计数(应该是所有可能的三元组的一小部分),然后丢弃那些最终计数只为1的字典。这样你就可以建立字典而不用需要立即将所有三元组保留在内存中。
概念证明:
from collections import defaultdict
chars = set('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
def cleanWord(s):
return ''.join(c for c in s if c in chars)
f = open('moby dick.txt') #downloaded from Project Gutenberg: http://www.gutenberg.org/ebooks/2701 -- Thanks!
words = f.read().split()
f.close()
words = [cleanWord(w.upper()) for w in words]
words = [w for w in words if len(w) > 1 and not(w in set('AIOY'))]
repeatedWords = defaultdict(int)
for w in words:
repeatedWords[w] += 1
repeatedWords = set(w for w in repeatedWords if repeatedWords[w] > 1)
repeatedBigrams = defaultdict(int)
for i in range(len(words) - 1):
x,y = words[i:i+2]
if x in repeatedWords or y in repeatedWords:
repeatedBigrams[x + ' ' + y] +=1
repeatedBigrams = set(b for b in repeatedBigrams if repeatedBigrams[b] > 1)
repeatedTrigrams = defaultdict(int)
for i in range(len(words) - 2):
x,y,z = words[i:i+3]
if x + ' ' + y in repeatedBigrams and y + ' ' + z in repeatedBigrams:
repeatedTrigrams[x + ' ' + y + ' ' + z] +=1
repeatedTrigrams = {t:c for t,c in repeatedTrigrams.items() if c > 1}
此代码会出现10016个三元组,这些三元组不止一次出现。相反,当我评估时
len(set(' '.join(words[i:i+3]) for i in range(len(words)-2)))
我得到188285,所以在这个有点大小的自然语言例子中,只有10016/188285 = 5.3%的可能三元组是重复的三元组。假设数据的比率相似,我估计重复三元组的频率字典大小约为100 MB。
原始解决方案:
您的代码和问题表明您可以将arrayOfTrigrams
保留在内存中,但无法创建字典。一种可能的解决方法是首先对此数组进行排序,并创建重复三元组的频率计数:
arrayOfTrigrams.sort()
repeatedTrigrams = {}
for i,t in enumerate(arrayOfTrigrams):
if i > 0 and arrayOfTrigrams[i-1] == t:
if t in repeatedTrigrams:
repeatedTrigrams[t] += 1
else:
repeatedTrigrams[t] = 2
构建repeatedTrigrams
后,您可以使用集合理解:
uniques = {t for t in arrayOfTrigrams if not t in repeatedTrigrams}
然后t in uniques
会传达t
的计数为1的信息,我绝对会认为这对于绝大多数的三卦都是正确的。在这个阶段,您拥有所有相关的频率信息,并且可以丢弃三元组列表以释放您消耗的一些内存:
arrayOfTrigrams = None
答案 1 :(得分:0)
我的第一个建议是不要将arrayOfTrigrams
完全保留在内存中,而是使用流式传输。您正在从某处读取它,因此您可以控制它的读取方式。 Python的生成器在这里非常方便。我们假装你正在从文件中读取它:
def read_trigrams(fobj):
unique = {}
def make_unique(w):
w = w.strip("\"'`!?,.():-;{}").lower()
return unique.setdefault(w, w)
fobj.seek(0, 2)
total_size = fobj.tell()
fobj.seek(0, 0)
read = 0
prev_words = []
for idx, line in enumerate(fobj):
read += len(line)
words = prev_words
words.extend(filter(None, (make_unique(w) for w in line.split())))
if len(words) > 3:
for i in range(len(words) - 3):
yield tuple(words[i:i+3])
prev_words = words[-2:]
这里有两件事:
S
时间读取相同的字节序列N
会占用N*len(S)
个字节。通过使用字典,我们确保输入中每个单词都有一个唯一的副本。当然,这确实消耗了一些记忆。此功能对您来说可能有所不同,具体取决于您从哪里读取三卦。请记住,我在这里使用的标记器是非常基本的。
这已经节省了一点点内存,但不过多。
所以,让我们将中间结果存储在磁盘上:
LIMIT = 5e6
def flush(counts, idx):
with open('counts-%d' % (idx,), 'wb') as fobj:
p = pickle.Pickler(fobj)
for item in sorted(counts.items()):
p.dump(item)
import sys
import pickle
from collections import defaultdict
counts = defaultdict(int)
caches = 0
with open(sys.argv[1], 'r') as fobj:
for t in read_trigrams(fobj):
counts[t] += 1
if len(counts) > LIMIT:
flush(counts, caches)
caches += 1
counts.clear()
flush(counts, caches)
在此步骤中,您可以调整LIMIT
以避免使用太多内存,即在您不再遇到MemoryError
之前将其缩小。
现在,驱动器上有N
个文件,其中包含三元组的排序列表。在一个单独的程序中,您可以阅读它们并总结所有中间计数:
import sys
import pickle
def merger(inputs):
unpicklers = [pickle.Unpickler(open(f, 'rb')) for f in inputs]
DONE = (object(), )
NEXT = (object(), )
peek = [NEXT] * len(unpicklers)
while True:
for idx in range(len(unpicklers)):
if peek[idx] is NEXT:
try:
peek[idx] = unpicklers[idx].load()
except EOFError:
peek[idx] = DONE
if all(v is DONE for v in peek):
return
min_key = min(v[0] for v in peek if v is not DONE)
yield min_key, sum(v[1] for v in peek if v[0] == min_key)
peek = [NEXT if (v[0] == min_key) else v for v in peek]
for trigram, count in merger(sys.argv[1:]):
print(trigram, count)
如果你有4 GiB的内存,你可能实际上必须使用分割功能。使用8 GiB,您应该能够将其全部保存在RAM中。