
百度网盘AI大赛-表格检测第1名方案
性能高效的ppyoloe+在表格结构化检测中的应用
★★★ 本文源自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.通过马赛克和遮挡进行数据增强
更多推荐
所有评论(0)