1. 为什么我要自己训练一个OCR模型
OCR这东西,用过的人都知道,通用模型在标准印刷体上表现还行,但一旦碰到特定字体、手写体、票据表格、工业铭牌这些场景,识别率就会断崖式下跌。我最早接触PaddleOCR是因为手头有个项目要批量处理一批老档案扫描件,试了好几个开源方案,要么是识别乱码,要么是对中文竖排支持不好,最后落到PaddleOCR上,发现它中文识别效果确实扎实,而且整套工具链从标注、训练到部署都是齐的,不用自己东拼西凑。
但问题也来了——官方预训练模型虽然强,可它是在通用数据集上训出来的,面对我那些泛黄、带噪点、字体特殊的档案,识别准确率大概只有七成出头。这个数字在实际业务里根本没法用,人工复核的成本比重新录入还高。所以我就动了念头:干脆自己训练一个专用模型。
这篇文章就是把我从零训练PaddleOCR模型的完整过程拆开来讲,包括环境怎么搭、数据怎么标、配置文件怎么改、训练中遇到哪些坑、以及最后怎么评估效果。适合两类人看:一类是手头有特定OCR需求、通用模型搞不定的开发者;另一类是想了解OCR训练全流程、但还没动手实践过的朋友。代码和命令我都会给全,数据集处理的技巧也会重点讲,因为这部分才是真正决定模型上限的东西。
2. 训练环境搭建与版本选择
2.1 硬件与基础环境确认
训练OCR模型对硬件是有要求的。CPU训练不是不能跑,但速度会让你怀疑人生——我试过用纯CPU训一个轻量模型,一个epoch跑了将近四个小时,而同样的数据在单卡GPU上只要十几分钟。所以如果你打算认真训练,一张显存8GB以上的NVIDIA显卡是底线,12GB以上会更从容,因为可以开更大的batch size。
软件层面,PaddlePaddle的GPU版本是必须的。这里有个关键点:PaddlePaddle的版本和CUDA版本必须严格对应,装错了就是各种报错。我的经验是,先去PaddlePaddle官网查版本对照表,确认你的显卡驱动支持的CUDA版本,然后装对应的PaddlePaddle。比如CUDA 11.8就装对应版本的paddlepaddle-gpu,不要想着“差不多就行”,版本不匹配轻则警告重则直接跑不起来。
Python版本建议3.8到3.10之间,太新的版本有些依赖包还没跟上。我用的3.9,一直很稳。另外强烈建议用conda建一个独立环境,因为PaddleOCR的依赖比较多,跟其他项目的包混在一起容易出冲突。
conda create -n paddle_ocr python=3.9 conda activate paddle_ocr2.2 PaddleOCR安装与验证
环境建好之后,先装PaddlePaddle再装PaddleOCR,顺序不能反。装PaddlePaddle的时候根据你的CUDA版本选对应的安装命令,这个官网有生成器,选一下就行。装完之后一定要验证GPU是否可用:
import paddle print(paddle.device.get_device()) print(paddle.utils.run_check())如果输出里有gpu字样,并且run_check通过了,说明基础环境没问题。然后装PaddleOCR:
pip install paddleocr但注意,如果你要训练而不只是推理,还需要把PaddleOCR的源码clone下来,因为训练脚本、配置文件都在源码里:
git clone https://github.com/PaddlePaddle/PaddleOCR.git cd PaddleOCR pip install -r requirements.txt这里有个坑:requirements.txt里的依赖版本可能跟你环境里已有的包冲突,尤其是opencv和numpy。我的做法是先装PaddleOCR的核心依赖,遇到冲突再单独调整,不要一股脑全装。
提示:安装完成后,用
paddleocr --image_dir ./test.jpg --use_gpu true跑一张测试图,确认推理链路是通的。这一步能排除掉大部分环境问题,别等到训练的时候才发现环境有问题。
2.3 目录结构规划
PaddleOCR的源码目录结构比较清晰,但训练自己的数据时,你需要清楚几个关键目录:
configs/:存放所有配置文件,det、rec、cls分别对应检测、识别、方向分类ppocr/:核心代码,包括模型定义、数据加载、后处理等tools/:训练、评估、导出、推理的入口脚本train_data/:我习惯把数据集放在这里,跟源码分开管理
建议在开始之前就把目录规划好,数据、配置、输出模型分开放,不然后面文件多了容易乱。我一般会建一个my_project目录,里面放data、configs、output三个子目录,训练时通过绝对路径引用,这样换机器也不用改太多东西。
3. 数据集处理:决定模型上限的关键环节
3.1 OCR数据集的两种标注格式
PaddleOCR支持两种主流的标注格式,理解它们的区别很重要,因为选错了后面转换很麻烦。
第一种是检测+识别联合标注,也就是一行里同时包含图片路径、检测框坐标和识别文本。格式大概是这样:
image_path\t[{"transcription": "文字内容", "points": [[x1,y1],[x2,y2],[x3,y3],[x4,y4]]}]第二种是检测和识别分开标注,检测用image_path\t[[x1,y1],...],识别用image_path\t文字内容。分开标注的好处是灵活,检测和识别可以独立训练、独立替换;联合标注的好处是一步到位,适合端到端流程。
我的建议是:如果你只是训练识别模型,用分开的识别标注格式就行,一行图片路径加一行文本,简单直接。如果你要训练检测模型,那检测标注必须要有,而且坐标要准确。实际项目中我通常两个都标,因为检测框的质量直接影响识别效果——框歪了,识别再强也白搭。
3.2 数据标注工具与实操技巧
标注工具我用过几个,LabelImg、PPOCRLabel、Labelme都试过。如果是纯OCR项目,PPOCRLabel是最顺手的,因为它是PaddleOCR官方出的,标注完直接导出就是PaddleOCR需要的格式,省去了转换步骤。而且它支持自动标注——先用预训练模型跑一遍,你只需要修正错误的框和文本,效率能提升好几倍。
但自动标注有个陷阱:预训练模型识别错的地方,如果你不仔细检查,错误就被带进训练集了。我踩过这个坑,训出来的模型在某些字符上一直错,后来发现是标注数据里就有错。所以自动标注之后,必须人工过一遍,尤其是那些模型置信度低的样本。
标注的时候有几个细节要注意:
- 检测框要贴紧文字边缘,不要留太多空白,也不要切掉笔画。框太松会让识别模型学到多余的背景信息,框太紧会丢失边缘特征。
- 对于倾斜文字,用四点标注而不是矩形框,PaddleOCR支持四点坐标,能更好地贴合倾斜文本。
- 文本内容要跟图片完全一致,包括标点符号。中文标点和英文标点要区分清楚,这个在训练时影响很大。
- 如果图片里有多个文本区域,每个区域单独标注,不要合并。
3.3 数据增强与合成数据策略
真实场景的数据往往不够,尤其是特定字体或特定版式的样本。这时候数据增强和合成数据就派上用场了。
PaddleOCR内置了丰富的数据增强策略,在配置文件里可以开。常用的有:
- 随机裁剪:模拟文字被部分遮挡的情况
- 颜色抖动:模拟不同光照和扫描质量
- 透视变换:模拟拍摄角度变化
- 模糊和噪声:模拟低质量扫描件
这些增强在configs/rec/下的配置文件里有对应参数,比如RecAug里的aug_type可以选ColorJitter、GaussianBlur等。我的经验是,增强不要开太猛,否则模型学到的都是变形严重的样本,反而影响正常图片的识别。一般开两到三种轻度增强就够了。
合成数据是另一个思路。如果你需要识别特定字体,可以用文字渲染工具批量生成图片。比如用PIL把文字渲染到不同背景上,加上随机噪声和模糊,就能造出大量训练样本。我做过一个实验:在只有500张真实样本的情况下,加入5000张合成样本,识别准确率从78%提升到了91%。合成数据的质量很关键,背景要多样、字体要一致、文字要清晰,否则就是噪声。
注意:合成数据不能完全替代真实数据。合成数据和真实数据分布有差异,如果全用合成数据训练,模型在真实场景上会表现很差。我的做法是真实数据占三分之一,合成数据占三分之二,混合训练。
3.4 数据集划分与格式转换
数据标好之后,要划分训练集、验证集和测试集。比例一般是7:2:1或者8:1:1。验证集用来监控训练过程,测试集用来最终评估,这两个不能混用。
PaddleOCR的识别训练需要两个文件:train_list.txt和val_list.txt,每行格式是图片路径\t标签。注意路径是相对于配置文件里data_dir的路径,不是绝对路径。这个很容易搞错,路径不对的话训练直接报错找不到文件。
如果标注格式跟PaddleOCR要求的不一致,需要写个转换脚本。比如从Labelme的JSON转成PaddleOCR格式,或者从联合标注拆成检测和识别两个文件。这种脚本不复杂,但要注意编码问题——中文标注一定要用UTF-8,不然会出现乱码。
# 简单的格式转换示例:从"路径 文本"转成"路径\t文本" with open('raw_label.txt', 'r', encoding='utf-8') as f: lines = f.readlines() with open('train_list.txt', 'w', encoding='utf-8') as f: for line in lines: parts = line.strip().split(' ', 1) if len(parts) == 2: f.write(f"{parts[0]}\t{parts[1]}\n")这个脚本看起来简单,但实际用的时候要注意:有些标注文件里路径和文本之间是空格分隔,但文本本身也可能包含空格,所以要用split(' ', 1)只分割一次。这种细节不注意,转换出来的标签就是错的。
4. 模型训练全流程拆解
4.1 配置文件详解与关键参数
PaddleOCR的训练是通过配置文件驱动的,识别模型的配置文件在configs/rec/下面。我一般选一个跟需求接近的预训练配置作为基础,比如ch_PP-OCRv4_rec.yml,然后复制一份改成自己的。
配置文件里几个关键部分:
Global部分:设置use_gpu、epoch_num、save_model_dir、pretrained_model等。pretrained_model指向预训练模型路径,这个很重要,用预训练权重初始化能大幅加快收敛。epoch_num根据数据量来定,一般100到500之间,数据少就多训几轮,数据多就少训几轮。
Architecture部分:定义模型结构,包括backbone、neck、head。PaddleOCR支持SVTR、CRNN等多种结构。如果追求精度,选SVTR-LCNet;如果追求速度,选MobileNetV3作为backbone。我一般先用默认结构跑通,再根据效果调整。
Train和Eval部分:设置数据路径、batch size、学习率等。data_dir是数据集根目录,label_file_list是标注文件路径。batch_size根据显存来,8GB显存建议设64到128,12GB可以设256。学习率跟batch size相关,一般batch size翻倍,学习率也翻倍。
Optimizer和Lr部分:优化器一般用Adam,学习率调度用Cosine或者Piecewise。学习率太大loss会震荡,太小收敛慢。我的经验是从0.001开始试,如果loss下降很慢就调大,如果震荡就调小。
PostProcess部分:设置字符字典路径。这个字典必须跟你的标注文本字符集一致,否则会出现识别乱码。字典文件在ppocr/utils/下面,中文一般用ppocr_keys_v1.txt。如果你的数据里有特殊字符,需要自己扩充字典。
4.2 启动训练与日志监控
配置改好之后,用tools/train.py启动训练:
python tools/train.py -c configs/rec/my_rec_config.yml训练启动后,控制台会输出loss、acc、学习率等信息。我一般会同时开一个终端用tail -f看日志文件,日志文件在save_model_dir下面的train.log。
监控训练过程主要看几个指标:
- loss:训练loss应该稳步下降,如果一直不降或者震荡,说明学习率或数据有问题
- acc:训练准确率应该逐步上升,验证准确率跟训练准确率的差距不能太大,差距大说明过拟合
- 学习率:确认学习率调度按预期变化,如果一直不变可能是配置没生效
训练过程中会定期保存模型,一般按epoch保存。如果中途中断了,可以从最近的checkpoint恢复:
python tools/train.py -c configs/rec/my_rec_config.yml -o Global.checkpoints=output/rec/epoch_50这里有个实用技巧:训练初期可以先冻结backbone,只训练head部分,等loss降下来再解冻全部参数微调。这样能防止预训练权重被早期的大梯度破坏,收敛更稳定。PaddleOCR的配置文件里可以通过freeze_norm和optimizer的设置来实现类似效果。
4.3 训练中断与恢复的实操细节
训练大模型动辄十几个小时,中途中断是常有的事。PaddleOCR支持从checkpoint恢复,但有几个细节要注意。
首先,恢复训练时配置文件里的epoch_num要设成总轮数,而不是剩余轮数。比如你计划训200轮,在第100轮中断了,恢复时epoch_num还是200,PaddleOCR会自动从第100轮继续。
其次,优化器状态和学习率调度状态也会被恢复,所以学习率会接着之前的走,不会重新开始。这个设计很合理,但如果你换了batch size或者学习率,恢复后可能会有异常,最好重新开始训。
另外,如果训练过程中改了配置文件(比如改了数据增强参数),恢复训练时这些改动会生效,但可能会导致loss突然跳变。我的建议是,训练中途尽量不改配置,要改就重新开始。
提示:训练时建议开
use_amp(自动混合精度),能省显存还能加速,对精度影响很小。但有些老显卡不支持,开了会报错,这个要试一下。
4.4 模型评估与指标解读
训练完成后,用tools/eval.py评估模型:
python tools/eval.py -c configs/rec/my_rec_config.yml -o Global.checkpoints=output/rec/best_accuracy评估会输出准确率(acc)和编辑距离(edit distance)等指标。准确率是整行文本完全正确的比例,编辑距离是预测文本和真实文本之间的字符差异。实际业务里,编辑距离往往比准确率更有参考价值,因为有些场景允许少量字符错误。
如果准确率不理想,先别急着调模型,回头检查数据:
- 标注是否有错?随机抽100张图人工核对一遍
- 训练集和验证集分布是否一致?如果验证集跟训练集差异大,准确率低是正常的
- 字符字典是否覆盖了所有字符?有未登录字符的话,识别结果会是乱码
我遇到过一次准确率死活上不去的情况,排查了半天发现是标注文件里有些文本带了不可见字符(比如零宽空格),导致模型学到的标签是错的。这种问题很隐蔽,用文本编辑器看不出来,要用十六进制工具才能发现。
5. 常见问题与排查技巧实录
5.1 训练报错速查表
| 报错信息 | 可能原因 | 解决方法 |
|---|---|---|
FileNotFoundError: train_list.txt | 路径配置错误 | 检查data_dir和label_file_list,确认路径拼接正确 |
KeyError: 'transcription' | 标注格式不匹配 | 确认标注文件格式跟配置文件里的dataset类型一致 |
CUDA out of memory | batch size太大 | 减小batch size,或开use_amp |
loss is nan | 学习率太大或数据有脏样本 | 降低学习率,检查标注数据 |
| 识别结果全是乱码 | 字典不匹配 | 确认字典文件跟训练数据字符集一致 |
| 验证准确率不上升 | 过拟合或数据问题 | 增加数据增强,检查验证集分布 |
5.2 识别乱码的排查思路
识别乱码是OCR训练里最常见的问题,原因可能有几种。
第一种是字典不匹配。训练时用的字典跟推理时用的字典不一致,模型输出的索引对应不上字符。这个最好排查,确认两边字典文件路径一致就行。
第二种是标注文本包含字典外的字符。比如字典里没有某个生僻字,但标注里有,训练时这个字符会被忽略或映射到错误索引。解决方法是扩充字典,把数据集中所有字符都加进去。
第三种是模型没训好。loss还没收敛就拿来推理,输出自然是乱的。这种情况看训练日志就能判断,loss还在高位震荡就说明没训好。
第四种是图片预处理不一致。训练时的归一化参数跟推理时不一致,导致输入分布不同。PaddleOCR的配置里image_shape和mean、std要跟推理时保持一致。
5.3 提升识别率的三条实战经验
第一条:数据质量比数据量重要。我试过用1万张标注质量一般的图片训练,效果不如3000张精标图片。标注框要准、文本要对、字符要清晰,这三条做到了,模型效果不会差。
第二条:难例挖掘很关键。训练完一轮后,用模型跑一遍训练集,把识别错误的样本挑出来,重点检查这些样本的标注是否有问题,或者把这些难例加入下一轮训练。这个迭代过程能持续提升模型效果。
第三条:后处理能救回不少错误。PaddleOCR支持自定义后处理,比如基于词典的纠错、正则匹配等。对于固定格式的文本(如身份证号、日期),后处理能纠正模型的小错误。我做过一个票据识别项目,加了日期格式后处理之后,日期字段的准确率从92%提升到了99%。
5.4 模型导出与部署注意事项
训练好的模型要导出成推理格式才能部署:
python tools/export_model.py -c configs/rec/my_rec_config.yml -o Global.checkpoints=output/rec/best_accuracy Global.save_inference_dir=./inference/rec导出后会生成inference.pdmodel和inference.pdiparams两个文件,加上字典文件,就是完整的推理模型。
部署时有几个点要注意:推理时的预处理必须跟训练时一致,包括resize尺寸、归一化参数、通道顺序。另外,如果部署环境没有GPU,导出时要确认模型不依赖GPU算子。PaddleOCR支持导出为ONNX格式,可以跨平台部署,但转换过程中有些自定义算子可能不支持,需要测试确认。
注意:导出模型后,一定要用测试集重新评估一遍,确认导出前后的精度一致。我遇到过导出后精度下降的情况,原因是导出时某些配置没带过去,排查了很久。
6. 从训练到落地的完整闭环
整套流程走下来,我最深的体会是:OCR训练不是一个“跑通脚本就完事”的事情,它是个数据、模型、后处理不断迭代的过程。第一版模型出来之后,别指望它直接能用,拿真实场景的图片去测,把错的挑出来,分析是数据问题还是模型问题,然后针对性优化。
数据方面,标注质量是根基,合成数据是补充,难例挖掘是提升手段。模型方面,预训练权重能省很多时间,配置文件里的参数要根据数据量调整,不要照搬默认值。后处理方面,针对业务场景加规则,往往比调模型更立竿见影。
还有一点,PaddleOCR的版本更新比较快,不同版本之间配置文件和API可能有变化。我建议锁定一个稳定版本,把依赖版本都固定下来,避免环境变动导致训练结果不可复现。如果非要升级,先在测试环境验证一遍再上生产。
最后分享一个我常用的调试技巧:先用极小的数据集(比如50张图)跑通全流程,确认数据格式、配置文件、训练脚本都没问题,再换全量数据正式训练。这样能快速定位问题,不用等几个小时才发现配置错了。这个习惯帮我省了大量时间,也推荐给你。