Skip to content

Commit 9dd0526

Browse files
authored
Merge pull request #756 from Evezerest/master
Add Report_Recognition_and_Analysis
2 parents 0354862 + 45d64ea commit 9dd0526

File tree

7 files changed

+384
-0
lines changed

7 files changed

+384
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# 财报识别与关键字段抽取
2+
3+
## 1. 案例背景与环境搭建
4+
5+
当前,诸多投资机构都通过研报的形式给出对于股票、基金以及行业的判断,让大众了解热点方向、龙头公司等各类信息。然而,分析和学习研报往往花费大量时间,研报数目的与日俱增也使得自动对研报分析和信息抽取的诉求不断提高。
6+
7+
本案例以文档场景下的文字识别和命名实体识别为串联任务,对研报实体进行词频统计,使用[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)[PaddleNLP](https://github.com/PaddlePaddle/PaddleNLP)两个开发套件,迅速搭建一套文字与命名实体识别统计系统。
8+
9+
### 1.1 安装PaddleOCR
10+
11+
#### 1.1.1 项目克隆
12+
13+
```
14+
【推荐】git clone https://github.com/PaddlePaddle/PaddleOCR
15+
```
16+
17+
如果因为网络问题无法pull成功,也可选择使用码云上的托管:
18+
19+
```
20+
git clone https://gitee.com/paddlepaddle/PaddleOCR
21+
```
22+
23+
注:码云托管代码可能无法实时同步本github项目更新,存在3~5天延时,请优先使用推荐方式。
24+
25+
#### 1.1.2 安装第三方库
26+
27+
```bash
28+
cd PaddleOCR
29+
pip install -r requirements.txt
30+
```
31+
32+
### 1.2 安装PaddleNLP whl包
33+
34+
```
35+
pip install --upgrade paddlenlp
36+
```
37+
38+
39+
40+
## 2. 文档场景文字识别微调
41+
42+
### 2.1 数据准备
43+
44+
#### 2.2.1 研报数据获取
45+
46+
获取研报的渠道有很多,比如艾瑞网、199IT以及各类国家机构的公开数据。这里使用东方财富网,研报覆盖范围广、数量多、下载方便。
47+
48+
#### 2.1.2 数据格式转换
49+
50+
研报数据通常以pdf格式存在,而深度学习模型训练需要图像数据,因此需要将pdf数据转化为jpeg数据。
51+
52+
这里使用fitz包读入pdf文件之后按页保存为jpeg格式,由于过大的图像分辨率会使得模型预测过程中内存消耗严重,因此控制输出图像格式的分辨率也较为重要,通过`fitz.Matrix(zoom_x, zoom_y)`即可传入在x和y方向的缩放系数。
53+
54+
最终输出的图片格式分辨率在1k-2k之间。
55+
56+
#### 2.1.3 数据标注
57+
58+
开源标注工具中,PPOCRLabel是首款针对OCR场景的半自动标注工具,其内嵌PP-OCR系列模型,通过自动标注和重新识别两大核心功能,能使标注效率提升60-80%。
59+
60+
实际使用流程如下:首先打开数据文件夹,点击自动标注,PPOCRLabel会利用PP-OCR模型对所有图片进行预标注,并在软件右侧展示模型自动标注的结果。然后对机器标注的结果进行修正,对于漏检的区域绘制检测框后可以点击重新识别。最后导出标记结果和裁剪的图像。
61+
62+
检测标注数据文件是一个txt,格式如下所示,左侧为图像的文件名,右侧是图像中文本框对应的文字与坐标,两组字符的中间用 `\t` 分隔,行间字符用 `\n` 间隔
63+
64+
```
65+
"图像文件名 json.dumps编码的图像标注信息"
66+
ch4_test_images/img_61.jpg [{"transcription": "MASA", "points": [[310, 104], [416, 141], [418, 216], [312, 179]]}, {...}]
67+
```
68+
69+
### 2.2 模型选型
70+
71+
![ppocr_framework](./imgs/ppocr_framework.png)
72+
73+
PP-OCR是一个实用的超轻量OCR系统。主要由DB文本检测、检测框矫正和CRNN文本识别三部分组成。该系统从骨干网络选择和调整、预测头部的设计、数据增强、学习率变换策略、正则化参数选择、预训练模型使用以及模型自动裁剪量化8个方面,采用19个有效策略,对各个模块的模型进行效果调优和瘦身。更多细节请参考PP-OCR技术方案 https://arxiv.org/abs/2009.09941
74+
75+
### 2.3 模型训练与评估
76+
77+
#### 2.3.1 预训练模型下载
78+
79+
当数据按照PP-OCR格式整理好后,开始训练前首先需要下载预训练模型
80+
81+
在PP-OCR模型库下载PP-OCR mobile系列的检测模型到`./pretrain_models`文件夹中并解压
82+
83+
```bash
84+
wget -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_train.tar
85+
cd ./pretrain_models/ && tar xf ch_ppocr_mobile_v2.0_det_train.tar
86+
```
87+
88+
得到的结果如下
89+
90+
```
91+
./pretrain_models/ch_ppocr_mobile_v2.0_det_train/
92+
└─ best_accuracy.pdopt
93+
└─ best_accuracy.pdparams
94+
└─ best_accuracy.states
95+
```
96+
97+
#### 2.3.2 修改配置文件
98+
99+
配置文件是模型训练过程中必不可少的部分,它是由模型参数和训练过程参数组成的集合。对`ch_det_mv3_db_v2.0.yml`配置文件的修改包括预训练模型路径、数据集路径两部分。通过`Global.pretrained_model`指定配置文件中的预训练模型路径。修改配置文件中的`data_dir, label_file_list`为数据所在路径。
100+
101+
```
102+
Global:
103+
└─pretrained_model:./pretrain_models/ch_ppocr_mobile_v2.0_det_train/best_accuracy
104+
Train:
105+
└─dataset
106+
└─data_dir:path/to/your/dataset
107+
└─label_file_list:path/to/your/dataset/label.txt
108+
Eval:
109+
└─dataset
110+
└─data_dir:path/to/your/dataset
111+
└─label_file_list:path/to/your/dataset/label.txt
112+
```
113+
114+
**注意:**
115+
116+
- 训练程序在读取数据时,读取的文件路径为`data_dir路径 + label.txt中的文件名`,需要注意组合后的路径与图片路径相同。
117+
118+
- 配置文件修改可通过上述方式直接更改yml,也可通过在下方启动训练命令中指定`-o Global.pretrained_model`实现
119+
120+
#### 2.3.3 启动训练
121+
122+
训练平台使用AI Studio,通过 `-c` 选择与模型相同的配置文件`ch_det_mv3_db_v2.0.yml`
123+
124+
```bash
125+
# 单机单卡训练
126+
python3 tools/train.py -c ./configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml
127+
```
128+
129+
此时程序会在ouput文件夹中输出配置文件 `config.yml`、最优模型 `best_accuracy.*`、最新模型 `latest.*` 和训练日志 `train.log`
130+
131+
#### 2.3.4 断点训练
132+
133+
当程序中值后,重新启动训练可通过`Global.checkpoints`读入之前训练的权重
134+
135+
```
136+
python3 tools/train.py -c ./configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.checkpoints=./output/ch_db_mv3/latest
137+
```
138+
139+
**注意**`Global.checkpoints`的优先级高于`Global.pretrained_model`的优先级,即同时指定两个参数时,优先加载`Global.checkpoints`指定的模型,如果`Global.checkpoints`指定的模型路径有误,会加载`Global.pretrained_model`指定的模型。
140+
141+
#### 2.3.5 测试集评估
142+
143+
PaddleOCR计算三个OCR检测相关的指标,分别是:Precision、Recall、H-mean(F-Score)。
144+
145+
训练中模型参数默认保存在`Global.save_model_dir`目录下。在评估指标时,需要设置`Global.checkpoints`指向保存的参数文件。
146+
147+
```shell
148+
# 原始模型
149+
python3 tools/eval.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.checkpoints="./pretrain_models/ch_ppocr_mobile_v2.0_det_train/best_accuracy"
150+
151+
# Finetune后模型
152+
python3 tools/eval.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.checkpoints="./output/ch_db_mv3/best_accuracy"
153+
```
154+
155+
获得结果如下,经过finetune后的模型在综合指标H-mean上比原先提升3%左右。
156+
157+
| Model | Precision | Recall | H-mean |
158+
| ---------------------- | --------- | ------ | ------ |
159+
| PP-OCR mobile | 0.770 | 0.890 | 0.826 |
160+
| PP-OCR mobile finetune | 0.833 | 0.882 | 0.856 |
161+
162+
### 2.4 结果可视化
163+
164+
首先将训练模型转换为inference模型,加载配置文件`ch_det_mv3_db_v2.0.yml`,从`output/ch_db_mv3`目录下加载`best_accuracy`模型,inference模型保存在`./output/ch_db_mv3_inference`目录下
165+
166+
```python
167+
python3 tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model="./output/ch_db_mv3/best_accuracy" Global.save_inference_dir="./output/ch_db_mv3_inference/"
168+
```
169+
170+
然后在实例化PaddleOCR时通过参数`det_model_dir`指定转化后的模型位置
171+
172+
```python
173+
from paddleocr import PaddleOCR, draw_ocr
174+
175+
ocr = PaddleOCR(det_model_dir='./output/ch_db_mv3_inference/inference',
176+
use_angle_cls=True)
177+
img_path = './train_data/Research_val/85_11.jpeg'
178+
result = ocr.ocr(img_path, cls=True)
179+
for line in result:
180+
print(line)
181+
182+
# 显示结果
183+
from PIL import Image
184+
185+
image = Image.open(img_path).convert('RGB')
186+
boxes = [line[0] for line in result]
187+
txts = [line[1][0] for line in result]
188+
scores = [line[1][1] for line in result]
189+
im_show = draw_ocr(image, boxes, txts, scores, font_path='./doc/fonts/simfang.ttf')
190+
im_show = Image.fromarray(im_show)
191+
im_show.save('result.jpg')
192+
```
193+
194+
结果图片
195+
196+
![result](./imgs/result.jpeg)
197+
198+
199+
200+
## 3. 命名实体识别
201+
202+
什么是实体?*实体,可以认为是某一个概念的实例,*
203+
204+
*命名实体识别(Named Entities Recognition,NER),就是识别这些实体指称的边界和类别。主要关注人名、地名和组织机构名这三类专有名词的识别方法。*
205+
206+
词法分析任务即可获得句子中的实体。该任务的输入是一个字符串(句子),输出是句子中的词边界和词性、实体类别。其中能够识别的标签和对应含义如下表所示:
207+
208+
| 标签 | 含义 | 标签 | 含义 | 标签 | 含义 | 标签 | 含义 |
209+
| ---- | -------- | ---- | -------- | ---- | -------- | ---- | -------- |
210+
| n | 普通名词 | f | 方位名词 | s | 处所名词 | t | 时间 |
211+
| nr | 人名 | ns | 地名 | nt | 机构名 | nw | 作品名 |
212+
| nz | 其他专名 | v | 普通动词 | vd | 动副词 | vn | 名动词 |
213+
| a | 形容词 | ad | 副形词 | an | 名形词 | d | 副词 |
214+
| m | 数量词 | q | 量词 | r | 代词 | p | 介词 |
215+
| c | 连词 | u | 助词 | xc | 其他虚词 | w | 标点符号 |
216+
| PER | 人名 | LOC | 地名 | ORG | 机构名 | TIME | 时间 |
217+
218+
用户可以使用PaddleNLP提供的Taskflow工具来对输入的文本进行一键分词,具体使用方法如下,其中 `segs` 部分即为对 `text` 分词后的结果,`tags` 即为 `segs` 中每个分词对应的标签。对于其中的机构名,可以根据标签 `ORG``nt` 抽出。
219+
220+
```python
221+
from paddlenlp import Taskflow
222+
223+
lac = Taskflow("lexical_analysis")
224+
lac("LAC是个优秀的分词工具")
225+
'''
226+
[{'text': 'LAC是个优秀的分词工具', 'segs': ['LAC', '是', '个', '优秀', '的', '分词', '工具'], 'tags': ['nz', 'v', 'q', 'a', 'u', 'n', 'n']}]
227+
'''
228+
229+
lac(["LAC是个优秀的分词工具", "三亚是一个美丽的城市"])
230+
'''
231+
[{'text': 'LAC是个优秀的分词工具', 'segs': ['LAC', '是', '个', '优秀', '的', '分词', '工具'], 'tags': ['nz', 'v', 'q', 'a', 'u', 'n', 'n']},
232+
{'text': '三亚是一个美丽的城市', 'segs': ['三亚', '是', '一个', '美丽', '的', '城市'], 'tags': ['LOC', 'v', 'm', 'a', 'u', 'n']}
233+
]
234+
'''
235+
```
236+
237+
关于词法分析的详细说明文档可以参考 [此处](https://github.com/PaddlePaddle/PaddleNLP/tree/develop/examples/lexical_analysis) ,其中包含在自定义数据集上的训练、评估和导出。
238+
239+
240+
241+
## 4. Pipeline
242+
243+
<div align="center">
244+
<img src="./imgs/pipeline.png" width="800"/>
245+
</div>
246+
247+
248+
整个系统包含一下流程:首先对pdf格式的研报拆分为图片格式,然后对每张图片进行ocr,得到结果后输入LAC分词工具提取其中出现的机构名,最后统计同一个pdf研报下机构名出现的频率,得到当前研报主要关注的机构,批量统计多个研报后即可得到当前主要研究的热点领域和机构等。代码运行方法如下
249+
250+
首先将数据放置在`./Research` 文件夹下,运行下方代码将pdf切分为图片
251+
252+
```python
253+
python splitPDF.py
254+
```
255+
256+
然后运行 `test.py` 完成OCR与LAC以及词频统计,得到示意图如下图所示
257+
258+
```
259+
python DocRec.py
260+
```
261+
262+
<div align="center">
263+
<img src="./imgs/img.png" width="1000"/>
264+
</div>
265+
266+
267+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# ocr识别
2+
from paddleocr import PaddleOCR, draw_ocr
3+
from paddlenlp import Taskflow
4+
from collections import Counter
5+
import matplotlib.pyplot as plt
6+
7+
import matplotlib, os
8+
9+
10+
# 命名实体识别与词频统计
11+
12+
def deletaNum(doc):
13+
return [i for i in doc if len(i) > 1]
14+
15+
16+
def LAC(lac, res_list): # 根据结果筛选
17+
doc = [text[1][0] for res in res_list for text in res]
18+
19+
doc = deletaNum(doc)
20+
# print('\n\ndoc is ',doc)
21+
lac_dic = lac(doc)
22+
enti = []
23+
for la in lac_dic:
24+
ent = []
25+
for l in la:
26+
if l[1] in ['nt', 'ORG']:
27+
ent.append(l[0])
28+
enti += ent
29+
30+
return enti
31+
32+
33+
def PlotHist(counter):
34+
matplotlib.rc("font", family='FZHuaLi-M14S')
35+
36+
cnt = {}
37+
for key in counter.keys():
38+
if counter[key] >= 10:
39+
cnt[key] = counter[key]
40+
print(cnt)
41+
plt.figure(figsize=(10, 5))
42+
plt.bar(range(len(cnt)), cnt.values(), tick_label=list(cnt.keys())) # , orientation="horizontal"
43+
# for a, b in zip(x_list, y_list):
44+
# plt.text(a, b + 0.05, '%.0f' % b, ha='center', va='bottom', fontsize=10)
45+
plt.xticks(rotation=45)
46+
plt.savefig('./img.png')
47+
plt.show()
48+
49+
50+
if __name__ == '__main__':
51+
# 模型路径下必须含有model和params文件
52+
ocr = PaddleOCR(det_model_dir='./PaddleOCR/output/ch_db_mv3_inference/inference',
53+
use_angle_cls=True)
54+
lac = Taskflow("pos_tagging")
55+
56+
enti_list = []
57+
pdfFolder = './ResearchReport'
58+
for p in os.listdir(pdfFolder):
59+
if os.path.isdir(os.path.join(pdfFolder, p)):
60+
print('Processing folder:', p)
61+
imgPath = pdfFolder + '/' + p
62+
res_list = []
63+
for i in os.listdir(imgPath):
64+
img_path = os.path.join(imgPath, i)
65+
result = ocr.ocr(img_path, cls=True)
66+
res_list.append(result)
67+
68+
enti = LAC(lac, res_list)
69+
enti_list += enti
70+
71+
counter = Counter(enti_list)
72+
print('Entity results:', counter)
73+
74+
PlotHist(counter)
75+
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# pdf切分
2+
import os, fitz, time
3+
4+
def pdf2png(pdfPath, imgPath, zoom_x=2, zoom_y=2, rotation_angle=0):
5+
'''
6+
# 将PDF转化为图片
7+
pdfPath pdf文件的路径
8+
imgPath 图像要保存的文件夹
9+
zoom_x x方向的缩放系数
10+
zoom_y y方向的缩放系数
11+
rotation_angle 旋转角度
12+
互联网行业《先进无线技术应用情况调研》全球版:借助5G和Wi-Fi6加速企业创新和转型 6.png
13+
zoom_x=5 2977x4210 885kb 0.256
14+
zoom_x=2 1191x1684 290kb 0.06s 4800s
15+
'''
16+
17+
time_start = time.time()
18+
# 打开PDF文件
19+
pdf = fitz.open(pdfPath)
20+
# 逐页读取PDF
21+
for pg in range(0, pdf.pageCount):
22+
page = pdf[pg]
23+
# 设置缩放和旋转系数
24+
trans = fitz.Matrix(zoom_x, zoom_y) #.preRotate(rotation_angle)
25+
pm = page.getPixmap(matrix=trans, alpha=False)
26+
27+
if pm.width>2000 or pm.height>2000:
28+
pm = page.getPixmap(matrix=fitz.Matrix(1, 1), alpha=False)
29+
pm.writePNG(imgPath + str(pg) + ".jpeg")
30+
pdf.close()
31+
time_end = time.time()
32+
time_cost = time_end - time_start
33+
print('totally cost: {}, page: {}, each page cost: {}'.format(time_cost, pg+1, time_cost/(pg+1)))
34+
35+
if __name__ == '__main__':
36+
pdfFolder = 'ResearchReport'
37+
for p in os.listdir(pdfFolder):
38+
pdfPath = pdfFolder+'/'+p
39+
imgPath = pdfFolder+'/'+os.path.basename(p)[:-4]+'/'
40+
print(imgPath)
41+
os.mkdir(imgPath)
42+
pdf2png(pdfPath, imgPath)
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)