python系列&AI系列【仅供参考】:【百度PaddleOCR踩坑】报错:RecursionError: maximum recursion depth exceeded while callin




【百度PaddleOCR踩坑】报错:RecursionError: maximum recursion depth exceeded while calling a Python object

摘要

在使用PaddleOCR训练方向识别模型时遇到RecursionError和内存溢出问题。错误源于标签文本中的字符导致ClsLabelEncode无法匹配,进而引发无限递归。解决方案是替换掉,问题根源在于以rb模式读取txt文件引入`。训练检测和识别模型未…

报错事件发生经过

在训练百度PaddleOCR开发套件的方向识别模型时候踩了一个小坑,通过分析找到问题,文本记录了分析过程,需要解决问题的可以直接看3.1。

1.下载PaddleOCR开发套件。

首先按照Github中PaddleOCR官方文档关于文字方向识别的文档进行准备工作及参数配置。

2.准备数据。

项目中需要识别图片的角度(0、90、180、270度),故准备了数张方向全为0度的图片,通过Pillow库进行随机反转,代码如下:

r = [Image.ROTATE_90, Image.ROTATE_180, Image.ROTATE_270]
degs = ['90', '180', '270']
for fn in tqdm(os.listdir('photo')):
    im = Image.open('photo/'+fn).convert('RGB')
    i = random.randint(0, 3)
    if i > 2:
        im.save('photo_rand_rot/'+fn)
        with open('label.txt', 'a') as f:
            f.write('{}\t{}\n'.format(fn, '0'))
    else:
        im = im.transpose(r[i])
        im.save('photo_rand_rot/'+fn)
        with open('label.txt', 'a') as f:
            f.write('{}\t{}\n'.format(fn, degs[i]))

3.配置YAML文件并运行。 运行命令如下:

python tools/train.py -c .\configs\cls\cls_mv3.yml

在加载数据步骤时,提示报错:RecursionError: maximum recursion depth exceeded while calling a Python object

[2022/08/07 23:38:59] ppocr INFO: train dataloader has 234 iters
[2022/08/07 23:38:59] ppocr INFO: valid dataloader has 19 iters
[2022/08/07 23:38:59] ppocr INFO: During the training process, after the 0th iteration, an evaluation is run every 400 iterations
Exception in thread Thread-3:
Traceback (most recent call last):
  File "D:\XXX\cls\PaddleOCR-release-2.5\ppocr\data\simple_dataset.py", line 138, in __getitem__
    data['image'] = img
RecursionError: maximum recursion depth exceeded while calling a Python object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\zhy\anaconda3\lib\threading.py", line 973, in _bootstrap_inner
  File "C:\Users\zhy\anaconda3\lib\threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\zhy\anaconda3\lib\site-packages\paddle\fluid\dataloader\dataloader_iter.py", line 217, in _thread_loop
    batch = self._dataset_fetcher.fetch(indices,
  File "C:\Users\zhy\anaconda3\lib\site-packages\paddle\fluid\dataloader\fetcher.py", line 121, in fetch
    data.append(self.dataset[idx])
  File "D:\XXX\cls\PaddleOCR-release-2.5\ppocr\data\simple_dataset.py", line 150, in __getitem__
    return self.__getitem__(rnd_idx)
  File "D:\XXX\cls\PaddleOCR-release-2.5\ppocr\data\simple_dataset.py", line 150, in __getitem__
    return self.__getitem__(rnd_idx)
  File "D:\XXX\cls\PaddleOCR-release-2.5\ppocr\data\simple_dataset.py", line 150, in __getitem__
    return self.__getitem__(rnd_idx)
  [Previous line repeated 987 more times]
  File "D:\XXX\cls\PaddleOCR-release-2.5\ppocr\data\simple_dataset.py", line 144, in __getitem__
    data_line, traceback.format_exc()))
  File "C:\Users\zhy\anaconda3\lib\traceback.py", line 167, in format_exc
    return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain))
  File "C:\Users\zhy\anaconda3\lib\traceback.py", line 120, in format_exception
    return list(TracebackException(
  File "C:\Users\zhy\anaconda3\lib\traceback.py", line 476, in __init__
    _seen.add(id(exc_value))
RecursionError: maximum recursion depth exceeded while calling a Python object

查找问题

1.经常解决递归达到最大问题。

直观的问题在于报错信息:maximum recursion depth exceeded,也就是字面意思,递归达到最深,这个问题百度上有很多解决方案,大致的意思相同:Python默认的最大递归深度是1000,通过下面的方式可以设置最大递归深度。
打开PaddleOCR下的ppocr\data\simple_dataset.py,头部增加:

import sys
sys.setrecursionlimit(3000) #设置递归深度

暂时设置为3000,问题依然存在,考虑项目数据共计12w张图像(压缩后22GB大小),将深度调整为一个巨大的数,10000000,但在程序在本地PC上因内存溢出直接崩溃,本地Windows PC的RAM为48G,理论来说即便所有数据放在内存中也是足够的,抱着试一试的心态,尝试在服务器上使用128G内存依然溢出崩溃,附一个Linux上的报错信息:

[2022/08/08 00:46:45] ppocr INFO: During the training process, after the 0th iteration, an evaluation is run every 400 iterations
Traceback (most recent call last):
  File "tools/train.py", line 188, in <module>
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 620, in _get_data
    data = self._data_queue.get(timeout=self._timeout)
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/multiprocessing/queues.py", line 105, in get
    raise Empty
_queue.Empty

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/threading.py", line 926, in _bootstrap_inner
    self.run()
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 534, in _thread_loop
    batch = self._get_data()
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 636, in _get_data
    "pids: {}".format(len(failed_workers), pids))
RuntimeError: DataLoader 1 workers exit unexpectedly, pids: 5503

    main(config, device, logger, vdl_writer)
  File "tools/train.py", line 163, in main
    eval_class, pre_best_model_dict, logger, vdl_writer, scaler)
  File "/home/aistudio/PaddleOCR/tools/program.py", line 238, in train
    for idx, batch in enumerate(train_dataloader):
  File "/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dataloader/dataloader_iter.py", line 746, in __next__
    data = self._reader.read_next_var_list()
SystemError: (Fatal) Blocking queue is killed because the data reader raises an exception.
  [Hint: Expected killed_ != true, but received killed_:1 == true:1.] (at /paddle/paddle/fluid/operators/reader/blocking_queue.h:166)

2.对PaddleOCR相关源码进行分析。

已知问题出在某处递归上,初步分析,可能是源码中的某处代码或者错误触发了死循环,导致不停的调用“自己”导致内存溢出,根据错误信息,问题出现在ppocr\data\simple_dataset.py下的特殊方法__getitem__中,分析该方法的代码,果然发现了一处递归。

在这里插入图片描述

try…except…中是根据txt文件对每个图片数据进行处理的代码,若一切无误则返回一个outs字典,若捕捉到错误则outs为None,且outs为None时,会随机拿一个数据补上这个数据,若随机拿的数据一直是错误,这个递归就会陷入死循环,以抵达最大深度或内存溢出结束。

通过追踪data变量可以发现,发现错误出现在标注文本中,原本的0、90、180、270都莫名其妙的多了一个\r,导致ops中的ClsLabelEncode无法进行检索,ClsLabelEncode代码在ppocr\data\imaug\label_ops.py的ClsLabelEncode类中,无法检索到则返回None,导致所有的图片的类型都匹配失败。

在这里插入图片描述

解决问题

1.解决方案

如果你的标签里没有\r,你又不想动数据,则直接简单粗暴的把\r替换为空。代码在ppocr\data\simple_dataset.py的130行:

data = {'img_path': img_path, 'label': label} # 修改前
data = {'img_path': img_path, 'label': label.replace('\r', '')} # 修改后

2.\r哪里来的,为什么会这样呢?

源码中,ppocr\data\simple_dataset.py下的get_image_info_list方法是读取txt文件的代码:

with open(file, "rb") as f:
    lines = f.readlines()

因为源码中以rb方式打开了txt文件,导致读取时多了一个\r,具体的解释在这里可以找到。

3.为什么训练检测模型和识别模型时候不会报错?

对于标签的处理代码,在ppocr\data\imaug\label_ops.py中,分析可知:

检测模型是DetLabelEncode类,检测图像的标签是json数据,而代码中的json.loads()方法会自动忽略结尾的\r。

识别模型是CTCLabelEncode类,类中存在一个encode方法来检查字符是否在规定的字典中,\r一般不会在字典中所以被忽略了。

爆雷的只有没有经过处理的方向识别模型了。

总结

1.这并不算PaddleOCR的BUG,只能说没有考虑到所有情况。
2.个人不了解open函数r和rb的基础知识(也可能选择性忘记了)。

底下评论

问题一:

博主你好,我在对图片进行译码时报错

(paddle2) D:\PaddleOCR-release-2.7>python tools/infer_rec.py -c configs/rec/PP-OCRv3/morse_PP-OCRv3_rec.yml -o "Global.pretrained_model=D:/PaddleOCR-release-2.7/rec-300/iter_epoch_300.pdparams" "Global.infer_img=D:/design/data/ocr_img/ocr_img_cut/mixed/1-l1q_snr=5.jpg"
Traceback (most recent call last):
File "tools/infer_rec.py", line 194, in <module>
config, device, logger, vdl_writer = program.preprocess()
File "D:\PaddleOCR-release-2.7\tools\program.py", line 635, in preprocess
FLAGS = ArgsParser().parse_args()
File "D:\PaddleOCR-release-2.7\tools\program.py", line 61, in parse_args
args.opt = self._parse_opt(args.opt)
File "D:\PaddleOCR-release-2.7\tools\program.py", line 70, in _parse_opt
k, v = s.split('=')
ValueError: too many values to unpack (expected 2)

请问这是什么情况

问题二:

问题是以前 没有现在突然出现 我也是 2w 数据集出现问题 在数据集增多的情况下出现问题

问题三:

大佬,我看官方只支持0和180,在哪里修改代码才能做到90、270度的分类







zhy-_-

【百度PaddleOCR踩坑】报错:RecursionError: maximum recursion depth exceeded while calling a Python object

<think>我们正在讨论一个特殊场景:用户遇到了RecursionError,但实际递归深度并未达到1000次(默认限制)。这看起来违反直觉,但确实可能发生。以下我将分析可能原因并提供解决方案。 ### 原因分析 1. **多线程/异步环境中的递归限制** 在Python中,递归深度限制是**针对每个线程独立计算**的。如果程序使用多线程,每个线程的递归深度单独计数。但若主线程或其他线程同时进行深层递归,可能触发全局限制[^1][^2]。 2. **递归函数内调用其他递归函数** 递归函数A调用递归函数B时,两者的调用栈会叠加。例如: ```python def func_A(n): if n == 0: return func_B(n) # 调用另一个递归函数 def func_B(n): if n == 0: return func_A(n-1) # 互相调用 ``` 此时总递归深度为`func_A`和`func_B`的调用次数之和,即使单函数未超限,总和也可能超限[^3]。 3. **C扩展或内置函数递归** 部分C扩展模块(如NumPy、Pandas)或Python内置函数(如`__str__`、`__repr__`)可能隐式递归。例如: ```python class Node: def __init__(self, children): self.children = children def __repr__(self): return f"Node({self.children})" # 递归调用子节点的__repr__ # 循环引用导致无限递归 node = Node([]) node.children.append(node) print(node) # 触发RecursionError ``` 此类递归不直接可见,但会消耗栈深度[^4]。 4. **装饰器或元类导致的间接递归** 装饰器可能意外增加递归深度。例如: ```python def debug_decorator(func): def wrapper(*args): print("Calling", func.__name__) return func(*args) # 调用原函数 return wrapper @debug_decorator def recursive_func(n): if n == 0: return recursive_func(n-1) # 实际递归深度 = n + 装饰器调用次数 ``` 5. **系统栈空间不足** 操作系统对线程栈大小有限制(如Linux默认8MB)。即使递归次数较少,若单次调用占用栈过大(如大型局部变量),也可能提前触发栈溢出[^5]。 ### 解决方案 #### 1. 检查代码中的间接递归 - 排查是否存在函数间相互调用(如A→B→A)。 - 使用`sys._getframe().f_back`跟踪调用栈: ```python import sys def print_stack(depth=0): frame = sys._getframe() stack = [] while frame: stack.append(f"{frame.f_code.co_name}:{frame.f_lineno}") frame = frame.f_back print(f"Current depth: {len(stack)}") print("Stack trace:\n" + "\n".join(stack[-10:])) # 打印最后10层 ``` #### 2. 避免对象自引用 - 对包含循环引用的对象,重写`__repr__`或`__str__`方法: ```python class Node: def __init__(self, children): self.children = children def __repr__(self): return "Node(children)" # 避免递归输出子节点 ``` #### 3. 增加线程栈大小 - 对于多线程场景,可通过`threading.stack_size()`调整: ```python import threading threading.stack_size(1024*1024*32) # 32MB ``` 注意:必须在创建线程前设置。 #### 4. 使用迭代重写递归 将递归改为迭代(参考前文方法),这是最彻底的解决方案。 #### 5. 监控实际递归深度 在递归函数中添加计数器: ```python import sys def recursive_func(n, depth=0): if depth > 500: # 设置安全阈值 raise RuntimeError("Depth exceeds safe limit") # ...正常递归逻辑... recursive_func(n-1, depth+1) ``` ### 特殊情况:递归深度未达1000却报错 若递归深度明显小于1000仍报错,可能是: - **局部变量过大**:单个函数调用占用大量栈空间(如`local_var = [0]*1000000`)。 **解决**:减少栈占用,或用堆分配(如`global`或`heapq`)。 - **第三方库bug**:如旧版pyinstaller或tokenizer的已知问题[^4][^5]。 **解决**:更新库版本或应用补丁。 ### 总结流程 ```mermaid graph TD A[出现RecursionError但递归深度<1000] --> B{检查原因} B --> C1[多线程/异步环境] B --> C2[函数间递归调用] B --> C3[隐式递归(如__repr__)] B --> C4[局部变量过大] B --> C5[第三方库问题] C1 --> D1[增加线程栈大小] C2 --> D2[改为迭代或消除交叉递归] C3 --> D3[重写特殊方法] C4 --> D4[减少栈占用] C5 --> D5[更新库版本] ``` 通过以上方法,您可定位并解决非常规递归深度问题。若仍无法解决,建议提供最小可复现代码以便进一步分析。 §§相关问题§§ 1. 如何检测Python程序中的循环引用? 2. 在多线程环境中如何安全地使用递归? 3. 如何计算Python函数的栈空间占用? 4. 哪些Python内置函数可能导致隐式递归? 5. 如何为包含递归的Python程序设置线程栈大小? [^1]: 引用自:RecursionError: maximum recursion depth exceeded原因及解决办法。 [^2]: 引用自:【Python】解决Python报错RecursionError: maximum recursion depth exceeded。 [^3]: 引用自:Python报错RecursionError: maximum recursion depth exceeded。 [^4]: 引用自:tokenizer出现RecursionError: maximum recursion depth exceeded while getting the str of an object的解决方案。 [^5]: 引用自:Python:pyinstaller报错【A RecursionError maximum recursion depth exceeded occurred】。</think>### Python RecursionError 但递归深度未达1000的原因与解决方案 当出现 `RecursionError: maximum recursion depth exceeded` 但实际递归调用次数未达到默认1000次限制时,通常有以下特殊原因及解决方案: --- #### 一、常见原因分析 1. **隐式递归调用** - 对象方法(如 `__str__`, `__repr__`, `__getattr__`)被意外递归调用 - **示例**: ```python class Node: def __init__(self, value): self.value = value self.children = [] def __repr__(self): # 错误:递归调用子节点的__repr__ return f"Node({self.value}, {self.children})" root = Node(1) child = Node(2) root.children.append(child) child.children.append(root) # 循环引用 print(root) # 触发RecursionError ``` *引用自对象循环引用场景[^4]* 2. **装饰器叠加递归** - 多层装饰器导致实际调用深度倍增 - **示例**: ```python def debug_decorator(func): def wrapper(*args): print(f"Calling {func.__name__}") return func(*args) # 装饰器本身增加调用深度 return wrapper @debug_decorator def recursive_func(n): if n <= 0: return recursive_func(n-1) # 实际深度 = n * 装饰器层数 ``` 3. **第三方库内部递归** - 使用库(如NLTK、SpaCy)时,其内部实现存在递归调用 - 典型场景:复杂文本解析、语法树生成[^4] 4. **系统栈空间不足** - 单次递归调用占用过大栈空间(如大型局部变量) - 递归深度远小于1000时即触发系统级栈溢出 5. **元类/metaclass 递归** - 元类中的 `__new__` 或 `__prepare__` 方法意外递归 --- #### 二、针对性解决方案 ##### 1. 对象循环引用问题 ```python class Node: def __init__(self, value): self.value = value self.children = [] def __repr__(self): # 安全方案:限制递归深度 return f"Node({self.value}, children_count={len(self.children)})" ``` ##### 2. 装饰器深度优化 ```python import functools def debug_decorator(func): @functools.wraps(func) def wrapper(*args): # 添加深度监控 if wrapper.depth > 100: raise RuntimeError("Depth limit reached") wrapper.depth += 1 result = func(*args) wrapper.depth -= 1 return result wrapper.depth = 0 return wrapper ``` ##### 3. 第三方库问题处理 - **步骤**: 1. 更新库到最新版本 2. 限制输入规模(如缩短文本长度) 3. 替换为迭代型工具(如SpaCy的`nlp.pipe`替代逐句处理) ##### 4. 栈空间优化 ```python def recursive_func(n, large_data=None): # 将大型数据移出栈帧 large_data = large_data or [] # 使用生成器减少内存占用 if n <= 0: yield else: yield from recursive_func(n-1, large_data) ``` ##### 5. 元类递归预防 ```python class SafeMeta(type): def __new__(cls, name, bases, dct): # 避免在__new__中创建同类实例 if not hasattr(cls, '_creating'): cls._creating = True instance = super().__new__(cls, name, bases, dct) delattr(cls, '_creating') return instance else: raise RuntimeError("Recursive metaclass call") ``` --- #### 三、调试工具与方法 1. **调用栈分析** ```python import sys import traceback def recursive_func(n): if n % 50 == 0: # 每50层打印栈信息 traceback.print_stack(limit=5) # ...递归逻辑... ``` 2. **深度监控装饰器** ```python def depth_monitor(func): def wrapper(*args, **kwargs): wrapper.count += 1 if wrapper.count > 500: raise RuntimeError(f"Depth exceeded at {wrapper.count}") result = func(*args, **kwargs) wrapper.count -= 1 return result wrapper.count = 0 return wrapper ``` 3. **内存分析工具** ```bash pip install memory-profiler mprof run --include-children your_script.py ``` --- #### 四、根本性预防策略 | **方案** | **适用场景** | **优势** | |-----------------------|----------------------------|-----------------------------| | 迭代替代递归 | 算法可改写 | 彻底避免递归限制 | | 尾递归优化 | 函数式编程场景 | 减少栈空间占用 | | 显式栈管理 | 复杂递归逻辑 | 完全控制调用深度 | | 分治+循环 | 大数据处理 | 并行处理能力 | > **关键提示**:当递归深度异常时,优先检查是否有**间接递归**(如通过属性访问触发的方法调用)而非直接递归调用[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坦笑&&life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值