ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零训练PaddleOCR模型:数据标注、调参与部署全流程实战

从零训练PaddleOCR模型:数据标注、调参与部署全流程实战 OCR这玩意儿说起来简单——不就是把图片里的字认出来嘛。但真到自己动手训练一个能打的模型尤其是面对那些歪歪扭扭的票据、模糊的扫描件、手写体混排的文档时你会发现通用模型根本不够看。我最早接触PaddleOCR是在一个发票识别项目里当时用官方预训练模型跑了一遍印刷体还行一遇到稍微倾斜或者字体特殊的场景识别率直接掉到七成以下。后来硬着头皮自己标注数据、微调模型踩了无数坑才把准确率拉到95%以上。这篇文章就是把我从零训练PaddleOCR模型的完整过程拆开来讲包括环境怎么搭、数据怎么标、配置文件怎么改、训练中遇到loss不降怎么办、以及那些官方文档里不会写的实操细节。不管你是刚接触OCR的新手还是已经用过PaddleOCR推理但没训练过自己模型的老手下面这些内容应该都能帮你省下不少折腾的时间。1. 动手之前先想清楚你到底需不需要自己训练很多人一上来就问“怎么训练PaddleOCR”但我觉得更有价值的问题是“我到底该不该自己训练”。这个问题想不清楚后面全是白费功夫。1.1 通用模型能覆盖的场景比你想的多PaddleOCR官方提供的预训练模型在印刷体、标准字体、清晰扫描件这些场景下识别准确率已经相当高了。我实测过PP-OCRv4的中文模型在300dpi扫描的合同文档上不加任何微调就能达到93%到96%的字符准确率。如果你要处理的是这类场景直接拿官方模型推理就行没必要折腾训练。那什么情况下通用模型会翻车我总结了几类第一是特殊字体比如某些票据上用的针式打印字体笔画断裂严重第二是背景干扰大的场景比如带水印、带底纹的图片第三是手写体尤其是连笔字第四是垂直排列或者弯曲排列的文字第五是专业领域的生僻字通用字典里压根没有。这几种情况你不训练自己的模型光靠调推理参数是解决不了的。1.2 训练之前先做一轮“极限测试”我的习惯是在决定训练之前先拿一批真实场景的测试图片跑一遍官方模型看看badcase到底长什么样。具体做法很简单准备200到500张你实际业务中会遇到的图片用官方模型推理一遍然后把识别错误的样本挑出来分类。分类的维度包括是检测框没框准还是识别内容错了如果是识别错了是字形相似导致的混淆还是压根不认识这个字把这些搞清楚之后你才能判断问题出在检测模型还是识别模型上。PaddleOCR的检测和识别是两个独立模块训练的时候也是分开训练的你得先定位到底是哪个环节拖了后腿。这一步很多人跳过直接就开始标数据训练结果训练完了发现检测框还是歪的识别模型再好也没用。先诊断再开药这个顺序不能反。1.3 数据量不够怎么办合成数据是一条路自己标数据是个体力活尤其是OCR这种需要标文字内容的任务。如果你手头真实数据不够可以考虑用合成数据来补充。PaddleOCR生态里有个工具叫Style-Text可以把文字渲染到各种背景上生成带标注的合成图片。我试过用它在几百张背景图上生成了上万张合成样本配合少量真实数据一起训练效果比纯用真实数据好不少。但合成数据不能完全替代真实数据。合成图片的文字边缘太干净和真实拍摄的图片在噪声分布上有差异。我的经验是合成数据和真实数据的比例控制在3:1到5:1之间比较合适而且真实数据要覆盖所有你关心的场景类型。2. 训练环境的搭建那些让你卡住半天的细节环境搭建看起来是最没技术含量的环节但实际上我见过太多人卡在这一步。PaddleOCR依赖PaddlePaddle框架而PaddlePaddle又对CUDA版本、cuDNN版本有要求版本对不上就是各种报错。2.1 版本匹配是最大的坑先说你最可能踩的坑PaddlePaddle GPU版本和CUDA版本的对应关系。截至我写这篇文章的时候PaddlePaddle 2.6稳定版支持CUDA 11.2到11.8对应的cuDNN版本是8.2以上。如果你机器上装的是CUDA 12.x那要么降级CUDA要么用PaddlePaddle的develop版本。我的建议是直接用conda创建一个独立环境把CUDA和cuDNN都装在环境里不要依赖系统级的CUDA。这样不同项目之间不会互相干扰。具体命令大概是这样conda create -n paddle_ocr python3.10 conda activate paddle_ocr conda install cudatoolkit11.8 cudnn8.2 -c conda-forge然后安装PaddlePaddle GPU版python -m pip install paddlepaddle-gpu2.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后一定要验证GPU是否可用import paddle print(paddle.device.get_device()) print(paddle.utils.run_check())如果输出里有“gpu”字样并且run_check没有报错那环境基本就OK了。如果run_check报错说找不到cudnn大概率是cuDNN版本不对或者环境变量没配好。2.2 PaddleOCR源码的安装方式选择PaddleOCR有两种安装方式pip直接装和源码编译安装。如果你只是做推理pip装就够了。但你要训练自己的模型我强烈建议从源码安装因为训练脚本、配置文件都在源码目录里pip安装的包是不包含这些的。git clone https://github.com/PaddlePaddle/PaddleOCR.git cd PaddleOCR pip install -r requirements.txt pip install -e .这里有个细节requirements.txt里的依赖版本可能和你环境里已有的包冲突尤其是opencv-python和numpy。我的做法是先装PaddlePaddle再装PaddleOCR的依赖遇到冲突的时候优先保证PaddlePaddle能正常跑。2.3 训练目录的结构规划在开始训练之前把目录结构规划好后面会省很多事。我通常是这样组织的my_ocr_project/ ├── data/ │ ├── train_images/ # 训练图片 │ ├── train_labels/ # 训练标注 │ ├── val_images/ # 验证图片 │ └── val_labels/ # 验证标注 ├── pretrain_models/ # 预训练模型 ├── output/ # 训练输出 └── configs/ # 自定义配置文件标注文件我习惯用PaddleOCR推荐的格式每行一个图片路径加一个JSONJSON里包含transcription字段。比如train_images/img_001.jpg {transcription: 发票号码 12345678} train_images/img_002.jpg {transcription: 合计金额 ¥520.00}注意图片路径和JSON之间用制表符分隔不是空格。这个细节很容易忽略但格式不对训练脚本直接报错。3. 数据标注决定模型上限的关键环节模型的上限是数据决定的标注质量差再好的网络结构也白搭。OCR标注有两个层面检测框的标注和识别文本的标注。3.1 检测标注框要贴多紧才算合适PaddleOCR的检测模型用的是DB算法训练时需要提供文字区域的四边形框标注。标注工具我推荐PPOCRLabel这是PaddleOCR官方出的半自动标注工具内置了预训练模型可以自动检测文字区域你只需要修正和补充。框的精度直接影响检测效果。我的经验是框要贴着文字的外边缘但不要切掉笔画。对于倾斜的文字用四边形框而不是矩形框。框太松会导致检测区域包含过多背景框太紧会切掉部分笔画两种情况都会降低识别率。还有一个容易忽略的点对于长文本行不要拆成多个框。DB算法本身能处理长文本拆框反而会让识别模型丢失上下文信息。但如果一行文字中间有大的空白间隔比如表格里的两列文字那就应该分开标注。3.2 识别标注文本内容怎么标才规范识别标注就是给每个检测框对应的文字内容。这里有几个坑第一空格的处理。中文文本一般不加空格但英文单词之间要加空格。如果你的数据中英文混排要统一规则。我通常的做法是中文之间不加空格中英文之间加一个空格英文单词之间加空格。第二特殊符号的转义。标注文件是JSON格式如果文本里包含双引号、反斜杠这些字符需要转义。我遇到过标注文本里有“¥”符号导致JSON解析失败的后来统一用Unicode编码处理。第三生僻字的处理。如果你的业务涉及生僻字需要在字典文件里加上这些字。PaddleOCR的字典文件在ppocr/utils/目录下中文默认字典是ppocr_keys_v1.txt。你可以在这个文件里追加自己的字符但要注意字典的顺序会影响模型的输出索引如果修改了字典预训练模型的识别头就需要重新训练。3.3 标注数据的质量检查标完数据不要直接拿去训练先做一轮质量检查。我通常写个脚本做几件事检查图片路径是否存在、检查JSON格式是否合法、检查标注文本是否为空、统计标注文本的长度分布。长度分布这个特别重要。如果你的标注文本长度集中在5到10个字但测试场景里有大量20字以上的长文本那训练出来的模型在长文本上表现肯定差。这时候要么补充长文本的标注数据要么在训练配置里调整识别模型的最大序列长度。import json import os def check_annotation(label_file, image_dir): errors [] text_lengths [] with open(label_file, r, encodingutf-8) as f: for line_num, line in enumerate(f, 1): parts line.strip().split(\t) if len(parts) ! 2: errors.append(fLine {line_num}: format error) continue img_path, json_str parts full_path os.path.join(image_dir, img_path) if not os.path.exists(full_path): errors.append(fLine {line_num}: image not found {img_path}) try: anno json.loads(json_str) text anno.get(transcription, ) if not text: errors.append(fLine {line_num}: empty transcription) text_lengths.append(len(text)) except json.JSONDecodeError: errors.append(fLine {line_num}: invalid json) print(fTotal errors: {len(errors)}) print(fText length: min{min(text_lengths)}, max{max(text_lengths)}, avg{sum(text_lengths)/len(text_lengths):.1f}) return errors这个脚本跑一遍基本能发现大部分标注问题。4. 检测模型训练从配置文件到loss曲线PaddleOCR的检测模型训练入口是tools/train.py通过配置文件来控制所有参数。配置文件在configs/det/目录下我一般复制一份官方的配置文件出来改而不是直接改原文件。4.1 配置文件里必须改的几个地方以DB检测模型为例配置文件里需要改的核心参数包括数据路径配置找到Train/Dataset和Eval/Dataset部分把data_dir改成你的数据目录label_file_list改成你的标注文件路径。预训练模型路径在Global部分pretrained_model可以指定官方预训练模型的路径。用预训练模型初始化能显著加快收敛速度尤其是你的数据量不大的时候。学习率和batch size这两个参数需要配合调整。官方配置里默认的learning_rate是0.001batch_size_per_card是8。如果你的GPU显存不够先把batch_size降下来然后等比例降低学习率。比如batch_size从8降到4学习率也从0.001降到0.0005。训练轮数epoch_num默认是500但实际训练中不一定需要这么多。我通常先设200轮观察loss曲线如果已经收敛了就提前停。4.2 启动训练与常见报错处理启动训练的命令python tools/train.py -c configs/det/my_det_config.yml如果要从预训练模型恢复训练python tools/train.py -c configs/det/my_det_config.yml -o Global.pretrained_model./pretrain_models/ch_PP-OCRv4_det_train/best_accuracy常见的报错和处理方式报错信息原因解决方案CUDA out of memory显存不足降低batch_size_per_cardKeyError: transcription标注JSON缺少字段检查标注文件格式AssertionError: image shape图片尺寸异常检查是否有损坏图片Loss is NaN学习率过大降低学习率或加warmup4.3 看loss曲线判断训练状态训练过程中PaddleOCR会在output目录下生成train.log里面记录了每一步的loss值。我习惯用脚本把loss曲线画出来看趋势。正常的loss曲线应该是先快速下降然后逐渐趋于平缓。如果loss一直在震荡不下降可能是学习率太大如果loss下降很慢可能是学习率太小或者数据有问题如果loss突然变成NaN那基本是学习率过大或者数据里有异常样本。检测模型的loss由几部分组成shrink_loss、threshold_loss、binary_loss。我一般关注总的loss但如果有某一项loss异常大也要单独看。比如threshold_loss一直很高说明模型在区分文字区域和背景区域上有困难可能需要增加负样本。5. 识别模型训练比检测更吃数据和调参识别模型是OCR的核心也是训练中最耗时的部分。PaddleOCR的识别模型支持多种网络结构常用的有CRNN、SVTR等。5.1 识别配置文件的关键参数识别模型的配置文件在configs/rec/目录下。几个关键参数backbone网络骨干CRNN默认用MobileNetV3SVTR用SVTRNet。如果追求精度可以用ResNet系列如果追求速度MobileNet系列更合适。head识别头CRNN用的是CTCHeadSVTR用的是CTCHead加NRTRHead的组合。CTC适合不定长文本识别NRTR适合规则文本。max_text_length最大文本长度默认是25。如果你的数据里有超过25个字符的文本需要调大这个值。但调大会增加显存占用和训练时间。character_dict_path字典文件路径。如果你有自定义字符需要指向你自己的字典文件。5.2 数据增强策略的选择识别模型对数据增强很敏感。PaddleOCR内置了几种增强方式RecAug、RecConAug、RecResizeImg等。我常用的组合是RecConAug随机拼接两张图片增加长文本样本RecAug包括颜色抖动、模糊、噪声等RecResizeImg统一图片高度宽度按比例缩放数据增强的强度要控制好。增强太弱模型泛化能力差增强太强训练loss降不下去。我的经验是先用默认增强跑一轮看看验证集准确率如果过拟合明显再加强增强。5.3 识别模型训练中的调参经验识别模型的训练比检测模型更依赖调参。以下是我踩过坑之后总结的几条经验学习率用warmup识别模型对初始学习率很敏感直接用大学习率容易导致loss震荡。PaddleOCR支持warmup在配置文件里设置warmup_epoch即可。我一般设5到10个epoch的warmup。梯度裁剪识别模型容易出现梯度爆炸尤其是用CTC损失的时候。在配置文件里开启grad_clip阈值设5到10之间。验证集评估频率默认是每个epoch评估一次但如果你的验证集很大评估会很耗时。可以改成每5个epoch评估一次训练过程中观察train loss就行。预训练模型的选择PaddleOCR提供了多种预训练模型中文场景用ch_PP-OCRv4_rec_train英文场景用en_PP-OCRv4_rec_train。如果用多语言模型初始化中文模型效果反而可能变差因为字符集不匹配。6. 模型评估与推理部署训练完之后怎么用训练完成之后output目录下会保存best_accuracy和latest两个模型。best_accuracy是验证集上表现最好的模型latest是最后一个epoch的模型。部署的时候用best_accuracy。6.1 评估指标怎么看PaddleOCR识别模型的评估指标主要是准确率accuracy但要注意这个准确率是序列级别的准确率也就是整个文本完全正确才算对。实际业务中我们可能更关心字符级别的准确率。如果序列准确率不高但字符准确率还行说明模型大部分字都认对了只是偶尔有一两个错字。检测模型的评估指标是Hmean综合了精确率和召回率。Hmean在0.8以上基本可用0.9以上算不错。6.2 推理部署的几种方式PaddleOCR支持多种推理部署方式Python推理直接用PaddleOCR的Python API适合快速验证和原型开发。from paddleocr import PaddleOCR ocr PaddleOCR(det_model_dir./output/det/best_accuracy, rec_model_dir./output/rec/best_accuracy, use_gpuTrue) result ocr.ocr(test.jpg, clsTrue) for line in result[0]: print(line[1][0])HubServing部署适合服务化部署支持HTTP接口调用。PaddleInference部署把模型转换成inference格式用PaddleInference引擎推理速度比Python API快不少。移动端部署PaddleOCR提供了Paddle-Lite的部署方案可以在手机端运行。6.3 推理速度优化如果推理速度不满足要求可以从几个方面优化第一用PaddleInference替代Python API第二开启TensorRT加速第三调整检测模型的输入尺寸小尺寸推理更快但可能漏检小文字第四识别模型用更轻量的backbone。我实测下来在同样的GPU上PaddleInference比Python API快大约30%到50%开启TensorRT之后还能再快20%左右。但TensorRT的转换过程比较折腾需要模型完全静态化动态shape支持有限。7. 那些官方文档不会告诉你的踩坑记录最后这部分我把我训练过程中遇到的最坑的几个问题整理出来希望能帮你少走弯路。7.1 训练loss正常但评估指标不涨这个问题我遇到过两次。第一次是因为验证集和训练集的数据分布差异太大训练集里都是清晰扫描件验证集里混了手机拍摄的图片。第二次是因为标注文件里有大量重复样本模型过拟合了。排查思路先看训练集和验证集的loss曲线如果训练loss持续下降但验证loss不降反升那就是过拟合如果两个loss都不降那就是数据或配置有问题。过拟合的话增加数据增强、加dropout、减少模型参数量都可以试试。7.2 识别结果出现大量重复字符这是CTC损失训练中常见的问题模型陷入了一种“重复输出同一个字符”的局部最优。解决方法有几个第一检查标注数据里是否有大量单字符样本如果有补充长文本样本第二调整CTC的blank标签权重第三用beam search解码替代greedy解码。我遇到这个问题的时候最后发现是标注数据里有一批图片的标注文本只有一个字符而且这批图片占了总数据的30%。补充了长文本数据之后问题自然就解决了。7.3 训练到一半突然报显存不足这个通常是因为某些批次的图片尺寸特别大。PaddleOCR默认会把图片resize到固定尺寸但如果原图的长宽比特别极端resize之后可能还是很大。解决方法是在配置文件里设置max_text_length和image_shape的限制或者在数据加载阶段过滤掉尺寸异常的图片。7.4 自定义字典后预训练模型加载失败如果你修改了字典文件预训练模型的识别头维度就和你的字典不匹配了加载的时候会报维度错误。这时候需要设置ignore_weights参数忽略识别头的权重只加载backbone的权重。在配置文件里这样写Global: pretrained_model: ./pretrain_models/ch_PP-OCRv4_rec_train/best_accuracy ignore_weights: [head]这样backbone用预训练权重初始化识别头随机初始化训练的时候识别头会从头学起。7.5 多卡训练的那些坑如果你有多张GPU可以用-o Global.use_gpuTrue Global.distributedTrue开启多卡训练。但多卡训练有几个坑第一batch_size是每张卡的batch_size总batch_size要乘以卡数学习率也要相应调整第二多卡训练时数据是分片加载的如果数据量太小每张卡分到的数据可能不够一个batch第三多卡训练的模型保存和单卡略有不同加载的时候要注意。我个人的建议是如果单卡能跑起来尽量先用单卡调好参数再上多卡加速。多卡训练虽然快但调试起来麻烦很多。7.6 关于训练时间的一个参考最后给一个训练时间的参考帮你做资源规划。以1万张训练图片为例检测模型在单张V100上训练200个epoch大约需要6到8小时识别模型大约需要10到12小时。如果用更小的backbone或者更少的epoch时间会相应减少。合成数据可以无限生成但真实数据的标注成本是固定的所以我的策略是用合成数据做预训练用真实数据做微调这样能在有限标注成本下达到最好的效果。训练OCR模型这件事说到底是个耐心活。数据标注要耐心调参要耐心等loss收敛也要耐心。但当你看到自己训练的模型在那些通用模型搞不定的场景下准确识别出文字的时候那种成就感是直接用现成模型体会不到的。上面这些内容都是我一步步踩过来的希望能帮你在这条路上走得顺一点。
返回列表