如何在处理一本大字典的时候去掉记忆错误?

2024-10-02 12:26:13 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图用口述式的结构建立一个单词的三角图索引。键是字符串,值是出现的次数。在

for t in arrayOfTrigrams:
    if t in trigrams:
        trigrams[t] += 1
    else:
        trigrams[t] = 1

但是数据非常大——超过500MB的原始文本,我不知道如何处理内存错误。 与Python memoryerror creating large dictionary不同的是,我不创建任何无关的东西,每个三元组都很重要。在


Tags: 数据内存字符串in文本forif单词
2条回答

我的第一个建议是不要将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:]

这里有两件事:

  1. 我们使用的是一个生成器,所以我们不需要读取整个文件并返回一个三元组列表,而是逐个返回三元组。这有点慢,但可以节省内存。在
  2. 我们最终通过对字符串的dict来确保我们读取的每个字符串最多有一个副本。虽然一开始看起来很奇怪,但是从文件N时间读取相同的字节序列S确实占用了N*len(S)个字节。通过使用字典,我们确保输入中每个单词都有一个唯一的副本。当然,这会消耗一些内存。在

这个函数对你来说可能不同,这取决于你从哪里读到你的八卦。请记住,我在这里使用的标记器是非常基本的。在

这已经节省了一点内存,但不是太多。在

所以,让我们将中间结果存储在磁盘上:

^{pr2}$

在这一步中,您可以调整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吉布的内存,你实际上可能不得不使用拆分功能。有了8吉布斯,你应该能够保持所有的内存。在

在进一步编辑包含的代码时如果您能够在内存中保存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个多次出现的三元组。相反,当我评估

^{pr2}$

我得到了188285,所以在这个有点大的自然语言例子中,只有10016/188285=5.3%的可能的三元组是重复的。假设您的数据也有类似的比率,我估计用于重复三角函数的频率字典的大小大约为100MB。在


原液:


您的代码和问题表明您可以在内存中保存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的信息,我怀疑这对于绝大多数的三元曲线都是正确的。在这一阶段,您拥有所有相关频率信息,并可以放弃trigrams列表,以释放一些已消耗的内存:

arrayOfTrigrams = None 

相关问题 更多 >

    热门问题