PaddleOCR 2.6全流程踩坑:环境依赖、数据标注、训练与导出推理
简介面向使用 PaddleOCR 2.6 开展中文 OCR 检测与识别实践的开发者、学生及算法初学者这份教程型文档围绕从环境搭建到模型落地的完整流程展开适合项目复现、课程实验与自有数据训练前的流程自查。压缩包仅含 1 个 docx 文件约 78KB以单篇图文笔记形式组织篇幅紧凑便于按章节查阅和本地留存。内容覆盖虚拟环境与依赖安装、PPOCRLabel 自动标注及识别/检测标签生成、检测与识别数据集划分、YML 参数配置、训练与验证命令、推理模型导出以及串联预测等关键环节对检测、识别与方向分类组件的依赖关系、权重保存策略也有交代并提示导出时预训练权重加载的常见坑帮助读者减少配置试错、理清训练链路。已有 1308 人学习适合需要快速熟悉 PaddleOCR 2.6 工程流程、对照搭建自有数据训练任务的人群。1. 环境装完就报 No module named yamlPaddleOCR 2.6 第一个坑拉下 PaddleOCR 2.6 源码照着快速开始敲完三条命令cd PPOCRLabel python PPOCRLabel.py --lang ch终端回你一句ModuleNotFoundError: No module named yaml。做 OCR 训练的人对这一幕大多不陌生环境、标注、数据集划分、yml 配置、训练验证、导出推理六道关卡每道都埋着几个不显眼的坑而且报错信息经常把人往错的方向带。这篇按我自己的操作顺序复盘一遍从装不上依赖、PPOCRLabel 导出标签、用脚本划分数据集、改 yml 到训练、验证、导出和串联预测每个故障都给到命令、参数含义和判断依据。新手照着敲能跑通熟手能看到每一步为什么这么配、崩了先看哪里。2. 环境依赖No module named yaml 和 paddlepaddle-gpu 装不上的排查顺序这一章的三个故障几乎占了我在这套框架上手阶段一半的时间。它们的共同点是报错信息看着都像“代码写错了”实际全是环境和顺序问题。2.1 三条安装命令的顺序和解释器归属先把顺序摆正。下面这段是我现在固定使用的安装流程跑在源码根目录下# 1) 建独立环境避免和机器上已有的 paddle 互相覆盖 conda create -n PaddleOCR python3.9 -y conda activate PaddleOCR # 2) 一定要进到源码根目录requirements.txt 是相对路径 cd /path/to/PaddleOCR # 3) 先装框架本体再装仓库依赖最后装打包好的推理包和分类组件 python -m pip install --upgrade pip python -m pip install paddlepaddle-gpu # 只有 CPU 的机器换成 paddlepaddle python -m pip install -r requirements.txt python -m pip install paddleocr2.6.0.3 python -m pip install paddleclas2.4.3python -m pip而不是裸pip是因为一台机器上常有 conda 的 pip、系统的 pip、用户目录的 pip 三个入口裸pip install经常把包装进别的解释器然后你在这个环境里import不到报错长得跟没装一模一样。这是第一个高频翻车点。paddlepaddle-gpu要和你机器上的 CUDA 对齐。装完用下面这行验输出成功提示才算数python -c import paddle; paddle.utils.run_check()如果报ImportError: libcudart.so.xx: cannot open shared object file说明驱动或 CUDA 版本和这个 wheel 对不上换对应 CUDA 版本的安装命令别在这个报错上改代码。装不动、下载极慢的时候加-i指向你手头能通的 pypi 镜像即可命令其余部分不动。还有一个隐蔽的坑先装了paddlepaddleCPU 版又装paddlepaddle-gpu两个包会互相覆盖最后留下的是后装的那个但中间编译进来的依赖可能已经混了。我一般会先pip uninstall paddlepaddle paddlepaddle-gpu各卸一遍再干净装一次。2.2 ModuleNotFoundError: No module named yaml 的根因这个报错的迷惑性在于它听起来像“少装一个叫 yml 的模块”于是很多人包括我早期直接敲了pip install yml。装是装上了重启脚本照旧报同样的错。原因是 Python 里这个模块的导入名是yaml而 PyPI 上的包名是PyYAML两者不一致至于yml这个包名字对不上装上去也不解决任何问题。正确做法只有一条# 包名是 PyYAML导入名是 yaml不是 yml python -m pip install PyYAML # 验证装到了当前解释器里 python -c import yaml; print(yaml.__version__)第二条验证命令是关键。如果pip install说“已安装”但import yaml还是失败说明装到了别的解释器回到 2.1 用python -m pip重装。装完 PyYAML 6.x 之后还可能撞上第二个报错TypeError: load() missing 1 required positional argument: Loader这是 PyYAML 6 收紧了yaml.load()的签名老代码不传Loader就会炸。两条路把版本压回去或者改调用点。# 路线一压到兼容版本Python 3.10 以上可能编译失败 python -m pip install PyYAML5.4.1# 路线二保留 6.x把脚本里的 yaml.load(f) 统一改成安全加载 with open(cfg_path, r, encodingutf-8) as f: # 显式传 Loader既兼容 PyYAML 6也比默认 loader 安全 cfg yaml.load(f, Loaderyaml.SafeLoader)我一般选路线二改一处少一处隐患而且不用为了一个依赖把 Python 版本绑死。判断该走哪条报错里出现Loader就是这个问题报No module named yaml就是没装对解释器两个报错别混为一谈。2.3 paddleocr 与 paddleclas 的版本咬合源码仓库里那套训练脚本和pip install paddleocr2.6.0.3装进来的推理包是两个东西。前者跑tools/train.py做训练后者是打包好的、可以直接命令行出结果的推理工具。两个都留着不冲突但版本要对得上否则会出现“训练脚本能跑、推理包导入就报ImportError: cannot import name ...”这种莫名其妙的组合。我现在装完会做三步自检# 1) 看三个 paddle 相关包的版本是否同时存在、版本是否同代 python -m pip list | grep -i paddle # 2) 推理包能否单独跑通不涉及训练代码 paddleocr --image_dir ./doc/imgs/11.jpg --use_angle_cls true # 3) 训练脚本能否正常解析配置 python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml --help第二步能出结果说明推理链路是通的第三步能把参数打出来说明训练链路的环境没问题。这两步都过了再去碰数据集能省掉大量“以为是数据问题、其实是环境问题”的排查时间。paddleclas是分类组件做方向分类或者文档图像分类时才真正用到装它主要是为了让requirements.txt里的相关依赖不至于互相拉扯版本。3. 标注与划分PPOCRLabel 导出后 rec_gt.txt 和 Label.txt 对不上怎么查环境通了之后下一个卡点几乎都出在数据上。而这套流程里最容易出错的地方是检测和识别用的根本不是同一种标签文件很多人把两者混着喂训练要么直接报路径不存在要么跑起来精度诡异。3.1 PPOCRLabel 启动与自动标注标注工具在源码的PPOCRLabel目录下它有自己的依赖清单要单独装一遍cd PPOCRLabel # 标注工具的依赖和主仓库不完全一样必须单独装 python -m pip install -r requirements.txt # --lang ch 必须和脚本在同一行写错位置参数不生效 python PPOCRLabel.py --lang ch--lang ch指定中文界面和中文识别模型第一次点“自动标注”时会去拉轻量的检测和识别模型网络不好就会卡在“加载模型”那一步看着像死机其实是在下载。判断方式很简单看终端有没有下载进度或者缓存目录里的文件体积有没有在涨。自动标注跑完之后要人工过一遍框和文字重点是三类样本框切偏的、框内文字识别错的、竖排或者倾斜文字的。这一步偷懒后面训练出来的模型就会稳定地错在同一批样本上而且很难通过调参救回来。确认无误后导出结果会得到两份东西文件或目录用途内容形态Label.txt检测模型训练标签每行是图片路径加该图上所有文字框的坐标和转写内容fileState.txt标注状态记录记录每张图是否已确认不参与训练rec_gt.txt识别模型训练标签每行是裁好的小图路径加这一行的文字crop_img/识别模型训练图片按文字行裁好的小图是rec_gt.txt里路径的实体3.2 Label.txt 与 rec_gt.txt 的对应关系把两份文件的内容摊开看就明白为什么不能混用了。检测标签长这样train_data/det/img/001.jpg [{transcription: 发票代码, points: [[12, 30], [200, 30], [200, 58], [12, 58]], difficult: false, key_clip: 0, id: 0}]一行一条样本transcription是框里的文字points是四个角点坐标训练检测模型时要的是“整图 若干个框”。识别标签长这样crop_img/001_0.jpg 发票代码一行也是一条样本但左边是已经裁好的单行小图右边就是这一行的文字。如果你把检测标签里的原图路径直接塞给识别训练模型会把整张图当成一行文字去认loss 会一直压不下来还查不出原因——因为路径确实是存在的不报错。提示识别训练永远用crop_img/里裁出来的小图不要用原图crop_img目录和rec_gt.txt必须一起搬走只搬 txt 必炸。3.3 数据集划分两个脚本的顺序不能反划分脚本有两个先跑检测的再跑识别的顺序反了识别那份就对不上。先是检测划分。打开PPOCRLabel/gen_det_train_val_test.py改的是文件开头的参数区# PPOCRLabel/gen_det_train_val_test.py 参数区 label_file ./Label.txt # PPOCRLabel 导出的检测标签 img_dir ./train_data/det/img # 原始图片目录和 Label.txt 里的路径基准要一致 out_dir ./train_data/det # train.txt / val.txt / test.txt 的输出位置 # 三个比例之和必须是 1.0只想要训练和测试就把 val_ratio 设成 0 train_ratio, val_ratio, test_ratio 0.8, 0.1, 0.1改完在当前目录执行python gen_det_train_val_test.py执行完检查三件事三个 txt 的行数之和等于Label.txt的行数随便抽一行里的图片路径ls得到train 和 val 之间没有重复图片。前两条用命令就能验# 行数对不上说明有样本被丢了通常是某些图找不到 wc -l train.txt val.txt test.txt # 抽第一行看路径能不能落地 head -n 1 train.txt然后是识别划分PPOCRLabel/gen_rec_follow_det.py。这个脚本的设计意图是跟着检测的划分走一张原图进了训练集它裁出来的所有小图也进识别训练集原图进了测试集小图也进测试集。这样才不会有数据泄漏——同一张图的行既训练又评测指标会虚高模型上线就露馅。# PPOCRLabel/gen_rec_follow_det.py 参数区 det_dir ./train_data/det # 上一步生成的 train.txt / val.txt 所在目录 rec_label ./rec_gt.txt # PPOCRLabel 导出的识别标签 out_dir ./train_data/rec # rec_train.txt / rec_test.txt 输出位置 # 这里只分训练和测试要三份就自己按同样逻辑加一段 val 的处理执行后得到rec_train.txt和rec_test.txt。如果你也想要验证集就在脚本里照着 train/test 的写法再补一段逻辑无非是“按原图文件名把 rec 样本归类到同一个集合”。最后统一验一遍识别数据集# rec 标签里的路径是相对 crop_img 的所以要拼上前缀再判断存在性 head -n 1 rec_train.txt ls ./train_data/rec/crop_img/ | head对不上就会出现FileNotFoundError: [Errno 2] ... crop_img/xxx.jpg。这个报错十有八九不是文件丢了而是相对路径的基准错了脚本按crop_img/开头写路径但你实际把图放在别的层级。要么改脚本输出时的前缀要么在 yml 里用data_dir把前缀补上下一章讲后者。4. yml 配置与训练改错一个 data_dir训练直接报 FileNotFoundError配置文件这块的坑特点是“改的时候不觉得有问题跑起来才知道”。而且很多字段之间有隐含的拼接关系单看某一个字段看不出错。4.1 复制配置与几个必须动的字段不要直接改官方配置复制一份出来加后缀出问题好对比cp configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml \ configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml_myself.yml然后只动这几个字段Global: # 预训练权重从头训练就删掉这一行这里放的是官方蒸馏模型的目录 pretrained_model: ./pretrain_models/ch_PP-OCRv3_det_distill_train/best_accuracy # 训练输出目录日志和权重都落在这里 save_model_dir: ./output/ch_PP-OCR_v3_det_1206/ # [起始步, 间隔步]从第 0 步开始每 10 步验证一次 eval_batch_step: [0, 10] Train: dataset: # 这个目录会和 txt 里的相对路径拼起来 data_dir: ./train_data/det/img label_file_list: - ./train_data/det/train.txt loader: batch_size_per_card: 8 Eval: dataset: data_dir: ./train_data/det/img label_file_list: - ./train_data/det/val.txteval_batch_step设成 10 属于“小数据集上比较激进”的配置好处是每 10 步就存一次精度最高的best_accuracy跑几百步就能看出模型有没有在学代价是验证本身要花时间数据集一大就会明显拖慢训练。我一般先按这个密度跑一轮看曲线确认没问题再放大到几百。4.2 data_dir 和 txt 里路径的拼接规则这是最容易出错的地方而且报错形式统一是FileNotFoundError看不出逻辑问题。规则其实一句话最终路径 data_dir txt 里的路径。所以分两种情况第一种检测数据集。划分脚本写进train.txt的路径可能已经带了完整前缀比如train_data/det/img/001.jpg这时再把data_dir设成./train_data/det/img就会拼成./train_data/det/img/train_data/det/img/001.jpg必然找不到。这种时候data_dir留空或者设成./即可。第二种识别数据集。rec_train.txt里的路径形如crop_img/001_0.jpg是相对的而图片实际放在./OCR/ALL_1206/crop_img下。这时必须靠data_dir补齐前缀Train: dataset: # 图片实际在 ./OCR/ALL_1206/crop_img/001_0.jpg # txt 里只写了 crop_img/001_0.jpg所以这里补到父级 data_dir: ./OCR/ALL_1206 label_file_list: - ./train_data/rec/rec_train.txt判断方法很土但很准拿 txt 里第一行的路径手动和data_dir拼一次然后把结果丢给ls。拼出来的文件存在配置就是对的不存在就去调data_dir别去改 txt。注意训练集和验证集的data_dir要分别检查很多人的 val 报错就是因为只改了 Train 忘了 Eval。4.3 batch_size 超过样本数会怎么炸batch_size_per_card不能大于你训练集里的样本数量。样本只有 6 张、batch 设了 8 的时候不同版本表现不一样有的直接AssertionError有的因为drop_last把所有 batch 都丢掉一个 epoch 里迭代器是空的然后以一个更难懂的错误收场。先量一下样本数再定 batch# 训练集有多少张图batch size 就该在这个数以下 wc -l ./train_data/det/train.txt显存那边是另一个方向的约束。报Out of memory或者显存占用一路涨到顶就把batch_size_per_card往下压8 → 4 → 2同时把loader.num_workers调到 2 或 0 减少数据加载进程的内存占用。这两项是训练阶段最常见的调节旋钮别一上来就怀疑模型结构。4.4 训练与验证命令以及 -o 覆盖参数配置改好之后训练命令本身很简单# 用改好的配置文件启动训练 python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml_myself.yml想在这次运行里临时换预训练权重不去动 yml用-o覆盖注意字符串要带引号# -o 覆盖的点号路径对应 yml 里的层级字符串值必须加引号 python tools/train.py \ -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml_myself.yml \ -o Global.pretrained_modelpretrain_models/best_accuracy验证时用Global.checkpoints指定具体权重指向的是训练输出目录里的best_accuracy不带后缀python tools/eval.py \ -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml_myself.yml \ -o Global.checkpointsD:/model/PaddleOCR-release-117/PaddleOCR-release-2.6/output/ch_PP-OCR_v3_det_1206/best_accuracy这里的路径要指到前缀不是.pdparams文件本身。指错了会报找不到权重但报错文案不会直说容易误判成目录没生成。训练过程中值得盯的两个东西save_model_dir下的train.log和权重目录。日志里能看到每多少步验证一次、当前 best 是多少权重目录里best_accuracy不断被覆盖latest是最近一次保存。如果save_epoch_step设得太小目录里会堆出一堆按 epoch 存的权重很快把盘吃满这也是我把保存策略交给“验证时存 best”的原因——只留一个最好用的权重比留二十个中间产物省心得多。5. 导出与推理Global.pretrained_model 用 -o 传不进去导出的还是预训练模型这一章是我在这套流程里踩得最深的一个坑因为它不报错跑完一切正常只有拿去推理的时候才隐约觉得“怎么跟没训练一样”。5.1 现象导出成功但精度停在预训练水平按常规写法导出python tools/export_model.py \ -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml_myself.yml \ -o Global.pretrained_modelD:/model/PaddleOCR-release-117/PaddleOCR-release-2.6/output/ch_PP-OCR_v3_det_1206/best_accuracy \ -o Global.save_inference_dir./inference/det_cml_ppocrv3_1206命令退出码是 0inference/det_cml_ppocrv3_1206/下也生成了推理模型文件看上去一切正常。但拿它去跑测试图效果和没用自己数据训练过一样——该认错的字还错该漏的行还漏。这个坑阴的地方在于它没有日志能证明你传错了。你以为覆盖生效了实际导出脚本用的还是 yml 里那个pretrained_model。5.2 根因这次覆盖没落到它该落的地方在 2.6 这套导出脚本里-o Global.pretrained_model...这个覆盖在导出流程中没有真正作用到加载权重的那一步实际加载的仍然是 yml 配置文件里写的Global.pretrained_model。也就是说命令行看着像把权重换成了训练输出实际用的还是配置里那个预训练目录。我一般不去深究到底是谁覆盖谁、执行顺序是什么因为验证成本太高、收益太低。判据很直接export 前后比对一下权重来源。如果导出前你把 yml 里的pretrained_model改成了训练输出目录导出效果立刻变了那就确诊是这条覆盖没生效。5.3 解决把训练后的权重写进 yml 再导出做法只有一步改 yml不动-o。Global: # 直接写死成训练产出的 best_accuracy导出脚本读的就是这个值 pretrained_model: D:/model/PaddleOCR-release-117/PaddleOCR-release-2.6/output/ch_PP-OCR_v3_det_1206/best_accuracy然后导出的命令只保留输出目录# 不再用 -o 覆盖权重只指定推理模型输出位置 python tools/export_model.py \ -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml_myself.yml \ -o Global.save_inference_dir./inference/det_cml_ppocrv3_1206save_inference_dir决定导出的.pdmodel和参数文件落在哪这个字段的覆盖是有效的可以放心用-o。导出完做一次对比验证拿同一张测试图分别用导出前后的模型跑一次predict_system如果结果完全一样说明权重根本没换回去检查 yml如果结果变了、且是变好才算真导出了。提示改完 yml 记得把它备份一份否则下次重新训练时这个路径会指向旧权重又是一轮“训练了但没生效”。蒸馏配置还要多看一眼结构。这类 cml 配置导出后目录里会有Student/和Teacher/两套子目录推理时要用的是学生模型那一套。目录选错同样表现为“效果不对但不报错”。5.4 串联推理det 和 rec 各自的目录都要指对导出完成后串联预测命令里的两个目录分别指向检测和识别的学生模型python tools/infer/predict_system.py \ --image_dirD:/model/PaddleOCR-release-117/PaddleOCR-release-2.6/OCR/ALL_1206/test \ --det_model_dir./inference/det_cml_ppocrv3_1206/Student \ --rec_model_dir./inference/rec_ppocr_v3_distillation_1206/Student三个参数各有各的失败方式image_dir指错会直接报目录不存在这个好查det_model_dir指到父目录而不是Student有些版本会报找不到模型文件有些版本会加载到 Teacher 上效果异常但能跑rec_model_dir指向没重新导出的旧目录结果就是识别文字全是老样子。所以串联之前先把两个 inference 目录列一遍确认里面有模型文件再去跑整条链路# 导出结果自查 должен 能看到推理模型文件 ls ./inference/det_cml_ppocrv3_1206/Student ls ./inference/rec_ppocr_v3_distillation_1206/Student上面那行注释里混进了非中文字符实际写脚本时按ls原样敲就好。整条链路跑通之后再回头去看那些单模块的指标才不会出现“eval 精度很高、实际用起来很差”的割裂感——串联推理才是最终判据。6. 常见问题与快速定位五条踩坑记录和一张报错对照表这些坑我在不同项目里反复见过写在一起方便对照。第一条pip install yml装完仍报No module named yaml。现象是安装成功但导入失败。原因是包名与导入名不一致yml这个包跟这里毫无关系。解决是装PyYAML再用python -c import yaml确认装进了当前解释器。第二条训练一启动 loss 就异常低或者一启动精度就异常高。现象是模型表现不像从零开始。原因是 yml 里的pretrained_model还指着上次的训练输出目录等于在旧权重上继续练。解决是把它改成真正的预训练权重或者整行删掉从头训。第三条FileNotFoundError: ...crop_img/xxx.jpg。现象是文件明明在硬盘上却找不到。原因是识别标签里是相对路径data_dir没补前缀或者前缀补多了一层。解决是拿 txt 第一行手动和data_dir拼一次用ls验证拼出来的路径。第四条Out of memory或者显存一路涨满。现象是训练几十步后进程被杀。原因是batch_size_per_card相对显存过大或者num_workers开太多导致数据进程占内存。解决是把 batch 减半、num_workers降到 2 或 0再试。第五条导出成功但推理效果和训练前没区别。现象是不报错、结果不对。原因是-o覆盖Global.pretrained_model在导出流程里没生效实际加载的是 yml 里的旧值。解决是把训练输出的best_accuracy直接写进 yml 再导出。真到现场我用的定位顺序是这样的先看最后 20 行里第一个Error倒着找三段信息——出错的阶段、这个阶段的输入是什么、输入的路径能不能ls到。就这三步能覆盖上面九成的故障。报错或现象关键词大概率阶段第一条排查命令No module named yaml环境python -m pip show PyYAMLTypeError: load() missing ... Loader环境python -c import yaml; print(yaml.__version__)cannot open shared object file环境nvidia-smipython -c import paddleFileNotFoundError: .../xxx.jpg数据或配置head -n 1 train.txt拼data_dir后lsAssertionError出现在 dataloader配置wc -l train.txtOut of memory训练nvidia-smi导出成功、效果不变导出查 yml 里pretrained_model指向推理结果和 eval 对不上推理ls inference/*/Student还有一个习惯性的验证动作每跑完一个阶段都拿一条最小命令确认这个阶段的产物真的存在、真的可用——装完环境跑一次paddleocr --image_dir划分完数据head一行ls一次训练完ls output/看best_accuracy在不在导出完ls inference/*/Student。四个ls加起来不到一分钟能把后面几小时的排查省掉。血泪经验是这套流程里最贵的不是调参是“以为某一步生效了其实没有”。从那以后我每次改完 yml、每次导出模型都会强制跑一遍最小验证确认产物真的换了再往下走。希望帮到你。本文还有配套的精品资源点击获取