
PaddleOCR 中的 Enhanced CTC Loss面向文字识别准确率提升的三种 CTC 损失改进方案【免费下载链接】PaddleOCR飞桨多语言OCR工具包实用超轻量OCR系统支持80种语言识别提供数据标注与合成工具支持服务器、移动端、嵌入式及IoT设备端的训练与部署 Awesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80 languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices)项目地址: https://gitcode.com/paddlepaddle/PaddleOCR导读本文围绕 PaddleOCR 仓库中的 Enhanced CTC Loss 技术说明系统讲解在 CRNN 文字识别框架下以 CTCLoss 为基座融合难例挖掘、多任务学习与 Metric Learning 三种思想而设计的 Focal-CTC、A-CTC、C-CTC 三种损失函数以及它们的统一实现EnhancedCTCLoss。读完本文你将掌握每一种改进损失的理论动机、数学形式、源码实现细节与配置方法并能在自己的 OCR 识别任务中复现 PP-OCRv2 所使用的 C-CTC 方案包括 Center 初始化的完整实操流程。1. 背景CRNN 与 CTCLoss 的识别精度瓶颈在 OCR 识别任务中CRNNConvolutional Recurrent Neural Network是工业界使用最广泛的文字识别算法之一。其训练阶段使用 CTCLoss 计算网络损失推理阶段使用 CTC Decode 获得解码结果。CRNN 在实际业务中已被证明能取得很好的识别效果但用户对识别准确率的要求没有止境。如何在不改变网络结构的前提下从损失函数层面进一步压榨识别精度是本文探讨的核心问题。PaddleOCR 的探索路径是从三个不同角度切入角度对应改进核心思想来源难例挖掘Focal-CTC LossFocal Loss 的困难样本加权多任务学习A-CTC LossCTC ACEACE Loss 的序列聚合交叉熵辅助监督Metric LearningC-CTC LossCTC CenterCenter Loss 的类内聚拢、类间拉远三者合称Enhanced CTC Loss对应的统一实现位于 ppocr/losses/rec_enhanced_ctc_loss.py。2. Focal-CTC Loss难例挖掘驱动的 CTC 加权2.1 理论来源Focal LossFocal Loss 出自论文《Focal Loss for Dense Object Detection》最初用于解决 one-stage 目标检测中正负样本比例严重失衡的问题。其通过降低大量简单负样本在训练中的权重实现困难样本挖掘。损失函数形式如下$$ \begin{equation} L_{fl}\left{ \begin{array}{cl} -\alpha(1 - y^{})^{\gamma}logy^{} , y 1 \ -(1 - \alpha)y^{\gamma}log(1 - y^{}), y 0 \ \end{array} \right. \end{equation} $$其中 $y$ 是经过激活函数的输出取值在 0-1 之间。它在原始交叉熵损失基础上增加了调制系数 $(1-y)^{\gamma}$ 和平衡因子 $\alpha$当 $\gamma 0$ 时调制系数 $(1-y)^{\gamma}$ 赋予易分类样本损失更小的权重使网络更关注困难、错分的样本调整因子 $\gamma$ 控制简单样本权重降低的速率$\gamma0$ 时退化为交叉熵损失$\gamma$ 增大则调制影响增强实验发现 $\gamma2$ 为最优平衡因子 $\alpha$ 用于平衡正负样本本身的比例不均原论文中 $\alpha$ 取 0.25。2.2 从 Focal Loss 到 Focal-CTC Loss对于经典 CTC 算法假设某个特征序列 $(f_1, f_2, \dots, f_t)$ 经过 CTC 解码之后结果等于 label 的概率为 $y$则解码结果不为 label 的概率为 $(1-y)$。不难发现 CTCLoss 与 $y$ 存在如下关系$$ L_{CTC} -log(y^{}) $$结合 Focal Loss 的思想——困难样本给大权重、简单样本给小权重——PaddleOCR 提出了 Focal-CTC Loss$$ L_{Focal_CTC} \alpha * (1 - y^{})^{\gamma} * L_{CTC} $$实验中 $\gamma$ 取 2、$\alpha$ 取 1。注意此处 $\alpha$ 与 Focal Loss 原论文中的正负样本平衡因子不同在 OCR 的序列场景下直接取 1仅保留调制系数的难例挖掘作用。2.3 源码实现解析Focal-CTC 的实现位于 ppocr/losses/rec_ctc_loss.py核心逻辑如下class CTCLoss(nn.Layer): def __init__(self, use_focal_lossFalse, **kwargs): super(CTCLoss, self).__init__() self.loss_func nn.CTCLoss(blank0, reductionnone) self.use_focal_loss use_focal_loss def forward(self, predicts, batch): ... loss self.loss_func(predicts, labels, preds_lengths, label_lengths) if self.use_focal_loss: weight paddle.exp(-loss) # weight y weight paddle.subtract(paddle.to_tensor([1.0]), weight) # 1 - y weight paddle.square(weight) # (1 - y)^2 loss paddle.multiply(loss, weight) loss loss.mean() return {loss: loss}实现细节值得注意底层使用nn.CTCLoss(blank0, reductionnone)先计算逐样本的原始 CTC 损失不做 reduction便于后续逐样本加权由 $L_{CTC} -\log(y)$ 反推 $y e^{-L_{CTC}}$因此paddle.exp(-loss)即得到解码概率 $y$调制系数直接以平方形式实现对应 $\gamma2$乘回原始 loss 完成加权最终loss.mean()聚合为标量。由于简单样本的 $y$ 接近 1、$(1-y)^2$ 趋近 0其梯度贡献被大幅压低网络训练自然向错分样本倾斜。3. A-CTC LossCTC 为主、ACE 为辅的多任务监督3.1 理论来源ACE Loss 的优势与局限A-CTC Loss 是 CTC Loss ACE Loss 的简称。ACE LossAggregation Cross-Entropy for Sequence Recognition相比 CTCLoss 主要有两点优势支持 2-D 文本识别ACE Loss 能够处理 2-D 文本的识别问题而 CTCLoss 只能处理 1-D 文本复杂度更优ACE Loss 在时间复杂度和空间复杂度上优于 CTC Loss。前人总结的 OCR 识别算法优劣如下图所示然而在 PaddleOCR 的实践中发现虽然 ACE Loss 如上图所述可以处理 2D 预测、在内存占用及推理速度方面具备优势但单独使用 ACE Loss 时识别效果并不如 CTCLoss。为此PaddleOCR 将 CTCLoss 与 ACELoss 结合以 CTCLoss 为主、ACELoss 为辅作为辅助监督 loss。这一尝试效果显著在内部实验数据集上相比单独使用 CTCLoss识别准确率提升约 1%。3.2 损失定义与源码实现A-CTC Loss 定义如下$$ L_{A-CTC} L_{CTC} \lambda * L_{ACE} $$实验中 $\lambda 0.1$。ACE Loss 的实现位于 ppocr/losses/ace_loss.py参考自 LS-ACELoss核心是序列维度的聚合交叉熵class ACELoss(nn.Layer): def __init__(self, **kwargs): super().__init__() self.loss_func nn.CrossEntropyLoss( weightNone, ignore_index0, reductionnone, soft_labelTrue, axis-1 ) def __call__(self, predicts, batch): ... B, N predicts.shape[:2] div paddle.to_tensor([N]).astype(float32) predicts nn.functional.softmax(predicts, axis-1) aggregation_preds paddle.sum(predicts, axis1) # 沿时间步聚合 aggregation_preds paddle.divide(aggregation_preds, div) length batch[2].astype(float32) batch batch[3].astype(float32) # label_ace batch[:, 0] paddle.subtract(div, length) batch paddle.divide(batch, div) loss self.loss_func(aggregation_preds, batch) return {loss_ace: loss}从源码可以理解 ACE Loss 与 CTC 的本质差异CTC 需要在所有合法路径含 blank上做动态规划求和而 ACE 直接把各时间步的 softmax 概率沿序列维度求和、再归一化得到聚合预测与每个字符出现次数占比形式的软标签label_ace做交叉熵。这一聚合操作绕开了序列对齐问题因此天然支持 2D 预测且无需前向-后向算法复杂度更低。作为辅助监督时它迫使网络在每个时间步上的字符分布整体逼近真实字符频率分布与 CTC 的对齐监督形成互补。4. C-CTC LossMetric Learning 提升相似字符区分度4.1 动机中文 OCR 的相似字符难题C-CTC Loss 是 CTC Loss Center Loss 的简称。Center Loss 出自论文《A Discriminative Feature Learning Approach for Deep Face Recognition》最早用于人脸识别通过增大类间距离、减小类内距离来学习判别性特征是 Metric Learning 领域较早也较常用的算法。在中文 OCR 识别任务中通过对 badcase 的分析PaddleOCR 发现中文识别的一大难点是相似字符多、容易误识。由此自然想到借鉴 Metric Learning 的思想增大相似字符的类间距以提升识别准确率。但二者结合存在一个关键障碍Metric Learning 主要用于图像识别领域训练数据的标签是固定值而 OCR 本质上是序列识别任务特征与 label 之间没有显式的对齐关系。经过 Arcmargin、Cosmargin 等方法的尝试后PaddleOCR 最终发现Center Loss 有助于进一步提升识别准确率。4.2 损失定义与源码实现C-CTC Loss 定义如下$$ L_{C-CTC} L_{CTC} \lambda * L_{center} $$实验中设置 $\lambda 0.25$。Center Loss 的实现位于 ppocr/losses/center_loss.py核心逻辑是计算特征与对应类别 center 之间的 L2 距离class CenterLoss(nn.Layer): def __init__(self, num_classes6625, feat_dim96, center_file_pathNone): super().__init__() self.num_classes num_classes self.feat_dim feat_dim self.centers paddle.randn(shape[self.num_classes, self.feat_dim]).astype(float64) if center_file_path is not None: assert os.path.exists(center_file_path), ... with open(center_file_path, rb) as f: char_dict pickle.load(f) for key in char_dict.keys(): self.centers[key] paddle.to_tensor(char_dict[key]) def __call__(self, predicts, batch): assert isinstance(predicts, (list, tuple)) features, predicts predicts # 需要 Head 返回特征 feats_reshape paddle.reshape(features, [-1, features.shape[-1]]).astype(float64) label paddle.argmax(predicts, axis2) label paddle.reshape(label, [label.shape[0] * label.shape[1]]) ... # 计算 feats 与 centers 的 L2 距离按 label 生成 mask 后取均值 dist paddle.multiply(distmat, mask) loss paddle.sum(paddle.clip(dist, min1e-12, max1e12)) / batch_size return {loss_center: loss}从源码可以提炼出 C-CTC 的三个关键设计特征来源__call__要求predicts是(features, logits)的元组即 Head 必须以return_featsTrue的方式同时返回特征与分类 logits详见下文配置伪标签由于 OCR 序列无显式对齐代码用paddle.argmax(predicts, axis2)取每个时间步 logits 的 argmax 作为该特征对应的类别伪标签再计算该特征与对应 center 的 L2 距离作为损失从而在特征空间上把同类字符聚拢Center 可加载center_file_path支持从train_center.pkl加载预计算的初始 Center这是 C-CTC 训练效果的关键见 4.3。4.3 Center 初始化随机初始化不行的实证与实操值得一提的是在 C-CTC Loss 中随机初始化 Center 并不能带来明显提升。PaddleOCR 采用的 Center 初始化方法如下基于原始的 CTCLoss训练得到一个网络 $N$挑选出训练集中识别完全正确的部分组成集合 $G$将 $G$ 中的每个样本送入网络进行前向计算提取最后一个 FC 层的输入即 feature与其 argmax 结果即 index之间的对应关系将相同 index 的 feature 进行聚合、计算平均值得到各自字符的初始 center。对应的提取流程在 tools/export_center.py 中实现脚本会强制将 Head 配置为return_feats True加载预训练模型后基于训练数据tools/program.py 中的get_center逐 batch 前向、通过update_center累积同 index 特征的均值得到每个字符的 center 字典最终序列化到磁盘。以配置文件 configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml 为例Center 提取命令如下python tools/export_center.py -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml -o Global.pretrained_model./output/rec_mobile_pp-OCRv2/best_accuracy运行完成后会在 PaddleOCR 主目录下生成train_center.pkl该文件随后通过CenterLoss的center_file_path参数在训练时加载。5. 实验结论与 PP-OCRv2 的取舍针对上述三种方案PaddleOCR 基于内部数据集进行了训练与评测实验结果如下表所示algorithmFocal_CTCA_CTCC-CTCgain0.3%0.7%1.7%基于上述结论PP-OCRv2 最终采用了 C-CTC 策略。需要特别说明的是PP-OCRv2 处理的是 6625 个中文字符的识别任务字符集较大、形似字较多因此在该任务上 C-CTC 方案带来的提升较大如果换做其他 OCR 识别任务结论可能有所不同。Focal-CTC、A-CTC、C-CTC 以及组合方案 EnhancedCTC 都值得针对自身数据做实验验证通常会带来不同程度的提升。6. 实战在 PaddleOCR 中配置与训练 Enhanced CTC Loss6.1 统一实现EnhancedCTCLoss三种改进的统合实现在 ppocr/losses/rec_enhanced_ctc_loss.py 中EnhancedCTCLoss内部组合了三个子损失class EnhancedCTCLoss(nn.Layer): def __init__( self, use_focal_lossFalse, # 是否启用 Focal-CTC use_ace_lossFalse, # 是否启用 ACE 辅助损失 ace_loss_weight0.1, # A-CTC 中 L_ACE 的权重 λ use_center_lossFalse, # 是否启用 Center 辅助损失 center_loss_weight0.05, # C-CTC 中 L_center 的权重 λ num_classes6625, # 字符类别数 feat_dim96, # 特征维度 init_centerFalse, # 是否从文件初始化 Center center_file_pathNone, # train_center.pkl 路径 **kwargs, ): ... self.ctc_loss_func CTCLoss(use_focal_lossuse_focal_loss) if use_ace_loss: self.ace_loss_func ACELoss() self.ace_loss_weight ace_loss_weight if use_center_loss: self.center_loss_func CenterLoss( num_classesnum_classes, feat_dimfeat_dim, init_centerinit_center, center_file_pathcenter_file_path, ) self.center_loss_weight center_loss_weight def __call__(self, predicts, batch): loss self.ctc_loss_func(predicts, batch)[loss] if self.use_center_loss: center_loss self.center_loss_func(predicts, batch)[loss_center] * self.center_loss_weight loss loss center_loss if self.use_ace_loss: ace_loss self.ace_loss_func(predicts, batch)[loss_ace] * self.ace_loss_weight loss loss ace_loss return {enhanced_ctc_loss: loss}需要注意的是EnhancedCTCLoss中center_loss_weight的默认值为 0.05而 ppocr/losses/combined_loss.py 中CombinedLoss提供了另一种等价的组合方式将各子损失列入loss_config_list通过weight字段为每个子损失分配权重并求和——这是仓库中 configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml 实际采用的配置形态。6.2 配置示例PP-OCRv2 的 Enhanced CTC 训练配置仓库提供了现成的 C-CTC 训练配置 configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml其关键改动如下Architecture: model_type: rec algorithm: CRNN Backbone: name: MobileNetV1Enhance scale: 0.5 Neck: name: SequenceEncoder encoder_type: rnn hidden_size: 64 Head: name: CTCHead mid_channels: 96 fc_decay: 0.00002 return_feats: true # 关键Head 需返回特征供 CenterLoss 使用 Loss: name: CombinedLoss loss_config_list: - CTCLoss: use_focal_loss: false # Focal-CTC 开关置 true 即启用难例挖掘 weight: 1.0 # L_CTC 权重 - CenterLoss: weight: 0.05 # C-CTC 中 L_center 的权重 λ num_classes: 6625 # 字符类别数与 dict 一致 feat_dim: 96 # 特征维度与 CTCHead.mid_channels 对应 center_file_path: # 填入 train_center.pkl 路径以加载预计算 Center # you can also try to add ace loss on your own dataset # - ACELoss: # weight: 0.1 Train: dataset: name: SimpleDataSet ... transforms: - DecodeImage: img_mode: BGR channel_first: false - RecAug: - CTCLabelEncode: - RecResizeImg: image_shape: [3, 32, 320] - KeepKeys: keep_keys: - image - label - length - label_ace # 若启用 ACELoss训练时需保留该字段该配置的要点Head 必须设置return_feats: trueCenterLoss 需要(features, predicts)元组输入这正是export_center.py中强制注入的配置feat_dim与mid_channels保持一致Center 的维度必须与 CTCHead 输出的特征维度相同此处均为 96Center 加载将center_file_path指向 4.3 节生成的train_center.pkl即可用预计算 Center 初始化若留空Center 会以paddle.randn随机初始化通常提升有限启用 A-CTC取消ACELoss注释块并同步在KeepKeys.keep_keys中加入label_ace即可叠加 ACE 辅助损失启用 Focal-CTC将CTCLoss下的use_focal_loss置为true即可。完成 Center 提取与配置修改后即可按标准流程启动训练配置文件中的save_model_dir与基础版本区分开便于对比python tools/train.py -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml7. 小结与扩展建议Enhanced CTC Loss 是 PaddleOCR 在损失函数层面提升 CRNN 识别精度的系统性尝试三条路线各有侧重Focal-CTC通过 $(1-y)^\gamma$ 调制系数实现难例挖掘改动最小、即插即用适合作为基线增强A-CTC用 ACE 聚合交叉熵做辅助监督同时保留向 2D 文本识别扩展的可能性C-CTC通过 Center Loss 在特征空间拉近同类、推远异类在字符集大、形似字多的中文任务如 PP-OCRv2 的 6625 类识别上收益最大1.7%。从源码结构看三种方案最终统一收敛到 ppocr/losses/rec_enhanced_ctc_loss.py 与 configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml使用者可以自由组合或单独启用。不同数据集上的收益会因字符集规模、相似字符密度、样本分布而异建议在自己的数据上分别实验 Focal-CTC、A-CTC、C-CTC 及组合方案 EnhancedCTC选择最适合任务的配置。【免费下载链接】PaddleOCR飞桨多语言OCR工具包实用超轻量OCR系统支持80种语言识别提供数据标注与合成工具支持服务器、移动端、嵌入式及IoT设备端的训练与部署 Awesome multilingual OCR toolkits based on PaddlePaddle (practical ultra lightweight OCR system, support 80 languages recognition, provide data annotation and synthesis tools, support training and deployment among server, mobile, embedded and IoT devices)项目地址: https://gitcode.com/paddlepaddle/PaddleOCR创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考