上篇我们聊完PaddleOCR的安装和官方模型直接跑通识别,这篇接着把"怎么训练自己的模型"这层窗户纸捅破。标题挂着"AI6-PaddleOCR模型训练2",其实就是想写:当你真拿一批自己的图片去微调PaddleOCR时,会遇到什么、要准备什么、哪些参数能改、哪些坑别踩。PaddleOCR这套框架发展到现在,官方预训练模型已经能扛住绝大多数印刷体场景,但真到自己的业务数据上——比如特殊字体、扭曲文本、生僻字、票据印章——还是得走一遍自己的模型训练流程。这篇就按我实际跑项目的顺序,把从数据准备到部署上线的完整链路拆开讲。
先说清楚一个认知:你不需要从零训练一个OCR模型。所谓"训练自己的模型",绝大多数情况下是拿官方预训练模型做微调。PaddleOCR之所以能省那么多算力,靠的就是预训练模型打底,这也是为什么网上搜"resnet预训练模型"、各种backbone权重时,一定要分清它是主干网络权重还是完整训练好的检测/识别模型。
1. 什么场景下,官方模型真的不够用
1.1 官方模型为什么会在你的数据上失灵
PP-OCR系列的识别模型在通用印刷体中文、英文、数字上已经做得非常成熟,但它的训练数据分布和你业务数据的分布大概率不是一回事。我遇到过三类典型场景:
- 第一类是字符集外。官方模型的字典是常见简体中文字符、英文字母、数字,但你的场景里有生僻字、特殊符号、计量单位(比如"㎥"、"²"),模型在推理时只能给一个相似字或者乱码。
- 第二类是字体和排版偏移。比如古籍里的繁体竖排、手写体、艺术字、带干扰线的验证码,通用模型识别率会直线下降。
- 第三类是检测环节出错。PaddleOCR是先检测文本框再识别内容,如果你的版面是表格、印章、倾斜透视文本,检测框画不准,后面识别再强也白搭。
这时候你去调推理参数、换后处理阈值,天花板就在那,最实际的路径就是自己训练。训练的目标不是超越官方模型,而是让模型拟合你的数据分布。
1.2 训练的本质是"微调",不是从零造轮子
第一次接触模型训练的人,最容易产生"我要从零写网络、从零训权重"的误解。实际上PaddleOCR的训练脚本里,pretrained_model这个参数就是干这个的:把官方在超大数据集上训练好的权重作为初始值,只在你自己的数据上继续迭代。这跟学骑车先有个大人扶着是一个道理,你只需要调整平衡,不需要重新发明轮子。
所以在动手之前,先想清楚两件事:
- 你要解决的是检测问题还是识别问题,还是两者都要重新训?检测模型负责"文本在哪",识别模型负责"文本是啥"。很多项目只需要重新训识别模型,检测直接用官方的。
- 你的数据量和标注质量能不能支撑微调?识别模型最低也得有几千张有效标注,否则你训出来的模型只会过拟合到你那几十张图上。
想明白这两点,再往下走。
2. 训练前的数据准备:格式、标注、字典
2.1 千万别把检测和识别任务的标注搞混
PaddleOCR中检测任务和识别任务的数据格式完全不同,这是新手第一道坎。
识别模型(REC)的训练数据格式非常朴素,每行就是:
图片路径 标签文本标签文本是你要让模型识别出来的字符串,注意中间用制表符\t分隔,图片路径和标签之间不能有空格。如果标签里本身带空格(比如英文句子),就要用制表符来分隔路径和标签,标签内的空格保留。
检测模型(DET)则要复杂得多,它需要标注文本框的四个角点:
图片路径 [{"points": [[x1, y1], [x2, y2], [x3, y3], [x4, y4]], "transcription": "GT文本", "ignore": false}, ...]这里的points是四边形四个顶点,顺序要按顺时针或逆时针保持一致,不要求矩形框,多边形也能处理。transcription是对应的文本内容,检测训练时它会参与损失计算,ignore标记为true的框算作"应忽略"区域。
我见过有人把识别模型的数据喂给检测模型,训练直接报shape错,一看就是没搞清楚这个区别。建议动手前先分别跑一遍官方给的快速开始示例,确认两种数据格式各自长什么样。
2.2 标注工具与字符字典
推荐直接用PaddleOCR自带的PPOCRLabel工具做标注,它和检测/识别的数据格式天然兼容,你标完导出就是上面那个JSON格式。手动改JSON效率极低,而且容易格式错误。
识别模型训练前还需要一个dict.txt(字符字典),它的每一行代表一个字符,顺序就是模型输出层softmax的索引顺序。这里有几个常见的坑:
- 字典里不能有重复字符,否则两个索引对应同一个字符,训练目标就乱了。
- 字典里必须包含
blank符号吗?识别模型的CTC解码里,blank字符是内置的,不需要在dict里手动加,但PaddleOCR的配置里会用character_dict_path指向一个纯字符列表。 - 中英文混排场景,尤其要注意全角、半角字符都算不同字符,比如"A"和"A"不是一个东西。
- 空格要不要进字典?如果你有英文句子,空格必须进字典,否则推理时所有空格都会被去掉。
生成字典最稳妥的方式是遍历所有训练标注文本,把所有出现过的字符去重后按频率排序,写进dict.txt。频率排序的好处是可以把高频字符排在前面,对模型收敛有一定的正面影响。
2.3 多少数据才够用
官方模型微调的最低数据量,识别模型我建议每个字符至少出现10次以上,总体样本五六千张起步比较稳。检测模型对数据量的要求更高,因为目标框的形状变化太丰富,一万张是及格线,五万张才能谈效果。
如果你的数据远低于这个量级,先别急着砸时间和算力,优先做两件事:
- 加数据增强。PaddleOCR训练配置里自带
RecAug、RandAugment等增强策略,可以轻微旋转、缩放、颜色扰动、噪声叠加。增强不要做得太过,比如旋转超过30度,印刷体文本识别就基本没人能看懂了。 - 从网上找公开的开源OCR数据集补充进来,混合训练。这样虽然增加了点噪声,但能显著缓解过拟合。
3. 环境与平台:本地GPU与在线训练怎么选
3.1 本地环境搭建里最容易被忽略的版本问题
如果你有NVIDIA显卡,优先装GPU版PaddlePaddle。PaddleOCR官方文档对CUDA版本有明确要求,但很多人在这一步翻车,原因就一个字:版本漂移。
这里敲黑板划重点:paddlepaddle-gpu、CUDA、cuDNN、GPU驱动的版本必须对齐。比如你装paddlepaddle-gpu==2.5.x,对应CUDA 11.7,如果机器上只有CUDA 11.2,或者驱动太老,训练时就会出现"无法定位cuDNN库"或者直接报Out of memory但显存明明是够的。建议严格照官方安装表格装,别凭感觉选最新版本。
装好Paddle之后,PaddleOCR本身用pip安装即可,但训练需要把源码仓库克隆到本地,因为训练脚本、配置文件、工具脚本都在仓库里。很多人只pip了paddleocr包就去找train.py,结果半天找不到——paddleocr是个推理库,训练源码要另外拿一份。
3.2 免费GPU在线平台的实操体验
没有本地GPU的同学也别灰心,现在有几个免费的GPU训练平台可以白嫖,比如AI Studio、Kaggle Notebooks。但在线训练有几个具体问题要注意:
- 会话断开。在线平台的GPU实例不是永久的,长时间没有操作会自动回收,训练到一半断掉是家常便饭。所以训练一定要配
eval和save_model的周期策略,每几百个迭代存一次checkpoint,断了也能从最近断点接着跑。 - 数据上传。几十GB的数据在Notebook环境里上传很痛苦。建议做好数据集后先打包成压缩文件传上去,再在代码里解压,别一个文件一个文件拖。
- 路径不要写死。在线平台的挂载路径和你本地一定不一样,配置文件里的
Global.train_data_dir、Global.epoch_num这些路径参数,到了平台必须改。
我自己比较推荐的做法是:代码和配置先在本地调通(用CPU跑个几十步验证数据管道),再整个项目目录上传在线平台,用GPU跑正式训练。先在本地用少量数据跑通流程,能省掉大量在平台上调试的时间——在线平台调试一次就要排队等GPU,很烦。
3.3 本地和在线,选哪个
| 维度 | 本地GPU | 在线GPU平台 |
|---|---|---|
| 硬件成本 | 一次性投入高 | 免费额度够用 |
| 调试便利性 | 随时改随时跑 | 排队等待、会话易断 |
| 隐私安全 | 数据不出门 | 数据要上传第三方 |
| 长时间训练 | 稳定 | 有会话时长限制 |
如果你的数据涉及敏感信息(比如票据、医疗报告),即便在线平台有隐私承诺,我仍然建议本地训练。数据安全这条线,永远不要指望平台的自律。
4. 配置文件里的每个参数,到底干什么的
4.1 认识PaddleOCR的config体系:yaml文件就是你的训练蓝图
PaddleOCR训练不是靠命令行传几百个参数,而是靠一个yaml配置文件把所有东西都管起来。打开configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml,你会看到五大块:Global、Architecture、Loss、Optimizer、Metric、Dataset。其中Dataset又分Train和Eval两个子配置。
对于首次训练,核心关注这几个参数:
Global.epoch_num:训练轮数。识别模型一般80到150轮就能收敛,不是越多越好,跑太久反而过拟合。Global.pretrained_model:预训练权重路径。这里填官方发布的识别模型训练权重(ch_PP-OCRv3_rec_train之类的),它会作为初始权重加载。如果你不填,就是从零训练,代价是训练时间成倍增加、效果还差。Global.use_gpu:是否用GPU。没GPU的机器记得设成false。Optimizer.lr:学习率。这是所有参数里最敏感的,微调阶段一般设0.0005到0.001,比从零训练低一个量级,因为预训练权重已经在好位置附近了,学习率太大会把权重"踢"出好区域。Optimizer.batch_size.per_device:每张卡上的批大小。受显存限制,识别模型一般16到64,检测模型一般8到16。Dataset.Train.data_dir和label_file_list:你的训练图片目录和标注文件路径。Dataset.Train.transforms:数据增强。新手建议先用RecAug默认配置,不要上来就自定义。
4.2 识别模型和检测模型的关键差异
识别模型是"看图识字",输入是已经裁剪好的文本行图像,输出是字符串;检测模型是"找字在哪",输入是整张图,输出是一堆多边形框。两者在配置上的差异主要体现在:
- 网络结构:识别模型用
MobileNetV3或PP-LCNet做backbone加CTC解码器;检测模型用DB或DB++结构。你搜到的"resnet预训练模型"通常指这两种结构里的backbone预训练权重,替换Architecture.backbone相关配置即可,但整体微调时,直接用官方整个模型的权重就包含了backbone的部分,不需要单独处理resnet权重。 - 输入尺寸:识别模型输出的是文本行,宽度可以动态变化,所以
rec_img_shape一般是[3, 48, 320]这样高固定宽可变的;检测模型输入则是[3, 736, 1280]这类固定整图尺寸。 - loss:识别模型用
CTCLoss,检测模型用DBLoss。自己改配置的时候别改串了。
4.3 训练命令与一个能跑的baseline
环境就绪、数据就绪后,训练命令极简:
cd PaddleOCR # 识别模型训练 python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml # 如果想在默认配置上覆盖某些参数,用 -o 追加 python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml \ -o Global.epoch_num=100 \ Optimizer.batch_size.per_device=32 \ Global.pretrained_model=./pretrain_models/PP-OCRv3_train首次训练我建议先用-o把epoch_num压到10,把batch_size压到4,在CPU上跑几十步,目的是验证数据管道通畅:能正常读图、正常算loss、正常存checkpoint。这一步跑通再上GPU全量训练,能帮你省半天时间。
5. 报nan、显存爆掉、loss僵死:训练现场的真实问题
5.1 训练过程怎么读日志
训练一旦开始,终端会滚动输出训练日志,核心看这几列:
loss:总损失,训练初期应该在2到5之间波动,然后逐步下降。如果loss从几千上万开始,说明学习率大了或者数据有异常。lr:当前学习率,会随scheduler变化。acc:识别模型的字符准确率,最终希望到0.9以上(打印字符级别),但注意这只是训练集上的,评估集要单独看。
日志里的loss在训练初期轻微浮动是正常的,不用焦虑。但如果你发现loss从第一轮开始就在上升,那不是"预热",大概率是配置出问题了。
5.2 模型训练报nan的完整排查思路
"模型训练报nan"这个搜索词常年高居热榜,因为太常见了。我总结一套排查链路,按顺序看:
- 先查学习率。学习率过大是nan的第一原因,尤其是从头训练(没加载预训练)的时候。微调一般
0.0005不会炸,如果你设了0.01还带动量,炸的概率非常高。先把学习率降到0.0001试一次。 - 查数据里有没有异常值。图片损坏、全黑图、标注文件里多了不可见字符,都会让loss失稳。用脚本遍历图片和标注,把打不开的图、空标签样本挑出来删掉。
- 查梯度裁剪。配置里有
grad_clip相关选项,把它设上,比如max_norm=5,能防止梯度爆炸导致的nan。这招治标不治本,但它能让你先排除梯度问题。 - 关混合精度。如果你开启了
AMP混合精度,在某些低端GPU上会因为fp16精度溢出导致nan。把Global.amp相关配置关掉再试。 - 查预训练权重是否匹配。加载了错误的预训练权重(比如用检测模型权重初始化识别模型),前向传播会直接崩掉,loss输出nan。检查配置里的通道数和类别数是否一致。
我印象最深的一次nan,折腾了三个小时,最后发现是标注文件里的标签有个\u3000全角空格,模型把它当成一个不存在的字符索引,直接炸掉。所以数据清洗比调参更重要,这句话我想加重显示。
5.3 显存不够和data loader崩溃
显存不够(OOM)就两点:降batch_size、降输入图片尺寸。识别模型把batch_size从64降到16是很正常的,不要觉得丢人,跑得稳比跑得快重要。
data loader崩溃常见现象是训练刚开始就报一堆worker错误。在Windows上尤其常见,因为PaddleOCR默认的dataloader会拉起多个子进程,Windows进程模型和Linux不一样,容易挂。临时方案是把dataloader_num设成0,强制在主线进程里读数据,性能会打折扣但至少能跑起来;长期方案是换到Linux环境训练,这确实是最省心的路线。
6. 从checkpoint到部署:评估、导出与C++落地
6.1 评估指标:怎么判断模型能不能用
训练过程中脚本会定期做评估,输出metric结果。识别模型主要看acc(字符准确率)和norm_edit_dis(归一化编辑距离),检测模型看precision、recall和hmean(F1)。
光看这些还不够,拿到checkpoint后我会写一个几十行的推理脚本,挑二三十张最难的真实图跑一遍,肉眼看输出结果。训练日志上的acc高不等于业务上能用,尤其在样本类别不均衡时,模型可能只学会了高频字符。此时你该做的是:
- 按字符统计识别错误,找出错误最集中的是哪些字符。
- 回到数据,把这类字符的样本补足,重新微调一轮。
这是OCR效果调优最朴素也最有效的手段,我在几个项目里都是这么把准确率从85%拉到97%的。
6.2 导出inference模型
训练得到的checkpoint(best_accuracy.pdparams)默认是PaddlePaddle的动态图格式,推理前要先转成inference模型(静态图),这样部署时才不需要加载训练时那些冗余配置。
# 识别模型导出示例 python tools/export_model.py \ -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml \ -o Global.pretrained_model=./output/best_accuracy \ Global.save_inference_dir=./inference/rec_model导出后inference目录下有inference.pdmodel和inference.pdiparams两个文件,这才是部署真正用的东西。用tools/infer/predict_rec.py验证一下导出的inference模型和训练时的预测结果一致,再拿去部署,很多部署问题其实都出在导出环节的config没对齐。
6.3 PaddleOCR C++部署思路与边缘设备扩展
说到部署,网上关于"paddleocr c++"的搜索量一直很大,因为项目上线时Python推理往往不够快,尤其是CPU环境。C++部署其实没有那么神秘,原理就是加载上面导出的inference模型,用Paddle Inference的C++ API做前向推理。PaddleOCR官方仓库的deploy目录下有现成C++代码,编译依赖OpenCV和Paddle Inference库。关键点在于:
- 编译时要选对
WITH_GPU选项,CPU版就不要开启GPU选项。 - 预处理逻辑(resize、归一化)要和训练配置完全一致,否则识别率掉一截。
- 后处理(包括文本框还原、CTC解码)在C++里要自己实现,官方示例代码可以救急,但复杂场景还是要读源码改。
如果你部署的目标是边缘设备,比如搜索词里出现的K210这一类,那就要另说。K210的内存和算力非常有限,跑完整的PaddleOCR不现实,通常是在服务器上跑PaddleOCR得到文本行图,再把裁剪后的文字图片扔给边缘设备上的轻量模型做识别。这种架构已经不属于"PaddleOCR训练"的范畴了,但架构设计时要提前考虑,别等训练完了再发现部署不上去。
最后再分享一条我这几年攒下的经验:模型训练的成败,八成在数据,两成在参数。网上能找到各种花里胡哨的调参技巧,但当你真正把一个项目的准确率从"不能用"调到"能用",回头看往往是因为把标注错误改掉了、把缺失的字符样本补齐了。PaddleOCR给了你一套非常成熟的训练框架,你要做的不是去挑战这套框架,而是把你的业务数据理解清楚。如果你正准备训自己的第一个OCR模型,先把数据格式跑通、把预训练模型路径配好,用一次10轮的小训练验证全流程,再考虑调参优化——这个顺序走完,你已经超过一半的人了。