This commit is contained in:
289
ppstructure/kie/README.md
Normal file
289
ppstructure/kie/README.md
Normal file
@@ -0,0 +1,289 @@
|
||||
English | [简体中文](README_ch.md)
|
||||
|
||||
# Key Information Extraction (KIE)
|
||||
|
||||
- [1. Introduction](#1-introduction)
|
||||
- [2. Performance](#2-performance)
|
||||
- [3. Visualization](#3-visualization)
|
||||
- [3.1 SER](#31-ser)
|
||||
- [3.2 RE](#32-re)
|
||||
- [4. Usage](#4-usage)
|
||||
- [4.1 Prepare for the environment](#41-prepare-for-the-environment)
|
||||
- [4.2 Quick start](#42-quick-start)
|
||||
- [4.3 More](#43-more)
|
||||
- [5. Reference](#5-reference)
|
||||
- [6. License](#6-license)
|
||||
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
Key information extraction (KIE) refers to extracting key information from text or images. As downstream task of OCR, the key information extraction task of document image has many practical application scenarios, such as form recognition, ticket information extraction, ID card information extraction, etc.
|
||||
|
||||
PP-Structure conducts research based on the LayoutXLM multi-modal, and proposes the VI-LayoutXLM, which gets rid of visual features when finetuning the downstream tasks. An textline sorting method is also utilized to fit in reading order. What's more, UDML knowledge distillation is used for higher accuracy. Finally, the accuracy and inference speed of VI-LayoutXLM surpass those of LayoutXLM.
|
||||
|
||||
The main features of the key information extraction module in PP-Structure are as follows.
|
||||
|
||||
|
||||
- Integrate multi-modal methods such as [LayoutXLM](https://arxiv.org/pdf/2104.08836.pdf), VI-LayoutXLM, and PP-OCR inference engine.
|
||||
- Supports Semantic Entity Recognition (SER) and Relation Extraction (RE) tasks based on multimodal methods. Based on the SER task, the text recognition and classification in the image can be completed; based on the RE task, the relationship extraction of the text content in the image can be completed, such as judging the problem pair (pair).
|
||||
- Supports custom training for SER tasks and RE tasks.
|
||||
- Supports end-to-end system prediction and evaluation of OCR+SER.
|
||||
- Supports end-to-end system prediction of OCR+SER+RE.
|
||||
- Support SER model export and inference using PaddleInference.
|
||||
|
||||
|
||||
## 2. Performance
|
||||
|
||||
We evaluate the methods on the Chinese dataset of [XFUND](https://github.com/doc-analysis/XFUND), and the performance is as follows
|
||||
|
||||
|Model | Backbone | Task | Config file | Hmean | Inference time (ms) | Download link|
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
|VI-LayoutXLM| VI-LayoutXLM-base | SER | [ser_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml)|**93.19%**| 15.49|[trained model](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar)|
|
||||
|LayoutXLM| LayoutXLM-base | SER | [ser_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml)|90.38%| 19.49 | [trained model](https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar)|
|
||||
|VI-LayoutXLM| VI-LayoutXLM-base | RE | [re_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml)|**83.92%**| 15.49|[trained model](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar)|
|
||||
|LayoutXLM| LayoutXLM-base | RE | [re_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml)|74.83%| 19.49|[trained model](https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar)|
|
||||
|
||||
|
||||
* Note:Inference environment:V100 GPU + cuda10.2 + cudnn8.1.1 + TensorRT 7.2.3.4,tested using fp16.
|
||||
|
||||
For more KIE models in PaddleOCR, please refer to [KIE model zoo](../../doc/doc_en/algorithm_overview_en.md).
|
||||
|
||||
|
||||
## 3. Visualization
|
||||
|
||||
There are two main solutions to the key information extraction task based on VI-LayoutXLM series model.
|
||||
|
||||
(1) Text detection + text recognition + semantic entity recognition (SER)
|
||||
|
||||
(2) Text detection + text recognition + semantic entity recognition (SER) + relationship extraction (RE)
|
||||
|
||||
|
||||
The following images are demo results of the SER and RE models. For more detailed introduction to the above solutions, please refer to [KIE Guide](./how_to_do_kie.md).
|
||||
|
||||
### 3.1 SER
|
||||
|
||||
Demo results for SER task are as follows.
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185539141-68e71c75-5cf7-4529-b2ca-219d29fa5f68.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185539517-ccf2372a-f026-4a7c-ad28-c741c770f60a.png" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185539735-37b5c2ef-629d-43fe-9abb-44bb717ef7ee.jpg" width="600">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
**Note:** test pictures are from [xfund dataset](https://github.com/doc-analysis/XFUND), [invoice dataset](https://aistudio.baidu.com/aistudio/datasetdetail/165561) and a composite ID card dataset.
|
||||
|
||||
|
||||
Boxes of different colors in the image represent different categories.
|
||||
|
||||
The invoice and application form images have three categories: `request`, `answer` and `header`. The `question` and `answer` can be used to extract the relationship.
|
||||
|
||||
For the ID card image, the model can directly identify the key information such as `name`, `gender`, `nationality`, so that the subsequent relationship extraction process is not required, and the key information extraction task can be completed using only one model.
|
||||
|
||||
### 3.2 RE
|
||||
|
||||
Demo results for RE task are as follows.
|
||||
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185540080-0431e006-9235-4b6d-b63d-0b3c6e1de48f.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185540291-f64e5daf-6d42-4e7c-bbbb-471e3fac4fcc.png" width="600">
|
||||
</div>
|
||||
|
||||
Red boxes are questions, blue boxes are answers. The green lines means the two connected objects are a pair.
|
||||
|
||||
|
||||
## 4. Usage
|
||||
|
||||
### 4.1 Prepare for the environment
|
||||
|
||||
|
||||
Use the following command to install KIE dependencies.
|
||||
|
||||
|
||||
```bash
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
cd PaddleOCR
|
||||
pip install -r requirements.txt
|
||||
pip install -r ppstructure/kie/requirements.txt
|
||||
# 安装PaddleOCR引擎用于预测
|
||||
pip install paddleocr -U
|
||||
```
|
||||
|
||||
NOTE: For KIE tasks, it is necessary to downgrade the Paddle framework version (Paddle<2.6) and the PaddleNLP version (PaddleNLP<2.6).
|
||||
|
||||
The visualized results of SER are saved in the `./output` folder by default. Examples of results are as follows.
|
||||
|
||||
|
||||
<div align="center">
|
||||
<img src="../../ppstructure/docs/kie/result_ser/zh_val_42_ser.jpg" width="800">
|
||||
</div>
|
||||
|
||||
|
||||
### 4.2 Quick start
|
||||
|
||||
Here we use XFUND dataset to quickly experience the SER model and RE model.
|
||||
|
||||
|
||||
#### 4.2.1 Prepare for the dataset
|
||||
|
||||
```bash
|
||||
mkdir train_data
|
||||
cd train_data
|
||||
# download and uncompress the dataset
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar && tar -xf XFUND.tar
|
||||
cd ..
|
||||
```
|
||||
|
||||
#### 4.2.2 Predict images using the trained model
|
||||
|
||||
Use the following command to download the models.
|
||||
|
||||
```bash
|
||||
mkdir pretrained_model
|
||||
cd pretrained_model
|
||||
# download and uncompress the SER trained model
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar && tar -xf ser_vi_layoutxlm_xfund_pretrained.tar
|
||||
|
||||
# download and uncompress the RE trained model
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar && tar -xf re_vi_layoutxlm_xfund_pretrained.tar
|
||||
```
|
||||
|
||||
|
||||
If you want to use OCR engine to obtain end-to-end prediction results, you can use the following command to predict.
|
||||
|
||||
```bash
|
||||
# just predict using SER trained model
|
||||
python3 tools/infer_kie_token_ser.py \
|
||||
-c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./ppstructure/docs/kie/input/zh_val_42.jpg
|
||||
|
||||
# predict using SER and RE trained model at the same time
|
||||
python3 ./tools/infer_kie_token_ser_re.py \
|
||||
-c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./train_data/XFUND/zh_val/image/zh_val_42.jpg \
|
||||
-c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy
|
||||
```
|
||||
|
||||
The visual result images and the predicted text file will be saved in the `Global.save_res_path` directory.
|
||||
|
||||
If you want to use a custom ocr model, you can set it through the following fields
|
||||
- `Global.kie_det_model_dir`: the detection inference model path
|
||||
- `Global.kie_rec_model_dir`: the recognition inference model path
|
||||
|
||||
|
||||
If you want to load the text detection and recognition results collected before, you can use the following command to predict.
|
||||
|
||||
```bash
|
||||
# just predict using SER trained model
|
||||
python3 tools/infer_kie_token_ser.py \
|
||||
-c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./train_data/XFUND/zh_val/val.json \
|
||||
Global.infer_mode=False
|
||||
|
||||
# predict using SER and RE trained model at the same time
|
||||
python3 ./tools/infer_kie_token_ser_re.py \
|
||||
-c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./train_data/XFUND/zh_val/val.json \
|
||||
Global.infer_mode=False \
|
||||
-c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy
|
||||
```
|
||||
|
||||
#### 4.2.3 Inference using PaddleInference
|
||||
|
||||
Firstly, download the inference SER inference model.
|
||||
|
||||
```bash
|
||||
mkdir inference
|
||||
cd inference
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_infer.tar && tar -xf ser_vi_layoutxlm_xfund_infer.tar
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_infer.tar && tar -xf re_vi_layoutxlm_xfund_infer.tar
|
||||
cd ..
|
||||
```
|
||||
|
||||
- SER
|
||||
|
||||
Use the following command for inference.
|
||||
|
||||
|
||||
```bash
|
||||
cd ppstructure
|
||||
python3 kie/predict_kie_token_ser.py \
|
||||
--kie_algorithm=LayoutXLM \
|
||||
--ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \
|
||||
--image_dir=./docs/kie/input/zh_val_42.jpg \
|
||||
--ser_dict_path=../train_data/XFUND/class_list_xfun.txt \
|
||||
--vis_font_path=../doc/fonts/simfang.ttf \
|
||||
--ocr_order_method="tb-yx"
|
||||
```
|
||||
|
||||
The visual results and text file will be saved in directory `output`.
|
||||
|
||||
- RE
|
||||
|
||||
Use the following command for inference.
|
||||
|
||||
|
||||
```bash
|
||||
cd ppstructure
|
||||
python3 kie/predict_kie_token_ser_re.py \
|
||||
--kie_algorithm=LayoutXLM \
|
||||
--re_model_dir=../inference/re_vi_layoutxlm_xfund_infer \
|
||||
--ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \
|
||||
--use_visual_backbone=False \
|
||||
--image_dir=./docs/kie/input/zh_val_42.jpg \
|
||||
--ser_dict_path=../train_data/XFUND/class_list_xfun.txt \
|
||||
--vis_font_path=../doc/fonts/simfang.ttf \
|
||||
--ocr_order_method="tb-yx"
|
||||
```
|
||||
|
||||
The visual results and text file will be saved in directory `output`.
|
||||
|
||||
If you want to use a custom ocr model, you can set it through the following fields
|
||||
- `--det_model_dir`: the detection inference model path
|
||||
- `--rec_model_dir`: the recognition inference model path
|
||||
|
||||
### 4.3 More
|
||||
|
||||
For training, evaluation and inference tutorial for KIE models, please refer to [KIE doc](../../doc/doc_en/kie_en.md).
|
||||
|
||||
For training, evaluation and inference tutorial for text detection models, please refer to [text detection doc](../../doc/doc_en/detection_en.md).
|
||||
|
||||
For training, evaluation and inference tutorial for text recognition models, please refer to [text recognition doc](../../doc/doc_en/recognition_en.md).
|
||||
|
||||
To complete the key information extraction task in your own scenario from data preparation to model selection, please refer to: [Guide to End-to-end KIE](./how_to_do_kie_en.md)。
|
||||
|
||||
|
||||
## 5. Reference
|
||||
|
||||
- LayoutXLM: Multimodal Pre-training for Multilingual Visually-rich Document Understanding, https://arxiv.org/pdf/2104.08836.pdf
|
||||
- microsoft/unilm/layoutxlm, https://github.com/microsoft/unilm/tree/master/layoutxlm
|
||||
- XFUND dataset, https://github.com/doc-analysis/XFUND
|
||||
|
||||
## 6. License
|
||||
|
||||
The content of this project itself is licensed under the [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
271
ppstructure/kie/README_ch.md
Normal file
271
ppstructure/kie/README_ch.md
Normal file
@@ -0,0 +1,271 @@
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
# 关键信息抽取
|
||||
|
||||
- [1. 简介](#1-简介)
|
||||
- [2. 精度与性能](#2-精度与性能)
|
||||
- [3. 效果演示](#3-效果演示)
|
||||
- [3.1 SER](#31-ser)
|
||||
- [3.2 RE](#32-re)
|
||||
- [4. 使用](#4-使用)
|
||||
- [4.1 准备环境](#41-准备环境)
|
||||
- [4.2 快速开始](#42-快速开始)
|
||||
- [4.3 更多](#43-更多)
|
||||
- [5. 参考链接](#5-参考链接)
|
||||
- [6. License](#6-License)
|
||||
|
||||
|
||||
## 1. 简介
|
||||
|
||||
关键信息抽取 (Key Information Extraction, KIE)指的是是从文本或者图像中,抽取出关键的信息。针对文档图像的关键信息抽取任务作为OCR的下游任务,存在非常多的实际应用场景,如表单识别、车票信息抽取、身份证信息抽取等。
|
||||
|
||||
PP-Structure 基于 LayoutXLM 文档多模态系列方法进行研究与优化,设计了视觉特征无关的多模态模型结构VI-LayoutXLM,同时引入符合阅读顺序的文本行排序方法以及UDML联合互学习蒸馏方法,最终在精度与速度均超越LayoutXLM。
|
||||
|
||||
PP-Structure中关键信息抽取模块的主要特性如下:
|
||||
|
||||
- 集成[LayoutXLM](https://arxiv.org/pdf/2104.08836.pdf)、VI-LayoutXLM等多模态模型以及PP-OCR预测引擎。
|
||||
- 支持基于多模态方法的语义实体识别 (Semantic Entity Recognition, SER) 以及关系抽取 (Relation Extraction, RE) 任务。基于 SER 任务,可以完成对图像中的文本识别与分类;基于 RE 任务,可以完成对图象中的文本内容的关系提取,如判断问题对(pair)。
|
||||
- 支持SER任务和RE任务的自定义训练。
|
||||
- 支持OCR+SER的端到端系统预测与评估。
|
||||
- 支持OCR+SER+RE的端到端系统预测。
|
||||
- 支持SER模型的动转静导出与基于PaddleInfernece的模型推理。
|
||||
|
||||
|
||||
## 2. 精度与性能
|
||||
|
||||
|
||||
我们在 [XFUND](https://github.com/doc-analysis/XFUND) 的中文数据集上对算法进行了评估,SER与RE上的任务性能如下
|
||||
|
||||
|模型|骨干网络|任务|配置文件|hmean|预测耗时(ms)|下载链接|
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
|VI-LayoutXLM| VI-LayoutXLM-base | SER | [ser_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml)|**93.19%**| 15.49|[训练模型](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar)|
|
||||
|LayoutXLM| LayoutXLM-base | SER | [ser_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml)|90.38%| 19.49 | [训练模型](https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar)|
|
||||
|VI-LayoutXLM| VI-LayoutXLM-base | RE | [re_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml)|**83.92%**| 15.49|[训练模型](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar)|
|
||||
|LayoutXLM| LayoutXLM-base | RE | [re_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml)|74.83%| 19.49|[训练模型](https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar)|
|
||||
|
||||
|
||||
* 注:预测耗时测试条件:V100 GPU + cuda10.2 + cudnn8.1.1 + TensorRT 7.2.3.4,使用FP16进行测试。
|
||||
|
||||
更多关于PaddleOCR中关键信息抽取模型的介绍,请参考[关键信息抽取模型库](../../doc/doc_ch/algorithm_overview.md)。
|
||||
|
||||
|
||||
## 3. 效果演示
|
||||
|
||||
基于多模态模型的关键信息抽取任务有2种主要的解决方案。
|
||||
|
||||
(1)文本检测 + 文本识别 + 语义实体识别(SER)
|
||||
(2)文本检测 + 文本识别 + 语义实体识别(SER) + 关系抽取(RE)
|
||||
|
||||
下面给出SER与RE任务的示例效果,关于上述解决方案的详细介绍,请参考[关键信息抽取全流程指南](./how_to_do_kie.md)。
|
||||
|
||||
### 3.1 SER
|
||||
|
||||
对于SER任务,效果如下所示。
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185539141-68e71c75-5cf7-4529-b2ca-219d29fa5f68.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185539517-ccf2372a-f026-4a7c-ad28-c741c770f60a.png" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185539735-37b5c2ef-629d-43fe-9abb-44bb717ef7ee.jpg" width="600">
|
||||
</div>
|
||||
|
||||
**注意:** 测试图片来源于[XFUND数据集](https://github.com/doc-analysis/XFUND)、[发票数据集](https://aistudio.baidu.com/aistudio/datasetdetail/165561)以及合成的身份证数据集。
|
||||
|
||||
|
||||
图中不同颜色的框表示不同的类别。
|
||||
|
||||
图中的发票以及申请表图像,有`QUESTION`, `ANSWER`, `HEADER` 3种类别,识别的`QUESTION`, `ANSWER`可以用于后续的问题与答案的关系抽取。
|
||||
|
||||
图中的身份证图像,则直接识别出其中的`姓名`、`性别`、`民族`等关键信息,这样就无需后续的关系抽取过程,一个模型即可完成关键信息抽取。
|
||||
|
||||
|
||||
### 3.2 RE
|
||||
|
||||
对于RE任务,效果如下所示。
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185540080-0431e006-9235-4b6d-b63d-0b3c6e1de48f.jpg" width="600">
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185540291-f64e5daf-6d42-4e7c-bbbb-471e3fac4fcc.png" width="600">
|
||||
</div>
|
||||
|
||||
|
||||
红色框是问题,蓝色框是答案。绿色线条表示连接的两端为一个key-value的pair。
|
||||
|
||||
## 4. 使用
|
||||
|
||||
### 4.1 准备环境
|
||||
|
||||
使用下面的命令安装运行SER与RE关键信息抽取的依赖。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
cd PaddleOCR
|
||||
pip install -r requirements.txt
|
||||
pip install -r ppstructure/kie/requirements.txt
|
||||
# 安装PaddleOCR引擎用于预测
|
||||
pip install paddleocr -U
|
||||
```
|
||||
|
||||
NOTE: 对于KIE任务需要降低Paddle框架版本(Paddle<2.6),和PaddleNLP版本(PaddleNLP<2.6)。
|
||||
|
||||
### 4.2 快速开始
|
||||
|
||||
下面XFUND数据集,快速体验SER模型与RE模型。
|
||||
|
||||
#### 4.2.1 准备数据
|
||||
|
||||
```bash
|
||||
mkdir train_data
|
||||
cd train_data
|
||||
# 下载与解压数据
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar && tar -xf XFUND.tar
|
||||
cd ..
|
||||
```
|
||||
|
||||
#### 4.2.2 基于动态图的预测
|
||||
|
||||
首先下载模型。
|
||||
|
||||
```bash
|
||||
mkdir pretrained_model
|
||||
cd pretrained_model
|
||||
# 下载并解压SER预训练模型
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar && tar -xf ser_vi_layoutxlm_xfund_pretrained.tar
|
||||
|
||||
# 下载并解压RE预训练模型
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar && tar -xf re_vi_layoutxlm_xfund_pretrained.tar
|
||||
```
|
||||
|
||||
如果希望使用OCR引擎,获取端到端的预测结果,可以使用下面的命令进行预测。
|
||||
|
||||
```bash
|
||||
# 仅预测SER模型
|
||||
python3 tools/infer_kie_token_ser.py \
|
||||
-c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./ppstructure/docs/kie/input/zh_val_42.jpg
|
||||
|
||||
# SER + RE模型串联
|
||||
python3 ./tools/infer_kie_token_ser_re.py \
|
||||
-c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./train_data/XFUND/zh_val/image/zh_val_42.jpg \
|
||||
-c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy
|
||||
```
|
||||
|
||||
`Global.save_res_path`目录中会保存可视化的结果图像以及预测的文本文件。
|
||||
|
||||
如果想使用自定义OCR模型,可通过如下字段进行设置
|
||||
- `Global.kie_det_model_dir`: 设置检测inference模型地址
|
||||
- `Global.kie_rec_model_dir`: 设置识别inference模型地址
|
||||
|
||||
|
||||
如果希望加载标注好的文本检测与识别结果,仅预测可以使用下面的命令进行预测。
|
||||
|
||||
```bash
|
||||
# 仅预测SER模型
|
||||
python3 tools/infer_kie_token_ser.py \
|
||||
-c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./train_data/XFUND/zh_val/val.json \
|
||||
Global.infer_mode=False
|
||||
|
||||
# SER + RE模型串联
|
||||
python3 ./tools/infer_kie_token_ser_re.py \
|
||||
-c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \
|
||||
-o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \
|
||||
Global.infer_img=./train_data/XFUND/zh_val/val.json \
|
||||
Global.infer_mode=False \
|
||||
-c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \
|
||||
-o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy
|
||||
```
|
||||
|
||||
#### 4.2.3 基于PaddleInference的预测
|
||||
|
||||
首先下载SER和RE的推理模型。
|
||||
|
||||
```bash
|
||||
mkdir inference
|
||||
cd inference
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_infer.tar && tar -xf ser_vi_layoutxlm_xfund_infer.tar
|
||||
wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_infer.tar && tar -xf re_vi_layoutxlm_xfund_infer.tar
|
||||
cd ..
|
||||
```
|
||||
|
||||
- SER
|
||||
|
||||
执行下面的命令进行预测。
|
||||
|
||||
```bash
|
||||
cd ppstructure
|
||||
python3 kie/predict_kie_token_ser.py \
|
||||
--kie_algorithm=LayoutXLM \
|
||||
--ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \
|
||||
--image_dir=./docs/kie/input/zh_val_42.jpg \
|
||||
--ser_dict_path=../train_data/XFUND/class_list_xfun.txt \
|
||||
--vis_font_path=../doc/fonts/simfang.ttf \
|
||||
--ocr_order_method="tb-yx"
|
||||
```
|
||||
|
||||
可视化结果保存在`output`目录下。
|
||||
|
||||
- RE
|
||||
|
||||
执行下面的命令进行预测。
|
||||
|
||||
```bash
|
||||
cd ppstructure
|
||||
python3 kie/predict_kie_token_ser_re.py \
|
||||
--kie_algorithm=LayoutXLM \
|
||||
--re_model_dir=../inference/re_vi_layoutxlm_xfund_infer \
|
||||
--ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \
|
||||
--use_visual_backbone=False \
|
||||
--image_dir=./docs/kie/input/zh_val_42.jpg \
|
||||
--ser_dict_path=../train_data/XFUND/class_list_xfun.txt \
|
||||
--vis_font_path=../doc/fonts/simfang.ttf \
|
||||
--ocr_order_method="tb-yx"
|
||||
```
|
||||
|
||||
可视化结果保存在`output`目录下。
|
||||
|
||||
如果想使用自定义OCR模型,可通过如下字段进行设置
|
||||
- `--det_model_dir`: 设置检测inference模型地址
|
||||
- `--rec_model_dir`: 设置识别inference模型地址
|
||||
|
||||
### 4.3 更多
|
||||
|
||||
关于KIE模型的训练评估与推理,请参考:[关键信息抽取教程](../../doc/doc_ch/kie.md)。
|
||||
|
||||
关于文本检测模型的训练评估与推理,请参考:[文本检测教程](../../doc/doc_ch/detection.md)。
|
||||
|
||||
关于文本识别模型的训练评估与推理,请参考:[文本识别教程](../../doc/doc_ch/recognition.md)。
|
||||
|
||||
关于怎样在自己的场景中完成关键信息抽取任务,请参考:[关键信息抽取全流程指南](./how_to_do_kie.md)。
|
||||
|
||||
|
||||
## 5. 参考链接
|
||||
|
||||
- LayoutXLM: Multimodal Pre-training for Multilingual Visually-rich Document Understanding, https://arxiv.org/pdf/2104.08836.pdf
|
||||
- microsoft/unilm/layoutxlm, https://github.com/microsoft/unilm/tree/master/layoutxlm
|
||||
- XFUND dataset, https://github.com/doc-analysis/XFUND
|
||||
|
||||
## 6. License
|
||||
|
||||
The content of this project itself is licensed under the [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
168
ppstructure/kie/how_to_do_kie.md
Normal file
168
ppstructure/kie/how_to_do_kie.md
Normal file
@@ -0,0 +1,168 @@
|
||||
|
||||
# 怎样完成基于图像数据的信息抽取任务
|
||||
|
||||
- [1. 简介](#1-简介)
|
||||
- [1.1 背景](#11-背景)
|
||||
- [1.2 主流方法](#12-主流方法)
|
||||
- [2. 关键信息抽取任务流程](#2-关键信息抽取任务流程)
|
||||
- [2.1 训练OCR模型](#21-训练OCR模型)
|
||||
- [2.2 训练KIE模型](#22-训练KIE模型)
|
||||
- [3. 参考文献](#3-参考文献)
|
||||
|
||||
|
||||
## 1. 简介
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
关键信息抽取 (Key Information Extraction, KIE)指的是是从文本或者图像中,抽取出关键的信息。针对文档图像的关键信息抽取任务作为OCR的下游任务,存在非常多的实际应用场景,如表单识别、车票信息抽取、身份证信息抽取等。然而,使用人力从这些文档图像中提取或者收集关键信息耗时费力,怎样自动化融合图像中的视觉、布局、文字等特征并完成关键信息抽取是一个价值与挑战并存的问题。
|
||||
|
||||
对于特定场景的文档图像,其中的关键信息位置、版式等较为固定,因此在研究早期有很多基于模板匹配的方法进行关键信息的抽取,考虑到其流程较为简单,该方法仍然被广泛应用在目前的很多场景中。但是这种基于模板匹配的方法在应用到不同的场景中时,需要耗费大量精力去调整与适配模板,迁移成本较高。
|
||||
|
||||
文档图像中的KIE一般包含2个子任务,示意图如下图所示。
|
||||
|
||||
* (1)SER: 语义实体识别 (Semantic Entity Recognition),对每一个检测到的文本进行分类,如将其分为姓名,身份证。如下图中的黑色框和红色框。
|
||||
* (2)RE: 关系抽取 (Relation Extraction),对每一个检测到的文本进行分类,如将其分为问题 (key) 和答案 (value) 。然后对每一个问题找到对应的答案,相当于完成key-value的匹配过程。如下图中的红色框和黑色框分别代表问题和答案,黄色线代表问题和答案之间的对应关系。
|
||||
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/184588654-d87f54f3-13ab-42c4-afc0-da79bead3f14.png" width="800">
|
||||
</div>
|
||||
|
||||
|
||||
### 1.2 基于深度学习的主流方法
|
||||
|
||||
一般的KIE方法基于命名实体识别(Named Entity Recognition,NER)来展开研究,但是此类方法仅使用了文本信息而忽略了位置与视觉特征信息,因此精度受限。近几年大多学者开始融合多个模态的输入信息,进行特征融合,并对多模态信息进行处理,从而提升KIE的精度。主要方法有以下几种
|
||||
|
||||
* (1)基于Grid的方法:此类方法主要关注图像层面多模态信息的融合,文本大多大多为字符粒度,对文本与结构结构信息的嵌入方式较为简单,如Chargrid[1]等算法。
|
||||
* (2)基于Token的方法:此类方法参考NLP中的BERT等方法,将位置、视觉等特征信息共同编码到多模态模型中,并且在大规模数据集上进行预训练,从而在下游任务中,仅需要少量的标注数据便可以获得很好的效果。如LayoutLM[2], LayoutLMv2[3], LayoutXLM[4], StrucText[5]等算法。
|
||||
* (3)基于GCN的方法:此类方法尝试学习图像、文字之间的结构信息,从而可以解决开集信息抽取的问题(训练集中没有见过的模板),如GCN[6]、SDMGR[7]等算法。
|
||||
* (4)基于End-to-end的方法:此类方法将现有的OCR文字识别以及KIE信息抽取2个任务放在一个统一的网络中进行共同学习,并在学习过程中相互加强。如Trie[8]等算法。
|
||||
|
||||
更多关于该系列算法的详细介绍,请参考“动手学OCR·十讲”课程的课节六部分:[文档分析理论与实践](https://aistudio.baidu.com/aistudio/education/group/info/25207)。
|
||||
|
||||
## 2. 关键信息抽取任务流程
|
||||
|
||||
PaddleOCR中实现了LayoutXLM等算法(基于Token),同时,在PP-StructureV2中,对LayoutXLM多模态预训练模型的网络结构进行简化,去除了其中的Visual backbone部分,设计了视觉无关的VI-LayoutXLM模型,同时引入符合人类阅读顺序的排序逻辑以及UDML知识蒸馏策略,最终同时提升了关键信息抽取模型的精度与推理速度。
|
||||
|
||||
下面介绍怎样基于PaddleOCR完成关键信息抽取任务。
|
||||
|
||||
在非End-to-end的KIE方法中,完成关键信息抽取,至少需要**2个步骤**:首先使用OCR模型,完成文字位置与内容的提取,然后使用KIE模型,根据图像、文字位置以及文字内容,提取出其中的关键信息。
|
||||
|
||||
### 2.1 训练OCR模型
|
||||
|
||||
#### 2.1.1 文本检测
|
||||
|
||||
**(1)数据**
|
||||
|
||||
PaddleOCR中提供的模型大多数为通用模型,在进行文本检测的过程中,相邻文本行的检测一般是根据位置的远近进行区分,如上图,使用PP-OCRv3通用中英文检测模型进行文本检测时,容易将”民族“与“汉”这2个代表不同的字段检测到一起,从而增加后续KIE任务的难度。因此建议在做KIE任务的过程中,首先训练一个针对该文档数据集的检测模型。
|
||||
|
||||
在数据标注时,关键信息的标注需要隔开,比上图中的 “民族汉” 3个字相隔较近,此时需要将”民族“与”汉“标注为2个文本检测框,否则会增加后续KIE任务的难度。
|
||||
|
||||
对于下游任务,一般来说,`200~300`张的文本训练数据即可保证基本的训练效果,如果没有太多的先验知识,可以先标注 **`200~300`** 张图片,进行后续文本检测模型的训练。
|
||||
|
||||
|
||||
**(2)模型**
|
||||
|
||||
在模型选择方面,推荐使用PP-OCRv3_det,关于更多关于检测模型的训练方法介绍,请参考:[OCR文本检测模型训练教程](../../doc/doc_ch/detection.md)与[PP-OCRv3 文本检测模型训练教程](../../doc/doc_ch/PPOCRv3_det_train.md)。
|
||||
|
||||
#### 2.1.2 文本识别
|
||||
|
||||
相对自然场景,文档图像中的文本内容识别难度一般相对较低(背景相对不太复杂),因此**优先建议**尝试PaddleOCR中提供的PP-OCRv3通用文本识别模型([PP-OCRv3模型库链接](../../doc/doc_ch/models_list.md))。
|
||||
|
||||
**(1)数据**
|
||||
|
||||
然而,在部分文档场景中也会存在一些挑战,如身份证场景中存在着罕见字,在发票等场景中的字体比较特殊,这些问题都会增加文本识别的难度,此时如果希望保证或者进一步提升模型的精度,建议基于特定文档场景的文本识别数据集,加载PP-OCRv3模型进行微调。
|
||||
|
||||
在模型微调的过程中,建议准备至少`5000`张垂类场景的文本识别图像,可以保证基本的模型微调效果。如果希望提升模型的精度与泛化能力,可以合成更多与该场景类似的文本识别数据,从公开数据集中收集通用真实文本识别数据,一并添加到该场景的文本识别训练任务过程中。在训练过程中,建议每个epoch的真实垂类数据、合成数据、通用数据比例在`1:1:1`左右,这可以通过设置不同数据源的采样比例进行控制。如有3个训练文本文件,分别包含1W、2W、5W条数据,那么可以在配置文件中设置数据如下:
|
||||
|
||||
```yml
|
||||
Train:
|
||||
dataset:
|
||||
name: SimpleDataSet
|
||||
data_dir: ./train_data/
|
||||
label_file_list:
|
||||
- ./train_data/train_list_1W.txt
|
||||
- ./train_data/train_list_2W.txt
|
||||
- ./train_data/train_list_5W.txt
|
||||
ratio_list: [1.0, 0.5, 0.2]
|
||||
...
|
||||
```
|
||||
|
||||
**(2)模型**
|
||||
|
||||
在模型选择方面,推荐使用通用中英文文本识别模型PP-OCRv3_rec,关于更多关于文本识别模型的训练方法介绍,请参考:[OCR文本识别模型训练教程](../../doc/doc_ch/recognition.md)与[PP-OCRv3文本识别模型库与配置文件](../../doc/doc_ch/models_list.md)。
|
||||
|
||||
### 2.2 训练KIE模型
|
||||
|
||||
对于识别得到的文字进行关键信息抽取,有2种主要的方法。
|
||||
|
||||
(1)直接使用SER,获取关键信息的类别:如身份证场景中,将“姓名“与”张三“分别标记为`name_key`与`name_value`。最终识别得到的类别为`name_value`对应的**文本字段**即为我们所需要的关键信息。
|
||||
|
||||
(2)联合SER与RE进行使用:这种方法中,首先使用SER,获取图像文字内容中所有的key与value,然后使用RE方法,对所有的key与value进行配对,找到映射关系,从而完成关键信息的抽取。
|
||||
|
||||
#### 2.2.1 SER
|
||||
|
||||
以身份证场景为例, 关键信息一般包含`姓名`、`性别`、`民族`等,我们直接将对应的字段标注为特定的类别即可,如下图所示。
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/184526682-8b810397-5a93-4395-93da-37b8b8494c41.png" width="500">
|
||||
</div>
|
||||
|
||||
**注意:**
|
||||
|
||||
- 标注过程中,对于无关于KIE关键信息的文本内容,均需要将其标注为`other`类别,相当于背景信息。如在身份证场景中,如果我们不关注性别信息,那么可以将“性别”与“男”这2个字段的类别均标注为`other`。
|
||||
- 标注过程中,需要以**文本行**为单位进行标注,无需标注单个字符的位置信息。
|
||||
|
||||
数据量方面,一般来说,对于比较固定的场景,**50张**左右的训练图片即可达到可以接受的效果,可以使用[PPOCRLabel](https://github.com/PFCCLab/PPOCRLabel/blob/main/README_ch.md)完成KIE的标注过程。
|
||||
|
||||
模型方面,推荐使用PP-StructureV2中提出的VI-LayoutXLM模型,它基于LayoutXLM模型进行改进,去除其中的视觉特征提取模块,在精度基本无损的情况下,进一步提升了模型推理速度。更多教程请参考:[VI-LayoutXLM算法介绍](../../doc/doc_ch/algorithm_kie_vi_layoutxlm.md)与[KIE关键信息抽取使用教程](../../doc/doc_ch/kie.md)。
|
||||
|
||||
|
||||
#### 2.2.2 SER + RE
|
||||
|
||||
该过程主要包含SER与RE 2个过程。SER阶段主要用于识别出文档图像中的所有key与value,RE阶段主要用于对所有的key与value进行匹配。
|
||||
|
||||
以身份证场景为例, 关键信息一般包含`姓名`、`性别`、`民族`等关键信息,在SER阶段,我们需要识别所有的question (key) 与answer (value) 。标注如下所示。每个字段的类别信息(`label`字段)可以是question、answer或者other(与待抽取的关键信息无关的字段)
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/184526785-c3d2d310-cd57-4d31-b933-912716b29856.jpg" width="500">
|
||||
</div>
|
||||
|
||||
|
||||
在RE阶段,需要标注每个字段的的id与连接信息,如下图所示。
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/184528728-626f77eb-fd9f-4709-a7dc-5411cc417dab.jpg" width="500">
|
||||
</div>
|
||||
|
||||
每个文本行字段中,需要添加`id`与`linking`字段信息,`id`记录该文本行的唯一标识,同一张图片中的不同文本内容不能重复,`linking`是一个列表,记录了不同文本之间的连接信息。如字段“出生”的id为0,字段“1996年1月11日”的id为1,那么它们均有[[0, 1]]的`linking`标注,表示该id=0与id=1的字段构成key-value的关系(姓名、性别等字段类似,此处不再一一赘述)。
|
||||
|
||||
|
||||
**注意:**
|
||||
|
||||
- 标注过程中,如果value是多个字符,那么linking中可以新增一个key-value对,如`[[0, 1], [0, 2]]`
|
||||
|
||||
|
||||
数据量方面,一般来说,对于比较固定的场景,**50张**左右的训练图片即可达到可以接受的效果,可以使用PPOCRLabel完成KIE的标注过程。
|
||||
|
||||
模型方面,推荐使用PP-StructureV2中提出的VI-LayoutXLM模型,它基于LayoutXLM模型进行改进,去除其中的视觉特征提取模块,在精度基本无损的情况下,进一步提升了模型推理速度。更多教程请参考:[VI-LayoutXLM算法介绍](../../doc/doc_ch/algorithm_kie_vi_layoutxlm.md)与[KIE关键信息抽取使用教程](../../doc/doc_ch/kie.md)。
|
||||
|
||||
|
||||
## 3. 参考文献
|
||||
|
||||
|
||||
[1] Katti A R, Reisswig C, Guder C, et al. Chargrid: Towards understanding 2d documents[J]. arXiv preprint arXiv:1809.08799, 2018.
|
||||
|
||||
[2] Xu Y, Li M, Cui L, et al. Layoutlm: Pre-training of text and layout for document image understanding[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 1192-1200.
|
||||
|
||||
[3] Xu Y, Xu Y, Lv T, et al. LayoutLMv2: Multi-modal pre-training for visually-rich document understanding[J]. arXiv preprint arXiv:2012.14740, 2020.
|
||||
|
||||
[4]: Xu Y, Lv T, Cui L, et al. Layoutxlm: Multimodal pre-training for multilingual visually-rich document understanding[J]. arXiv preprint arXiv:2104.08836, 2021.
|
||||
|
||||
[5] Li Y, Qian Y, Yu Y, et al. StrucTexT: Structured Text Understanding with Multi-Modal Transformers[C]//Proceedings of the 29th ACM International Conference on Multimedia. 2021: 1912-1920.
|
||||
|
||||
[6] Liu X, Gao F, Zhang Q, et al. Graph convolution for multimodal information extraction from visually rich documents[J]. arXiv preprint arXiv:1903.11279, 2019.
|
||||
|
||||
[7] Sun H, Kuang Z, Yue X, et al. Spatial Dual-Modality Graph Reasoning for Key Information Extraction[J]. arXiv preprint arXiv:2103.14470, 2021.
|
||||
|
||||
[8] Zhang P, Xu Y, Cheng Z, et al. Trie: End-to-end text reading and information extraction for document understanding[C]//Proceedings of the 28th ACM International Conference on Multimedia. 2020: 1413-1422.
|
||||
179
ppstructure/kie/how_to_do_kie_en.md
Normal file
179
ppstructure/kie/how_to_do_kie_en.md
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
# Key Information Extraction Pipeline
|
||||
|
||||
- [1. Introduction](#1-Introduction)
|
||||
- [1.1 Background](#11-Background)
|
||||
- [1.2 Mainstream Deep-learning Solutions](#12-Mainstream-Deep-learning-Solutions)
|
||||
- [2. KIE Pipeline](#2-KIE-Pipeline)
|
||||
- [2.1 Train OCR Models](#21-Train-OCR-Models)
|
||||
- [2.2 Train KIE Models](#22-Train-KIE-Models)
|
||||
- [3. Reference](#3-Reference)
|
||||
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
### 1.1 Background
|
||||
|
||||
Key information extraction (KIE) refers to extracting key information from text or images. As the downstream task of OCR, KIE of document image has many practical application scenarios, such as form recognition, ticket information extraction, ID card information extraction, etc. However, it is time-consuming and laborious to extract key information from these document images by manpower. It's challengable but also valuable to combine multi-modal features (visual, layout, text, etc) together and complete KIE tasks.
|
||||
|
||||
For the document images in a specific scene, the position and layout of the key information are relatively fixed. Therefore, in the early stage of the research, there are many methods based on template matching to extract the key information. This method is still widely used in many simple scenarios at present. However, it takes long time to adjut the template for different scenarios.
|
||||
|
||||
|
||||
The KIE in the document image generally contains 2 subtasks, which is as shown follows.
|
||||
|
||||
* (1) SER: semantic entity recognition, which classifies each detected textline, such as dividing it into name and ID No. As shown in the red boxes in the following figure.
|
||||
|
||||
* (2) RE: relationship extraction, which matches the question and answer based on SER results. As shown in the figure below, the yellow arrows match the question and answer.
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185726510-faba470d-2c79-4784-b8da-6c1aa5af9572.png" width="800">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### 1.2 Mainstream Deep-learning Solutions
|
||||
|
||||
General KIE methods are based on Named Entity Recognition (NER), but such methods only use text information and ignore location and visual feature information, which leads to limited accuracy. In recent years, most scholars have started to combine mutil-modal features to improve the accuracy of KIE model. The main methods are as follows:
|
||||
|
||||
* (1) Grid based methods. These methods mainly focus on the fusion of multi-modal information at the image level. Most texts are of character granularity. The text and structure information embedding method is simple, such as the algorithm of chargrid [1].
|
||||
|
||||
* (2) Token based methods. These methods refer to the NLP methods such as Bert, which encode the position, vision and other feature information into the multi-modal model, and conduct pre-training on large-scale datasets, so that in downstream tasks, only a small amount of annotation data is required to obtain excellent results. The representative algorithms are layoutlm [2], layoutlmv2 [3], layoutxlm [4], structext [5], etc.
|
||||
|
||||
* (3) GCN based methods. These methods try to learn the structural information between images and characters, so as to solve the problem of extracting open set information (templates not seen in the training set), such as GCN [6], SDMGR [7] and other algorithms.
|
||||
|
||||
* (4) End to end based methods: these methods put the existing OCR character recognition and KIE information extraction tasks into a unified network for common learning, and strengthen each other in the learning process. Such as TRIE [8].
|
||||
|
||||
|
||||
For more detailed introduction of the algorithms, please refer to Chapter 6 of [Diving into OCR](https://aistudio.baidu.com/aistudio/education/group/info/25207).
|
||||
|
||||
## 2. KIE Pipeline
|
||||
|
||||
Token based methods such as LayoutXLM are implemented in PaddleOCR. What's more, in PP-StructureV2, we simplify the LayoutXLM model and proposed VI-LayoutXLM, in which the visual feature extraction module is removed for speed-up. The textline sorting strategy conforming to the human reading order and UDML knowledge distillation strategy are utilized for higher model accuracy.
|
||||
|
||||
|
||||
In the non end-to-end KIE method, KIE needs at least **2 steps**. Firstly, the OCR model is used to extract the text and its position. Secondly, the KIE model is used to extract the key information according to the image, text position and text content.
|
||||
|
||||
|
||||
### 2.1 Train OCR Models
|
||||
|
||||
#### 2.1.1 Text Detection
|
||||
|
||||
**(1) Data**
|
||||
|
||||
Most of the models provided in PaddleOCR are general models. In the process of text detection, the detection of adjacent text lines is generally based on the distance of the position. As shown in the figure above, when using PP-OCRv3 general English detection model for text detection, it is easy to detect the two fields representing different properties as one. Therefore, it is suggested to finetune a detection model according to your scenario firstly during the KIE task.
|
||||
|
||||
|
||||
During data annotation, the different key information needs to be separated. Otherwise, it will increase the difficulty of subsequent KIE tasks.
|
||||
|
||||
For downstream tasks, generally speaking, `200~300` training images can guarantee the basic training effect. If there is not too much prior knowledge, **`200~300`** images can be labeled firstly for subsequent text detection model training.
|
||||
|
||||
**(2) Model**
|
||||
|
||||
In terms of model selection, PP-OCRv3 detection model is recommended. For more information about the training methods of the detection model, please refer to: [Text detection tutorial](../../doc/doc_en/detection_en.md) and [PP-OCRv3 detection model tutorial](../../doc/doc_ch/PPOCRv3_det_train.md).
|
||||
|
||||
#### 2.1.2 Text recognition
|
||||
|
||||
|
||||
Compared with the natural scene, the text recognition in the document image is generally relatively easier (the background is not too complex), so **it is suggested to** try the PP-OCRv3 general text recognition model provided in PaddleOCR ([PP-OCRv3 model list](../../doc/doc_en/models_list_en.md))
|
||||
|
||||
|
||||
**(1) Data**
|
||||
|
||||
However, there are also some challenges in some document scenarios, such as rare words in ID card scenarios and special fonts in invoice and other scenarios. These problems will increase the difficulty of text recognition. At this time, if you want to ensure or further improve the model accuracy, it is recommended to load PP-OCRv3 model based on the text recognition dataset of specific document scenarios for finetuning.
|
||||
|
||||
In the process of model finetuning, it is recommended to prepare at least `5000` vertical scene text recognition images to ensure the basic model fine-tuning effect. If you want to improve the accuracy and generalization ability of the model, you can synthesize more text recognition images similar to the scene, collect general real text recognition data from the public data set, and add them to the text recognition training process. In the training process, it is suggested that the ratio of real data, synthetic data and general data of each epoch should be around `1:1:1`, which can be controlled by setting the sampling ratio of different data sources. If there are 3 training text files, including 10k, 20k and 50k pieces of data respectively, the data can be set in the configuration file as follows:
|
||||
|
||||
```yml
|
||||
Train:
|
||||
dataset:
|
||||
name: SimpleDataSet
|
||||
data_dir: ./train_data/
|
||||
label_file_list:
|
||||
- ./train_data/train_list_10k.txt
|
||||
- ./train_data/train_list_10k.txt
|
||||
- ./train_data/train_list_50k.txt
|
||||
ratio_list: [1.0, 0.5, 0.2]
|
||||
...
|
||||
```
|
||||
|
||||
**(2) Model**
|
||||
|
||||
In terms of model selection, PP-OCRv3 recognition model is recommended. For more information about the training methods of the recognition model, please refer to: [Text recognition tutorial](../../doc/doc_en/recognition_en.md) and [PP-OCRv3 model list](../../doc/doc_en/models_list_en.md).
|
||||
|
||||
|
||||
### 2.2 Train KIE Models
|
||||
|
||||
There are two main methods to extract the key information from the recognized texts.
|
||||
|
||||
(1) Directly use SER model to obtain the key information category. For example, in the ID card scenario, we mark "name" and "Geoff Sample" as "name_key" and "name_value", respectively. The **text field** corresponding to the category "name_value" finally identified is the key information we need.
|
||||
|
||||
(2) Joint use SER and RE models. For this case, we firstly use SER model to obtain all questions (keys) and questions (values) for the image text, and then use RE model to match all keys and values to find the relationship, so as to complete the extraction of key information.
|
||||
|
||||
#### 2.2.1 SER
|
||||
|
||||
Take the ID card scenario as an example. The key information generally includes `name`, `DOB`, etc. We can directly mark the corresponding fields as specific categories, as shown in the following figure.
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185728456-dc396f47-0880-4279-9c7c-c99601bf16a7.png" width="500">
|
||||
</div>
|
||||
|
||||
**Note:**
|
||||
|
||||
- In the labeling process, text content without key information about KIE shall be labeled as`other`, which is equivalent to background information. For example, in the ID card scenario, if we do not pay attention to `DOB` information, we can mark the categories of `DOB` and `Area manager` as `other`.
|
||||
- In the annotation process of, it is required to annotate the **textline** position rather than the character.
|
||||
|
||||
|
||||
In terms of data, generally speaking, for relatively fixed scenes, **50** training images can achieve acceptable effects. You can refer to [PPOCRLabel](https://github.com/PFCCLab/PPOCRLabel/blob/main/README.md) for finish the labeling process.
|
||||
|
||||
In terms of model, it is recommended to use the VI-layoutXLM model proposed in PP-StructureV2. It is improved based on the LayoutXLM model, removing the visual feature extraction module, and further improving the model inference speed without the significant reduction on model accuracy. For more tutorials, please refer to [VI-LayoutXLM introduction](../../doc/doc_en/algorithm_kie_vi_layoutxlm_en.md) and [KIE tutorial](../../doc/doc_en/kie_en.md).
|
||||
|
||||
|
||||
#### 2.2.2 SER + RE
|
||||
|
||||
The SER model is mainly used to identify all keys and values in the document image, and the RE model is mainly used to match all keys and values.
|
||||
|
||||
Taking the ID card scenario as an example, the key information generally includes key information such as `name`, `DOB`, etc. in the SER stage, we need to identify all questions (keys) and answers (values). The demo annotation is as follows. All keys can be annotated as `question`, and all values can be annotated as `answer`.
|
||||
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185728881-b6055e01-034c-4584-aaa6-97c9c25fb61b.png" width="500">
|
||||
</div>
|
||||
|
||||
|
||||
In the RE stage, the ID and connection information of each field need to be marked, as shown in the following figure.
|
||||
|
||||
<div align="center">
|
||||
<img src="https://user-images.githubusercontent.com/14270174/185728948-a4208013-5038-4025-9a93-0c6d51447488.png" width="500">
|
||||
</div>
|
||||
|
||||
For each textline, you need to add 'ID' and 'linking' field information. The 'ID' records the unique identifier of the textline. Different text contents in the same images cannot be repeated. The 'linking' is a list that records the connection information between different texts. If the ID of the field "name" is 0 and the ID of the field "Geoff Sample" is 1, then they all have [[0, 1]] 'linking' marks, indicating that the fields with `id=0` and `id=1` form a key value relationship (the fields such as DOB and Expires are similar, and will not be repeated here).
|
||||
|
||||
|
||||
**Note:**
|
||||
|
||||
During annotation, if value is multiple text lines, a key-value pair can be added in linking, such as `[[0, 1], [0, 2]]`.
|
||||
|
||||
In terms of data, generally speaking, for relatively fixed scenes, about **50** training images can achieve acceptable effects.
|
||||
|
||||
In terms of model, it is recommended to use the VI-layoutXLM model proposed in PP-StructureV2. It is improved based on the LayoutXLM model, removing the visual feature extraction module, and further improving the model inference speed without the significant reduction on model accuracy. For more tutorials, please refer to [VI-LayoutXLM introduction](../../doc/doc_en/algorithm_kie_vi_layoutxlm_en.md) and [KIE tutorial](../../doc/doc_en/kie_en.md).
|
||||
|
||||
|
||||
|
||||
## 3. Reference
|
||||
|
||||
|
||||
[1] Katti A R, Reisswig C, Guder C, et al. Chargrid: Towards understanding 2d documents[J]. arXiv preprint arXiv:1809.08799, 2018.
|
||||
|
||||
[2] Xu Y, Li M, Cui L, et al. Layoutlm: Pre-training of text and layout for document image understanding[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 1192-1200.
|
||||
|
||||
[3] Xu Y, Xu Y, Lv T, et al. LayoutLMv2: Multi-modal pre-training for visually-rich document understanding[J]. arXiv preprint arXiv:2012.14740, 2020.
|
||||
|
||||
[4]: Xu Y, Lv T, Cui L, et al. Layoutxlm: Multimodal pre-training for multilingual visually-rich document understanding[J]. arXiv preprint arXiv:2104.08836, 2021.
|
||||
|
||||
[5] Li Y, Qian Y, Yu Y, et al. StrucTexT: Structured Text Understanding with Multi-Modal Transformers[C]//Proceedings of the 29th ACM International Conference on Multimedia. 2021: 1912-1920.
|
||||
|
||||
[6] Liu X, Gao F, Zhang Q, et al. Graph convolution for multimodal information extraction from visually rich documents[J]. arXiv preprint arXiv:1903.11279, 2019.
|
||||
|
||||
[7] Sun H, Kuang Z, Yue X, et al. Spatial Dual-Modality Graph Reasoning for Key Information Extraction[J]. arXiv preprint arXiv:2103.14470, 2021.
|
||||
|
||||
[8] Zhang P, Xu Y, Cheng Z, et al. Trie: End-to-end text reading and information extraction for document understanding[C]//Proceedings of the 28th ACM International Conference on Multimedia. 2020: 1413-1422.
|
||||
191
ppstructure/kie/predict_kie_token_ser.py
Normal file
191
ppstructure/kie/predict_kie_token_ser.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import os
|
||||
import sys
|
||||
|
||||
__dir__ = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(__dir__)
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../..")))
|
||||
|
||||
os.environ["FLAGS_allocator_strategy"] = "auto_growth"
|
||||
|
||||
import cv2
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
import tools.infer.utility as utility
|
||||
from ppocr.data import create_operators, transform
|
||||
from ppocr.postprocess import build_post_process
|
||||
from ppocr.utils.logging import get_logger
|
||||
from ppocr.utils.visual import draw_ser_results
|
||||
from ppocr.utils.utility import get_image_file_list, check_and_read
|
||||
from ppstructure.utility import parse_args
|
||||
|
||||
from paddleocr import PaddleOCR
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class SerPredictor(object):
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
self.ocr_engine = PaddleOCR(
|
||||
use_angle_cls=args.use_angle_cls,
|
||||
det_model_dir=args.det_model_dir,
|
||||
rec_model_dir=args.rec_model_dir,
|
||||
show_log=False,
|
||||
use_gpu=args.use_gpu,
|
||||
)
|
||||
|
||||
pre_process_list = [
|
||||
{
|
||||
"VQATokenLabelEncode": {
|
||||
"algorithm": args.kie_algorithm,
|
||||
"class_path": args.ser_dict_path,
|
||||
"contains_re": False,
|
||||
"ocr_engine": self.ocr_engine,
|
||||
"order_method": args.ocr_order_method,
|
||||
}
|
||||
},
|
||||
{"VQATokenPad": {"max_seq_len": 512, "return_attention_mask": True}},
|
||||
{"VQASerTokenChunk": {"max_seq_len": 512, "return_attention_mask": True}},
|
||||
{"Resize": {"size": [224, 224]}},
|
||||
{
|
||||
"NormalizeImage": {
|
||||
"std": [58.395, 57.12, 57.375],
|
||||
"mean": [123.675, 116.28, 103.53],
|
||||
"scale": "1",
|
||||
"order": "hwc",
|
||||
}
|
||||
},
|
||||
{"ToCHWImage": None},
|
||||
{
|
||||
"KeepKeys": {
|
||||
"keep_keys": [
|
||||
"input_ids",
|
||||
"bbox",
|
||||
"attention_mask",
|
||||
"token_type_ids",
|
||||
"image",
|
||||
"labels",
|
||||
"segment_offset_id",
|
||||
"ocr_info",
|
||||
"entities",
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
postprocess_params = {
|
||||
"name": "VQASerTokenLayoutLMPostProcess",
|
||||
"class_path": args.ser_dict_path,
|
||||
}
|
||||
|
||||
self.preprocess_op = create_operators(pre_process_list, {"infer_mode": True})
|
||||
self.postprocess_op = build_post_process(postprocess_params)
|
||||
(
|
||||
self.predictor,
|
||||
self.input_tensor,
|
||||
self.output_tensors,
|
||||
self.config,
|
||||
) = utility.create_predictor(args, "ser", logger)
|
||||
|
||||
def __call__(self, img):
|
||||
ori_im = img.copy()
|
||||
data = {"image": img}
|
||||
data = transform(data, self.preprocess_op)
|
||||
if data[0] is None:
|
||||
return None, 0
|
||||
starttime = time.time()
|
||||
|
||||
for idx in range(len(data)):
|
||||
if isinstance(data[idx], np.ndarray):
|
||||
data[idx] = np.expand_dims(data[idx], axis=0)
|
||||
else:
|
||||
data[idx] = [data[idx]]
|
||||
if self.args.use_onnx:
|
||||
input_tensor = {
|
||||
name: data[idx] for idx, name in enumerate(self.input_tensor)
|
||||
}
|
||||
self.output_tensors = self.predictor.run(None, input_tensor)
|
||||
else:
|
||||
for idx in range(len(self.input_tensor)):
|
||||
self.input_tensor[idx].copy_from_cpu(data[idx])
|
||||
|
||||
self.predictor.run()
|
||||
|
||||
outputs = []
|
||||
for output_tensor in self.output_tensors:
|
||||
output = (
|
||||
output_tensor if self.args.use_onnx else output_tensor.copy_to_cpu()
|
||||
)
|
||||
outputs.append(output)
|
||||
preds = outputs[0]
|
||||
|
||||
post_result = self.postprocess_op(
|
||||
preds, segment_offset_ids=data[6], ocr_infos=data[7]
|
||||
)
|
||||
elapse = time.time() - starttime
|
||||
return post_result, data, elapse
|
||||
|
||||
|
||||
def main(args):
|
||||
image_file_list = get_image_file_list(args.image_dir)
|
||||
ser_predictor = SerPredictor(args)
|
||||
count = 0
|
||||
total_time = 0
|
||||
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
with open(
|
||||
os.path.join(args.output, "infer.txt"), mode="w", encoding="utf-8"
|
||||
) as f_w:
|
||||
for image_file in image_file_list:
|
||||
img, flag, _ = check_and_read(image_file)
|
||||
if not flag:
|
||||
img = cv2.imread(image_file)
|
||||
img = img[:, :, ::-1]
|
||||
if img is None:
|
||||
logger.info("error in loading image:{}".format(image_file))
|
||||
continue
|
||||
ser_res, _, elapse = ser_predictor(img)
|
||||
ser_res = ser_res[0]
|
||||
|
||||
res_str = "{}\t{}\n".format(
|
||||
image_file,
|
||||
json.dumps(
|
||||
{
|
||||
"ocr_info": ser_res,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
f_w.write(res_str)
|
||||
|
||||
img_res = draw_ser_results(
|
||||
image_file,
|
||||
ser_res,
|
||||
font_path=args.vis_font_path,
|
||||
)
|
||||
|
||||
img_save_path = os.path.join(args.output, os.path.basename(image_file))
|
||||
cv2.imwrite(img_save_path, img_res)
|
||||
logger.info("save vis result to {}".format(img_save_path))
|
||||
if count > 0:
|
||||
total_time += elapse
|
||||
count += 1
|
||||
logger.info("Predict time of {}: {}".format(image_file, elapse))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(parse_args())
|
||||
144
ppstructure/kie/predict_kie_token_ser_re.py
Normal file
144
ppstructure/kie/predict_kie_token_ser_re.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import os
|
||||
import sys
|
||||
|
||||
__dir__ = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(__dir__)
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../..")))
|
||||
|
||||
os.environ["FLAGS_allocator_strategy"] = "auto_growth"
|
||||
|
||||
import cv2
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
import tools.infer.utility as utility
|
||||
from tools.infer_kie_token_ser_re import make_input
|
||||
from ppocr.postprocess import build_post_process
|
||||
from ppocr.utils.logging import get_logger
|
||||
from ppocr.utils.visual import draw_ser_results, draw_re_results
|
||||
from ppocr.utils.utility import get_image_file_list, check_and_read
|
||||
from ppstructure.utility import parse_args
|
||||
from ppstructure.kie.predict_kie_token_ser import SerPredictor
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class SerRePredictor(object):
|
||||
def __init__(self, args):
|
||||
self.use_visual_backbone = args.use_visual_backbone
|
||||
self.ser_engine = SerPredictor(args)
|
||||
if args.re_model_dir is not None:
|
||||
postprocess_params = {"name": "VQAReTokenLayoutLMPostProcess"}
|
||||
self.postprocess_op = build_post_process(postprocess_params)
|
||||
(
|
||||
self.predictor,
|
||||
self.input_tensor,
|
||||
self.output_tensors,
|
||||
self.config,
|
||||
) = utility.create_predictor(args, "re", logger)
|
||||
else:
|
||||
self.predictor = None
|
||||
|
||||
def __call__(self, img):
|
||||
starttime = time.time()
|
||||
ser_results, ser_inputs, ser_elapse = self.ser_engine(img)
|
||||
if self.predictor is None:
|
||||
return ser_results, ser_elapse
|
||||
|
||||
re_input, entity_idx_dict_batch = make_input(ser_inputs, ser_results)
|
||||
if self.use_visual_backbone == False:
|
||||
re_input.pop(4)
|
||||
for idx in range(len(self.input_tensor)):
|
||||
self.input_tensor[idx].copy_from_cpu(re_input[idx])
|
||||
|
||||
self.predictor.run()
|
||||
outputs = []
|
||||
for output_tensor in self.output_tensors:
|
||||
output = output_tensor.copy_to_cpu()
|
||||
outputs.append(output)
|
||||
preds = dict(
|
||||
loss=outputs[1],
|
||||
pred_relations=outputs[2],
|
||||
hidden_states=outputs[0],
|
||||
)
|
||||
|
||||
post_result = self.postprocess_op(
|
||||
preds, ser_results=ser_results, entity_idx_dict_batch=entity_idx_dict_batch
|
||||
)
|
||||
|
||||
elapse = time.time() - starttime
|
||||
return post_result, elapse
|
||||
|
||||
|
||||
def main(args):
|
||||
image_file_list = get_image_file_list(args.image_dir)
|
||||
ser_re_predictor = SerRePredictor(args)
|
||||
count = 0
|
||||
total_time = 0
|
||||
|
||||
os.makedirs(args.output, exist_ok=True)
|
||||
with open(
|
||||
os.path.join(args.output, "infer.txt"), mode="w", encoding="utf-8"
|
||||
) as f_w:
|
||||
for image_file in image_file_list:
|
||||
img, flag, _ = check_and_read(image_file)
|
||||
if not flag:
|
||||
img = cv2.imread(image_file)
|
||||
img = img[:, :, ::-1]
|
||||
if img is None:
|
||||
logger.info("error in loading image:{}".format(image_file))
|
||||
continue
|
||||
re_res, elapse = ser_re_predictor(img)
|
||||
re_res = re_res[0]
|
||||
|
||||
res_str = "{}\t{}\n".format(
|
||||
image_file,
|
||||
json.dumps(
|
||||
{
|
||||
"ocr_info": re_res,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
f_w.write(res_str)
|
||||
if ser_re_predictor.predictor is not None:
|
||||
img_res = draw_re_results(
|
||||
image_file, re_res, font_path=args.vis_font_path
|
||||
)
|
||||
img_save_path = os.path.join(
|
||||
args.output,
|
||||
os.path.splitext(os.path.basename(image_file))[0] + "_ser_re.jpg",
|
||||
)
|
||||
else:
|
||||
img_res = draw_ser_results(
|
||||
image_file, re_res, font_path=args.vis_font_path
|
||||
)
|
||||
img_save_path = os.path.join(
|
||||
args.output,
|
||||
os.path.splitext(os.path.basename(image_file))[0] + "_ser.jpg",
|
||||
)
|
||||
|
||||
cv2.imwrite(img_save_path, img_res)
|
||||
logger.info("save vis result to {}".format(img_save_path))
|
||||
if count > 0:
|
||||
total_time += elapse
|
||||
count += 1
|
||||
logger.info("Predict time of {}: {}".format(image_file, elapse))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(parse_args())
|
||||
7
ppstructure/kie/requirements.txt
Normal file
7
ppstructure/kie/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
sentencepiece
|
||||
yacs
|
||||
seqeval
|
||||
pypandoc
|
||||
attrdict3
|
||||
python_docx
|
||||
paddlenlp==2.5.2
|
||||
259
ppstructure/kie/tools/eval_with_label_end2end.py
Normal file
259
ppstructure/kie/tools/eval_with_label_end2end.py
Normal file
@@ -0,0 +1,259 @@
|
||||
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import shapely
|
||||
from shapely.geometry import Polygon
|
||||
import numpy as np
|
||||
from collections import defaultdict
|
||||
import operator
|
||||
from rapidfuzz.distance import Levenshtein
|
||||
import argparse
|
||||
import json
|
||||
import copy
|
||||
|
||||
|
||||
def parse_ser_results_fp(fp, fp_type="gt", ignore_background=True):
|
||||
# img/zh_val_0.jpg {
|
||||
# "height": 3508,
|
||||
# "width": 2480,
|
||||
# "ocr_info": [
|
||||
# {"text": "Maribyrnong", "label": "other", "bbox": [1958, 144, 2184, 198]},
|
||||
# {"text": "CITYCOUNCIL", "label": "other", "bbox": [2052, 183, 2171, 214]},
|
||||
# ]
|
||||
assert fp_type in ["gt", "pred"]
|
||||
key = "label" if fp_type == "gt" else "pred"
|
||||
res_dict = dict()
|
||||
with open(fp, "r", encoding="utf-8") as fin:
|
||||
lines = fin.readlines()
|
||||
|
||||
for _, line in enumerate(lines):
|
||||
img_path, info = line.strip().split("\t")
|
||||
# get key
|
||||
image_name = os.path.basename(img_path)
|
||||
res_dict[image_name] = []
|
||||
# get infos
|
||||
json_info = json.loads(info)
|
||||
for single_ocr_info in json_info["ocr_info"]:
|
||||
label = single_ocr_info[key].upper()
|
||||
if label in ["O", "OTHERS", "OTHER"]:
|
||||
label = "O"
|
||||
if ignore_background and label == "O":
|
||||
continue
|
||||
single_ocr_info["label"] = label
|
||||
res_dict[image_name].append(copy.deepcopy(single_ocr_info))
|
||||
return res_dict
|
||||
|
||||
|
||||
def polygon_from_str(polygon_points):
|
||||
"""
|
||||
Create a shapely polygon object from gt or dt line.
|
||||
"""
|
||||
polygon_points = np.array(polygon_points).reshape(4, 2)
|
||||
polygon = Polygon(polygon_points).convex_hull
|
||||
return polygon
|
||||
|
||||
|
||||
def polygon_iou(poly1, poly2):
|
||||
"""
|
||||
Intersection over union between two shapely polygons.
|
||||
"""
|
||||
if not poly1.intersects(poly2): # this test is fast and can accelerate calculation
|
||||
iou = 0
|
||||
else:
|
||||
try:
|
||||
inter_area = poly1.intersection(poly2).area
|
||||
union_area = poly1.area + poly2.area - inter_area
|
||||
iou = float(inter_area) / union_area
|
||||
except shapely.geos.TopologicalError:
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
print("shapely.geos.TopologicalError occurred, iou set to 0")
|
||||
iou = 0
|
||||
return iou
|
||||
|
||||
|
||||
def ed(args, str1, str2):
|
||||
if args.ignore_space:
|
||||
str1 = str1.replace(" ", "")
|
||||
str2 = str2.replace(" ", "")
|
||||
if args.ignore_case:
|
||||
str1 = str1.lower()
|
||||
str2 = str2.lower()
|
||||
return Levenshtein.distance(str1, str2)
|
||||
|
||||
|
||||
def convert_bbox_to_polygon(bbox):
|
||||
"""
|
||||
bbox : [x1, y1, x2, y2]
|
||||
output: [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
|
||||
"""
|
||||
xmin, ymin, xmax, ymax = bbox
|
||||
poly = [[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]]
|
||||
return poly
|
||||
|
||||
|
||||
def eval_e2e(args):
|
||||
# gt
|
||||
gt_results = parse_ser_results_fp(args.gt_json_path, "gt", args.ignore_background)
|
||||
# pred
|
||||
dt_results = parse_ser_results_fp(
|
||||
args.pred_json_path, "pred", args.ignore_background
|
||||
)
|
||||
iou_thresh = args.iou_thres
|
||||
num_gt_chars = 0
|
||||
gt_count = 0
|
||||
dt_count = 0
|
||||
hit = 0
|
||||
ed_sum = 0
|
||||
|
||||
for img_name in dt_results:
|
||||
gt_info = gt_results[img_name]
|
||||
gt_count += len(gt_info)
|
||||
|
||||
dt_info = dt_results[img_name]
|
||||
dt_count += len(dt_info)
|
||||
|
||||
dt_match = [False] * len(dt_info)
|
||||
gt_match = [False] * len(gt_info)
|
||||
|
||||
all_ious = defaultdict(tuple)
|
||||
# gt: {text, label, bbox or poly}
|
||||
for index_gt, gt in enumerate(gt_info):
|
||||
if "poly" not in gt:
|
||||
gt["poly"] = convert_bbox_to_polygon(gt["bbox"])
|
||||
gt_poly = polygon_from_str(gt["poly"])
|
||||
for index_dt, dt in enumerate(dt_info):
|
||||
if "poly" not in dt:
|
||||
dt["poly"] = convert_bbox_to_polygon(dt["bbox"])
|
||||
dt_poly = polygon_from_str(dt["poly"])
|
||||
iou = polygon_iou(dt_poly, gt_poly)
|
||||
if iou >= iou_thresh:
|
||||
all_ious[(index_gt, index_dt)] = iou
|
||||
sorted_ious = sorted(all_ious.items(), key=operator.itemgetter(1), reverse=True)
|
||||
sorted_gt_dt_pairs = [item[0] for item in sorted_ious]
|
||||
|
||||
# matched gt and dt
|
||||
for gt_dt_pair in sorted_gt_dt_pairs:
|
||||
index_gt, index_dt = gt_dt_pair
|
||||
if gt_match[index_gt] == False and dt_match[index_dt] == False:
|
||||
gt_match[index_gt] = True
|
||||
dt_match[index_dt] = True
|
||||
# ocr rec results
|
||||
gt_text = gt_info[index_gt]["text"]
|
||||
dt_text = dt_info[index_dt]["text"]
|
||||
|
||||
# ser results
|
||||
gt_label = gt_info[index_gt]["label"]
|
||||
dt_label = dt_info[index_dt]["pred"]
|
||||
|
||||
if True: # ignore_masks[index_gt] == '0':
|
||||
ed_sum += ed(args, gt_text, dt_text)
|
||||
num_gt_chars += len(gt_text)
|
||||
if gt_text == dt_text:
|
||||
if args.ignore_ser_prediction or gt_label == dt_label:
|
||||
hit += 1
|
||||
|
||||
# unmatched dt
|
||||
for tindex, dt_match_flag in enumerate(dt_match):
|
||||
if dt_match_flag == False:
|
||||
dt_text = dt_info[tindex]["text"]
|
||||
gt_text = ""
|
||||
ed_sum += ed(args, dt_text, gt_text)
|
||||
|
||||
# unmatched gt
|
||||
for tindex, gt_match_flag in enumerate(gt_match):
|
||||
if gt_match_flag == False:
|
||||
dt_text = ""
|
||||
gt_text = gt_info[tindex]["text"]
|
||||
ed_sum += ed(args, gt_text, dt_text)
|
||||
num_gt_chars += len(gt_text)
|
||||
|
||||
eps = 1e-9
|
||||
print("config: ", args)
|
||||
print("hit, dt_count, gt_count", hit, dt_count, gt_count)
|
||||
precision = hit / (dt_count + eps)
|
||||
recall = hit / (gt_count + eps)
|
||||
fmeasure = 2.0 * precision * recall / (precision + recall + eps)
|
||||
avg_edit_dist_img = ed_sum / len(gt_results)
|
||||
avg_edit_dist_field = ed_sum / (gt_count + eps)
|
||||
character_acc = 1 - ed_sum / (num_gt_chars + eps)
|
||||
|
||||
print("character_acc: %.2f" % (character_acc * 100) + "%")
|
||||
print("avg_edit_dist_field: %.2f" % (avg_edit_dist_field))
|
||||
print("avg_edit_dist_img: %.2f" % (avg_edit_dist_img))
|
||||
print("precision: %.2f" % (precision * 100) + "%")
|
||||
print("recall: %.2f" % (recall * 100) + "%")
|
||||
print("fmeasure: %.2f" % (fmeasure * 100) + "%")
|
||||
|
||||
return
|
||||
|
||||
|
||||
def parse_args():
|
||||
""" """
|
||||
|
||||
def str2bool(v):
|
||||
return v.lower() in ("true", "t", "1")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
## Required parameters
|
||||
parser.add_argument(
|
||||
"--gt_json_path",
|
||||
default=None,
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pred_json_path",
|
||||
default=None,
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
|
||||
parser.add_argument("--iou_thres", default=0.5, type=float)
|
||||
|
||||
parser.add_argument(
|
||||
"--ignore_case",
|
||||
default=False,
|
||||
type=str2bool,
|
||||
help="whether to do lower case for the strs",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--ignore_space", default=True, type=str2bool, help="whether to ignore space"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--ignore_background",
|
||||
default=True,
|
||||
type=str2bool,
|
||||
help="whether to ignore other label",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--ignore_ser_prediction",
|
||||
default=False,
|
||||
type=str2bool,
|
||||
help="whether to ignore ocr pred results",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
eval_e2e(args)
|
||||
166
ppstructure/kie/tools/trans_funsd_label.py
Normal file
166
ppstructure/kie/tools/trans_funsd_label.py
Normal file
@@ -0,0 +1,166 @@
|
||||
# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import cv2
|
||||
import numpy as np
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def trans_poly_to_bbox(poly):
|
||||
x1 = np.min([p[0] for p in poly])
|
||||
x2 = np.max([p[0] for p in poly])
|
||||
y1 = np.min([p[1] for p in poly])
|
||||
y2 = np.max([p[1] for p in poly])
|
||||
return [x1, y1, x2, y2]
|
||||
|
||||
|
||||
def get_outer_poly(bbox_list):
|
||||
x1 = min([bbox[0] for bbox in bbox_list])
|
||||
y1 = min([bbox[1] for bbox in bbox_list])
|
||||
x2 = max([bbox[2] for bbox in bbox_list])
|
||||
y2 = max([bbox[3] for bbox in bbox_list])
|
||||
return [[x1, y1], [x2, y1], [x2, y2], [x1, y2]]
|
||||
|
||||
|
||||
def load_funsd_label(image_dir, anno_dir):
|
||||
imgs = os.listdir(image_dir)
|
||||
annos = os.listdir(anno_dir)
|
||||
|
||||
imgs = [img.replace(".png", "") for img in imgs]
|
||||
annos = [anno.replace(".json", "") for anno in annos]
|
||||
|
||||
fn_info_map = dict()
|
||||
for anno_fn in annos:
|
||||
res = []
|
||||
with open(os.path.join(anno_dir, anno_fn + ".json"), "r") as fin:
|
||||
infos = json.load(fin)
|
||||
infos = infos["form"]
|
||||
old_id2new_id_map = dict()
|
||||
global_new_id = 0
|
||||
for info in infos:
|
||||
if info["text"] is None:
|
||||
continue
|
||||
words = info["words"]
|
||||
if len(words) <= 0:
|
||||
continue
|
||||
word_idx = 1
|
||||
curr_bboxes = [words[0]["box"]]
|
||||
curr_texts = [words[0]["text"]]
|
||||
while word_idx < len(words):
|
||||
# switch to a new link
|
||||
if words[word_idx]["box"][0] + 10 <= words[word_idx - 1]["box"][2]:
|
||||
if len("".join(curr_texts[0])) > 0:
|
||||
res.append(
|
||||
{
|
||||
"transcription": " ".join(curr_texts),
|
||||
"label": info["label"],
|
||||
"points": get_outer_poly(curr_bboxes),
|
||||
"linking": info["linking"],
|
||||
"id": global_new_id,
|
||||
}
|
||||
)
|
||||
if info["id"] not in old_id2new_id_map:
|
||||
old_id2new_id_map[info["id"]] = []
|
||||
old_id2new_id_map[info["id"]].append(global_new_id)
|
||||
global_new_id += 1
|
||||
curr_bboxes = [words[word_idx]["box"]]
|
||||
curr_texts = [words[word_idx]["text"]]
|
||||
else:
|
||||
curr_bboxes.append(words[word_idx]["box"])
|
||||
curr_texts.append(words[word_idx]["text"])
|
||||
word_idx += 1
|
||||
if len("".join(curr_texts[0])) > 0:
|
||||
res.append(
|
||||
{
|
||||
"transcription": " ".join(curr_texts),
|
||||
"label": info["label"],
|
||||
"points": get_outer_poly(curr_bboxes),
|
||||
"linking": info["linking"],
|
||||
"id": global_new_id,
|
||||
}
|
||||
)
|
||||
if info["id"] not in old_id2new_id_map:
|
||||
old_id2new_id_map[info["id"]] = []
|
||||
old_id2new_id_map[info["id"]].append(global_new_id)
|
||||
global_new_id += 1
|
||||
res = sorted(res, key=lambda r: (r["points"][0][1], r["points"][0][0]))
|
||||
for i in range(len(res) - 1):
|
||||
for j in range(i, 0, -1):
|
||||
if abs(
|
||||
res[j + 1]["points"][0][1] - res[j]["points"][0][1]
|
||||
) < 20 and (res[j + 1]["points"][0][0] < res[j]["points"][0][0]):
|
||||
tmp = deepcopy(res[j])
|
||||
res[j] = deepcopy(res[j + 1])
|
||||
res[j + 1] = deepcopy(tmp)
|
||||
else:
|
||||
break
|
||||
# re-generate unique ids
|
||||
for idx, r in enumerate(res):
|
||||
new_links = []
|
||||
for link in r["linking"]:
|
||||
# illegal links will be removed
|
||||
if (
|
||||
link[0] not in old_id2new_id_map
|
||||
or link[1] not in old_id2new_id_map
|
||||
):
|
||||
continue
|
||||
for src in old_id2new_id_map[link[0]]:
|
||||
for dst in old_id2new_id_map[link[1]]:
|
||||
new_links.append([src, dst])
|
||||
res[idx]["linking"] = deepcopy(new_links)
|
||||
|
||||
fn_info_map[anno_fn] = res
|
||||
|
||||
return fn_info_map
|
||||
|
||||
|
||||
def main():
|
||||
test_image_dir = "train_data/FUNSD/testing_data/images/"
|
||||
test_anno_dir = "train_data/FUNSD/testing_data/annotations/"
|
||||
test_output_dir = "train_data/FUNSD/test.json"
|
||||
|
||||
fn_info_map = load_funsd_label(test_image_dir, test_anno_dir)
|
||||
with open(test_output_dir, "w") as fout:
|
||||
for fn in fn_info_map:
|
||||
fout.write(
|
||||
fn
|
||||
+ ".png"
|
||||
+ "\t"
|
||||
+ json.dumps(fn_info_map[fn], ensure_ascii=False)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
train_image_dir = "train_data/FUNSD/training_data/images/"
|
||||
train_anno_dir = "train_data/FUNSD/training_data/annotations/"
|
||||
train_output_dir = "train_data/FUNSD/train.json"
|
||||
|
||||
fn_info_map = load_funsd_label(train_image_dir, train_anno_dir)
|
||||
with open(train_output_dir, "w") as fout:
|
||||
for fn in fn_info_map:
|
||||
fout.write(
|
||||
fn
|
||||
+ ".png"
|
||||
+ "\t"
|
||||
+ json.dumps(fn_info_map[fn], ensure_ascii=False)
|
||||
+ "\n"
|
||||
)
|
||||
print("====ok====")
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
64
ppstructure/kie/tools/trans_xfun_data.py
Normal file
64
ppstructure/kie/tools/trans_xfun_data.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def transfer_xfun_data(json_path=None, output_file=None):
|
||||
with open(json_path, "r", encoding="utf-8") as fin:
|
||||
lines = fin.readlines()
|
||||
|
||||
json_info = json.loads(lines[0])
|
||||
documents = json_info["documents"]
|
||||
with open(output_file, "w", encoding="utf-8") as fout:
|
||||
for idx, document in enumerate(documents):
|
||||
label_info = []
|
||||
img_info = document["img"]
|
||||
document = document["document"]
|
||||
image_path = img_info["fname"]
|
||||
|
||||
for doc in document:
|
||||
x1, y1, x2, y2 = doc["box"]
|
||||
points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]]
|
||||
label_info.append(
|
||||
{
|
||||
"transcription": doc["text"],
|
||||
"label": doc["label"],
|
||||
"points": points,
|
||||
"id": doc["id"],
|
||||
"linking": doc["linking"],
|
||||
}
|
||||
)
|
||||
|
||||
fout.write(
|
||||
image_path + "\t" + json.dumps(label_info, ensure_ascii=False) + "\n"
|
||||
)
|
||||
|
||||
print("===ok====")
|
||||
|
||||
|
||||
def parser_args():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="args for paddleserving")
|
||||
parser.add_argument(
|
||||
"--ori_gt_path", type=str, required=True, help="origin xfun gt path"
|
||||
)
|
||||
parser.add_argument("--output_path", type=str, required=True, help="path to save")
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
args = parser_args()
|
||||
transfer_xfun_data(args.ori_gt_path, args.output_path)
|
||||
Reference in New Issue
Block a user