★★★ 本文源自AlStudio社区精品项目,【点击此处】查看更多精品内容 >>>

百度网盘AI大赛——表格检测进阶

比赛介绍

随着票据、名单等带有表单、表格的文件被广泛应用,将纸质文件转化成电子数据并保存管理成为了很多企业的必然工作。传统人工录入的方式效率低、差错多、流程长,如果能通过技术处理,实现表格图片的结构化展现,则可以很大程度降低成本,提高效率以及使用体验。本次比赛希望各位选手能通过OCR等技术解决此痛点问题,识别表格图片的内容与坐标,精准还原纸质数据。

数据集介绍

本次比赛最新发布的数据集共包含训练集、A榜测试集、B榜测试集三个部分,其中训练集共7742张图片,A榜测试集共500张图片,B榜测试集500张图片;
本次比赛共4个类别,分别为整体表格(table)、表格行(row)、表格列(column)、跨多行/列的合并单元格(spanning_cell),annos.txt 为标注文件,json格式。

模型构建思路及调优过程

数据分析

合成样本

真实样本

数据集合成样本与真实样本比例为:
7000:742

结论:整个数据集中,难样本集中在真实样本,预测过程中该类样本预测最难。

数据增强

RandomDistort

RandomExpand

RandomCrop

RandomFlip

新增一个GaussianBlur,使得合成的表格具有和真实表格相似清晰度。

模型构建

尝试实例分割模型Mask RCNN系列:

优点:模型精度高

缺点:模型推理耗时长

结论:不予选取

尝试目标检测模型:

baseline:PP-PicoDet

A榜模型得分最高0.7左右,推理平均每张图片耗时0.04s。

两阶段目标检测模型结合VITbackbone:Cascade_RCNN_ViT-base

优点:模型精度高,VIT模块泛化能力强,A榜模型得分最高0.86

缺点:训练占用显存过大导致batchsize过小,收敛缓慢,且推理平均每张图片耗时0.12s。

单阶段目标检测PP-YOLOE+模型

①更换backbone对比

将CSPRepResNet更换为convnext,性能反而下降

分析:CSPRepResNet的预训练模型与整个框架的拟合较好

②修改BatchRandomResize输入尺寸

修改target_size: [320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768]为:

target_size: [320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768,800,832,864,896,928]

③修改eval_height,eval_width输入尺寸

修改eval_height,eval_width:640,640为:

eval_height,eval_width:768,768

模型调优、参数设置

使用飞桨 AI studio训练,两张V100,32G显存。

optimizer_400e的base_lr为0.01,base_bacthsize为64。

模型训练80epoch,300epoch,400epoch的模型A榜得分分别为:0.877,0.888,0.893。

分析:说明模型训练精度未到瓶颈,继续增加训练轮数可获得一定分数提高,但同时按照规律,optimizer的base_Lr也要随之增大。

训练的batchsize为8,lr同比缩小应为0.00125。

实验得出,batchsize为8,lr为0.01效果较好。

分析:因为使用迁移学习进行训练,学习率变化使用LinearWarmup,因此更大的学习率更易跳出鞍点。

B榜分数,推理时间

解压数据

! unzip -oq /home/aistudio/data/data182509/train.zip
! unzip -oq /home/aistudio/data/data182509/testA.zip
! cp /home/aistudio/data/data195867/model_final.pdparams -d /home/aistudio

环境准备

#克隆PaddleDetection
!git clone https://github.com/PaddlePaddle/PaddleDetection.git
#! wget https://github.com/PaddlePaddle/PaddleDetection/archive/refs/tags/v2.5.0.zip

# 安装其他依赖
# %cd PaddleDetection
! pip install --user -r requirements.txt

# 编译安装paddledet
! python setup.py install

数据准备

# 转voc
%cd ~
! mkdir -p train/annotations

# thanks to https://blog.csdn.net/hu694028833/article/details/81089959
import xml.dom.minidom as minidom
import cv2
import json

with open('train/annos.txt', 'r') as f:
    data = json.load(f)

img_root = 'train/imgs/'
annotation_dir = 'train/annotations/'
with open('train.txt','w') as fw:
    for name in data.keys():
        img_dir = img_root+name
        save_dir = annotation_dir+name.split('.')[0]+'.xml'
        try:
            # generate xml
            img = cv2.imread(img_dir)
            h,w,c = img.shape

            dom = minidom.getDOMImplementation().createDocument(None,'annotations',None)
            root = dom.documentElement

            element = dom.createElement('filename')
            element.appendChild(dom.createTextNode(img_dir.split('/')[-1]))
            root.appendChild(element)

            element = dom.createElement('size')
            element_c = dom.createElement('width')
            element_c.appendChild(dom.createTextNode(str(w)))
            element.appendChild(element_c)
            element_c = dom.createElement('height')
            element_c.appendChild(dom.createTextNode(str(h)))
            element.appendChild(element_c)
            element_c = dom.createElement('depth')
            element_c.appendChild(dom.createTextNode(str(c)))
            element.appendChild(element_c)
            root.appendChild(element)

            objects = data[name]
            for a_object in objects:
                element = dom.createElement('object')

                element_c = dom.createElement('name')
                element_c.appendChild(dom.createTextNode(a_object['label']))
                element.appendChild(element_c)

                element_c = dom.createElement('bndbox')
                
                element.appendChild(element_c)
                element_cc = dom.createElement('xmin')
                element_cc.appendChild(dom.createTextNode(str(a_object['box'][0])))
                element_c.appendChild(element_cc)
                element_cc = dom.createElement('ymin')
                element_cc.appendChild(dom.createTextNode(str(a_object['box'][1])))
                element_c.appendChild(element_cc)
                element_cc = dom.createElement('xmax')
                element_cc.appendChild(dom.createTextNode(str(a_object['box'][2])))
                element_c.appendChild(element_cc)
                element_cc = dom.createElement('ymax')
                element_cc.appendChild(dom.createTextNode(str(a_object['box'][3])))

                element_c.appendChild(element_cc)

                root.appendChild(element)
            with open(save_dir, 'w', encoding='utf-8') as f:
                dom.writexml(f, addindent='\t', newl='\n',encoding='utf-8')
        except:
            print(name)
            continue
        fw.write(img_dir+' '+save_dir+'\n')
# 准备标签文件
%cd ~
with open('label_list.txt','w') as f:
    for item in ['table','row','column','spanning_cell']:
        f.write(item+'\n')

准备yml文件

在PaddleDetection/configs/ppyoloe/目录下准备一个名为ppyoloe_plus_crn_x_400e_coco.yml的文件,内容为:

_BASE_: [
  '../datasets/voc.yml',
  '../runtime.yml',
  './_base_/optimizer_400e.yml',
  './_base_/ppyoloe_plus_crn.yml',
  './_base_/ppyoloe_plus_reader.yml',
]

log_iter: 300
snapshot_epoch: 10
weights: output/ppyoloe_plus_crn_x_400e_coco/model_final

pretrain_weights: https://bj.bcebos.com/v1/paddledet/models/pretrained/ppyoloe_crn_x_obj365_pretrained.pdparams
depth_mult: 1.33
width_mult: 1.25

将PaddleDetection/configs/ppyoloe/base/ppyoloe_plus_reader.yml修改为如下内容

worker_num: 2
eval_height: &eval_height 768
eval_width: &eval_width 768
eval_size: &eval_size [*eval_height, *eval_width]

TrainReader:
  sample_transforms:
    - Decode: {}
    - RandomDistort: {}
    - RandomExpand: {fill_value: [123.675, 116.28, 103.53]}
    - RandomCrop: {}
    - RandomFlip: {prob: 0.5}
    - Blur: {prob: 0.1}
  batch_transforms:
    - BatchRandomResize: {target_size:[320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928], random_size: True, random_interp: True, keep_ratio: False}
    - NormalizeImage: {mean: [0., 0., 0.], std: [1., 1., 1.], norm_type: none}
    - Permute: {}
    - PadGT: {}
  batch_size: 8
  shuffle: true
  drop_last: true
  use_shared_memory: true
  collate_batch: true

EvalReader:
  sample_transforms:
    - Decode: {}
    - Resize: {target_size: *eval_size, keep_ratio: False, interp: 2}
    - NormalizeImage: {mean: [0., 0., 0.], std: [1., 1., 1.], norm_type: none}
    - Permute: {}
  batch_size: 2

TestReader:
  inputs_def:
    image_shape: [3, *eval_height, *eval_width]
  sample_transforms:
    - Decode: {}
    - Resize: {target_size: *eval_size, keep_ratio: False, interp: 2}
    - NormalizeImage: {mean: [0., 0., 0.], std: [1., 1., 1.], norm_type: none}
    - Permute: {}
  batch_size: 1

将PaddleDetection/configs/datasets/voc.yml修改为如下内容

metric: VOC
map_type: 11point
num_classes: 4

TrainDataset:
  !VOCDataSet
    dataset_dir: /home/aistudio
    anno_path: /home/aistudio/train.txt
    label_list: /home/aistudio/label_list.txt
    data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']

EvalDataset:
  !VOCDataSet
    dataset_dir: /home/aistudio
    anno_path: /home/aistudio/train.txt
    label_list: /home/aistudio/label_list.txt
    data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']

TestDataset:
  !ImageFolder
    anno_path: /home/aistudio/label_list.txt

在PaddleDetection/ppdet/data/transform/operators.py中添加高斯模糊数据增强操作,运行下列代码进行更新

!cp /home/aistudio/operators.py -d /home/aistudio/PaddleDetection/ppdet/data/transform/operators.py

模型训练与导出

%cd /home/aistudio/PaddleDetection
#bacthsize:8,base_lr:0.01,epoch:400
!CUDA_VISIBLE_DEVICES=0 python -m paddle.distributed.launch --gpus 0 tools/train.py -c configs/ppyoloe/ppyoloe_plus_crn_x_400e_coco.yml
#!python tools/export_model.py -c configs/ppyoloe/ppyoloe_plus_crn_x_400e_coco.yml -o weights=output/ppyoloe_plus_crn_x_400e_coco/model_final.pdparams
#下面使用checkpoint进行模型导出,若按照上述步骤训练完成,可使用上面命令行进行模型导出
!python tools/export_model.py -c configs/ppyoloe/ppyoloe_plus_crn_x_400e_coco.yml -o weights=/home/aistudio/model_final.pdparams

推理验证

!cp -r /home/aistudio/PaddleDetection/output_inference/ppyoloe_plus_crn_x_400e_coco -d /home/aistudio/predict/

预测文件中thre为0.7,thre从[0.5,0.7],隔0.05取值,实验得出,thre为0.7最好。

分析:因训练过程中简单合成样本占绝大多数,因此网络置信度整体偏高,牺牲部分召回率可极大提高准确度,因此在分数表现上更好。

可视化预测结果

修改/home/aistudio/predict/utils中–save_images为True,再运行下方程序,保存到res

%cd /home/aistudio/predict/
!python predict.py /home/aistudio/pubtest/imgs /home/aistudio/res

打包可提交代码

%cd /home/aistudio
!zip -r predict.zip predict

参考项目

百度网盘AI大赛——表格检测进阶:表格的结构化 Baseline

https://aistudio.baidu.com/aistudio/projectdetail/5234267

总结

综合而看:取得了推理时间较短,分数较高的成绩,同时也说明PP-YOLOE+性能优越,可以作为后续表格结构化检测持续优化的基础模型。
后续优化方向:

1.可以通过生成一些对抗样本来模型优化

2.通过形态学的处理增强表格结构,如锐化,对比度等

3.通过马赛克和遮挡进行数据增强

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐