背景:我正在使用最小构造算法构建一个代表字典的特里。输入列表是4.3M utf-8字符串,按字典顺序排序。生成的图形是非循环的,最大深度为638个节点。我的脚本的第一行通过sys.setrecursionlimit()
将递归限制设置为1100。
问题:我希望能够将我的trie序列化到磁盘,因此我可以将其加载到内存中而无需从头开始重建(大约22分钟)。我使用文本和二进制协议尝试了pickle.dump()
和cPickle.dump()
。每次,我得到一个如下所示的堆栈跟踪:
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 649, in save_dict
self._batch_setitems(obj.iteritems())
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 663, in _batch_setitems
save(v)
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 725, in save_inst
save(stuff)
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/pickle.py", line 648, in save_dict
self.memoize(obj)
RuntimeError: maximum recursion depth exceeded
我的数据结构相对简单:trie
包含对开始状态的引用,并定义了一些方法。 dfa_state
包含布尔字段,字符串字段和从标签到州的字典映射。
我对pickle
的内部工作原理不是很熟悉 - 我的最大递归深度是否需要大于/等于某些n的trie深度的n倍?或者这可能是由我不知道的其他东西造成的?
更新:将递归深度设置为3000无济于事,因此这条途径看起来并不乐观。
更新2:你们是对的;由于默认的递归限制,我认为pickle会使用一个小的嵌套深度,因此我是短视的。 10,000人做了这个伎俩。
答案 0 :(得分:35)
来自the docs:
尝试挑选高度递归的数据结构可能会超过最大递归深度,在这种情况下会引发RuntimeError。您可以使用
sys.setrecursionlimit()
小心提高此限制。
尽管您的trie实现可能很简单,但它使用递归,并且在转换为持久数据结构时可能会导致问题。
我的建议是继续提高递归限制,以查看您正在使用的数据是否存在上限以及您正在使用的trie实现。
除此之外,如果可能,您可以尝试将树实现更改为“更少递归”,或者编写内置数据持久性的其他实现(使用pickles和{{3在你的实现中)。希望有所帮助
答案 1 :(得分:9)
Pickle确实需要递归地走你的特里。如果Pickle只使用5个级别的函数调用来完成工作,那么深度为638的trie将需要设置为超过3000的级别。
尝试一个更大的数字,递归限制实际上只是为了保护用户不必等待太长时间,如果递归落入一个无限的洞。
Pickle处理循环没问题,所以即使你的trie在那里有一个循环也没关系
答案 2 :(得分:4)
还必须使用resource.setrlimit
增加堆栈大小以防止段错误
如果仅使用sys.setrecursionlimit
,如果达到Linux内核允许的最大堆栈大小,仍然可能会出现段错误。
如Setting stacksize in a python script
所述,可以使用resource.setrlimit
增加此值
import pickle
import resource
import sys
print resource.getrlimit(resource.RLIMIT_STACK)
print sys.getrecursionlimit()
max_rec = 0x100000
# May segfault without this line. 0x100 is a guess at the size of each stack frame.
resource.setrlimit(resource.RLIMIT_STACK, [0x100 * max_rec, resource.RLIM_INFINITY])
sys.setrecursionlimit(max_rec)
a = []
# 0x10 is to account for subfunctions called inside `pickle`.
for i in xrange(max_rec / 0x10):
a = [a]
print pickle.dumps(a, -1)
另请参阅:What is the maximum recursion depth in Python, and how to increase it?
我的默认最大值是8Mb。
在Ubuntu 16.10,Python 2.7.12。
上测试答案 3 :(得分:3)
仔细检查您的结构是否确实是非循环的。
您可以尝试进一步提高限额。平台依赖于最大硬盘,但尝试50000是合理的。
同时尝试腌制你的特里的小版本。如果泡菜死了,即使它只存储了几个三个字母的单词,那么你知道你的trie有一些根本问题,而不是泡菜。但是如果它只在你尝试存储10k字时发生,那么它可能是pickle中平台限制的错误。
答案 4 :(得分:0)
我的需求有些直接,所以我通过以.txt格式保存字典来解决这个问题。唯一的问题是当你再次加载文件时,你必须将它转换回字典。
import json
# Saving the dictionary
with open('filename.txt', 'w') as file_handle:
file_handle.write(str(dictionary))
# Importing the .txt file
with open('filename.txt', 'r') as file_handle:
f = '"' + file_handle.read() + '"'
# From .txt file to dictionary
dictionary = eval(json.loads(f))
如果这不起作用,您可以尝试使用json格式导出字典。
答案 5 :(得分:0)
对我来说,删除importlib.reload
的所有使用可以解决此问题。
我确实没有不需要,甚至需要使用setrecursionlimit
增加限制。
如果您想知道我是怎么找到它的,请继续阅读。
在找到解决方案之前,我发现如果先将模型移到CPU上就可以保存模型,但是在评估过程中出现错误(XXX是类名,无关紧要):
PicklingError: Can't pickle <class 'XXX'>: it's not the same object as XXX
然后我找到了这个答案: https://stackoverflow.com/a/1964942/4295037
但是在删除所有对importlib.reload
的使用之后,我能够保存模型而无需先将其移至CPU设备。