物流快递单信息抽取-实体抽取

   日期:2023-09-03     浏览:4    

1. 项目概述

本项目主要介绍如何使用飞桨自然语言处理开发库PaddleNLP完成快递单信息抽取:从用户提供的快递单中,抽取姓名、电话、省、市、区、详细地址等内容,形成结构化信息,如 图1 所示,辅助物流行业从业者进行有效信息的提取,从而降低客户填单的成本。


图1:快递单信息收取示意


技术难点

从物流信息中抽取想要的关键信息,实际上是自然语言处理中的实体抽取任务,这类任务在产业应用时通常会面临如下技术难点:

  • 实体类别多,任务复杂度高

同一名词可能对应多个实体类别,如“蜗牛”。
不少实体词与普通用语相同,尤其作品名。
此外,实体类别多,任务复杂度较高,如 图2 列举了部分实体类型。实际的实体类型依业务决定。


图2:实体示例


  • 实体词往往稀疏低频

人名在语料出现频次高,但不同人名出现频次有限
大多垂类实体词在通用语料出现频次低。

  • 特定业务场景中标注数据集较少,需要一定的成本标注,如果数据集质量差或样本量过少,精度会比较低。

2. 方案设计

2.1 模型选取

针对实体信息抽取技术,有如下三种方案:

  • 符号主义 —— 字符串匹配
    需要构建词典,穷举所有地址、姓名等信息,但无法发现新词、简称等。

  • 统计语言模型(Word-based Generative Model)- n-gram概率/最大概率路径
    需要构建词典及词频,但无法发现新词、简称等。

  • 序列标注(Character-based Discriminative Model)
    需要标注数据,效果好;    有两类实现方法:一是基于统计的模型:HMM、MEMM、CRF,这类方法需要关注特征工程;二是深度学习方法:RNN、LSTM、GRU、CRF、RNN+CRF…

本案例采用序列标注的方式,更加灵活、通用。

我们首先要定义好需要抽取哪些字段。

比如现在拿到一个快递单,可以作为我们的模型输入,例如“张三18625584663广东省深圳市南山区学府路东百度国际大厦”,那么序列标注模型的目的就是识别出其中的“张三”为人名(用符号 P 表示),“18625584663”为电话名(用符号 T 表示),“广东省深圳市南山区百度国际大厦”分别是 1-4 级的地址(分别用 A1~A4 表示,可以释义为省、市、区、街道)。

这是一个典型的命名实体识别(Named Entity Recognition,NER)场景,各实体类型及相应符号表示见下表:

抽取实体/字段符号抽取结果
姓名P张三
电话T18625584663
A1广东省
A2深圳市
A3南山区
详细地址A4百度国际大厦

在序列标注任务中,一般会定义一个标签集合,来表示所以可能取到的预测结果。在本案例中,针对需要被抽取的“姓名、电话、省、市、区、详细地址”等实体,标签集合可以定义为:

label = {P-B, P-I, T-B, T-I, A1-B, A1-I, A2-B, A2-I, A3-B, A3-I, A4-B, A4-I, O}

每个标签的定义分别为:

标签定义
P-B姓名起始位置
P-I姓名中间位置或结束位置
T-B电话起始位置
T-I电话中间位置或结束位置
A1-B省份起始位置
A1-I省份中间位置或结束位置
A2-B城市起始位置
A2-I城市中间位置或结束位置
A3-B县区起始位置
A3-I县区中间位置或结束位置
A4-B详细地址起始位置
A4-I详细地址中间位置或结束位置
O无关字符

注意每个标签的结果只有 B、I、O 三种,这种标签的定义方式叫做 BIO 体系,也有稍麻烦一点的 BIESO 体系,这里不做展开。其中 B 表示一个标签类别的开头,比如 P-B 指的是姓名的开头;相应的,I 表示一个标签的延续。

对于句子“张三18625584663广东省深圳市南山区百度国际大厦”,每个汉字及对应标签为:


图3:数据集标注示例


注意到“张“,”三”在这里表示成了“P-B” 和 “P-I”,“P-B”和“P-I”合并成“P” 这个标签。这样重新组合后可以得到以下信息抽取结果:

张三18625584663广东省深圳市南山区百度国际大厦
PTA1A2A3A4

2.2 模型介绍

2.2.1 循环神经网络 - 门控循环单元(GRU,Gate Recurrent Unit)

BIGRU是一种经典的循环神经网络(RNN,Recurrent Neural Network),用于对句子等序列信息进行建模。这里我们重点解释下其概念和相关原理。一个 RNN 的示意图如下所示,


图4:RNN示意图


左边是原始的 RNN,可以看到绿色的点代码输入 x,红色的点代表输出 y,中间的蓝色是 RNN 模型部分。橙色的箭头由自身指向自身,表示 RNN 的输入来自于上时刻的输出,这也是为什么名字中带有循环(Recurrent)这个词。

右边是按照时间序列展开的示意图,注意到蓝色的 RNN 模块是同一个,只不过在不同的时刻复用了。这时候能够清晰地表示序列标注模型的输入输出。

GRU为了解决长期记忆和反向传播中梯度问题而提出来的,和LSTM一样能够有效对长序列建模,且GRU训练效率更高。

2.2.2 条件随机场(CRF,Conditional Random Fields)

长句子的问题解决了,序列标注任务的另外一个问题也亟待解决,即标签之间的依赖性。举个例子,我们预测的标签一般不会出现 P-B,T-I 并列的情况,因为这样的标签不合理,也无法解析。无论是 RNN 还是 LSTM 都只能尽量不出现,却无法从原理上避免这个问题。下面要提到的条件随机场(CRF,Conditional Random Field)却很好的解决了这个问题。

条件随机场这个模型属于概率图模型中的无向图模型,这里我们不做展开,只直观解释下该模型背后考量的思想。一个经典的链式 CRF 如下图所示,


图5:CRF示意图


CRF 本质是一个无向图,其中绿色点表示输入,红色点表示输出。点与点之间的边可以分成两类,一类是 xxyy 之间的连线,表示其相关性;另一类是相邻时刻的 yy 之间的相关性。也就是说,在预测某时刻 yy 时,同时要考虑相邻的标签解决。当 CRF 模型收敛时,就会学到类似 P-B 和 T-I 作为相邻标签的概率非常低。

2.2.3 预训练模型 (Ernie、Ernie-Gram)

除了GRU+CRF方案外,我们也可以使用预训练模型,将序列信息抽取问题,建模成字符级分类问题。这里我们采用强大的语义模型ERNIE,完成字符级分类任务。



图6:ERNIE模型示意图


2.3 评估指标

信息抽取的通用评测指标:Precision、Recall、F1值。
例子:希望抽取10个信息,实际抽取12个,其中有9个是对的


图7:评估指标


3. 代码实现

3.1 数据准备

采取的是快递单数据集,这份数据除姓名、人名、地名之外,无其他信息。采用BIO方式标注训练集,由于仅含实体信息,所以数据集无“O”(非实体)标识。训练集中除第一行是 text_a\tlabel,后面的每行数据都是由两列组成,以制表符分隔,第一列是 utf-8 编码的中文文本,以 \002 分割,第二列是对应序列标注的结果,以 \002 分割。 !wc -l ./express_ner/train.txt
!wc -l ./express_ner/dev.txt
!wc -l  ./express_ner/test.txt

!head -10 ./express_ner/train.txt# 实体标签
!cat ./conf/tag.dic

3.2 模型组网、训练、评估、预测

本项目基于PaddleNLP NER example的代码进行修改,分别基于基本组网单元GRU、CRF和预训练模型,实现了多个方案。

我们以快递单信息抽取为例,介绍如何使用PaddleNLP搭建网络。

  • 方案1: RNN类网络BiGRU、CRF、ViterbiDecoder。

  • 方案2: Ernie

  • 方案3:Ernie+CRF

  • 方案4:Ernie-Gram

  • 方案5:Ernie-Gram+CRFAI Studio平台默认安装了Paddle和PaddleNLP,并定期更新版本。 如需手动更新Paddle,可参考飞桨安装说明,安装相应环境下最新版飞桨框架。

使用如下命令确保安装最新版PaddleNLP:!pip install --upgrade paddlenlp -i https://pypi.org/simple

GRU + CRF

!python run_bigru_crf.py

‘’’
eval begin…
step 1/6 - loss: 0.0000e+00 - precision: 0.9896 - recall: 0.9948 - f1: 0.9922 - 121ms/step
step 2/6 - loss: 0.0000e+00 - precision: 0.9896 - recall: 0.9948 - f1: 0.9922 - 125ms/step
step 3/6 - loss: 20.9767 - precision: 0.9861 - recall: 0.9895 - f1: 0.9878 - 123ms/step
step 4/6 - loss: 0.0000e+00 - precision: 0.9805 - recall: 0.9869 - f1: 0.9837 - 123ms/step
step 5/6 - loss: 0.0000e+00 - precision: 0.9782 - recall: 0.9843 - f1: 0.9812 - 122ms/step
step 6/6 - loss: 0.0000e+00 - precision: 0.9740 - recall: 0.9791 - f1: 0.9765 - 123ms/step
eval samples: 192
‘’’

Ernie

!python run_ernie.py

‘’’
epoch:8 - step:72 - loss: 0.038532
eval precision: 0.974124 - recall: 0.981497 - f1: 0.977796
epoch:9 - step:73 - loss: 0.031000
epoch:9 - step:74 - loss: 0.033214
epoch:9 - step:75 - loss: 0.034606
epoch:9 - step:76 - loss: 0.038763
epoch:9 - step:77 - loss: 0.033273
epoch:9 - step:78 - loss: 0.031058
epoch:9 - step:79 - loss: 0.028151
epoch:9 - step:80 - loss: 0.030707
eval precision: 0.976608 - recall: 0.983179 - f1: 0.979883
‘’’

ErnieGram

!python run_ernie_gram.py

‘’’
epoch:8 - step:72 - loss: 0.030066
eval precision: 0.990764 - recall: 0.992431 - f1: 0.991597
epoch:9 - step:73 - loss: 0.023607
epoch:9 - step:74 - loss: 0.023326
epoch:9 - step:75 - loss: 0.022730
epoch:9 - step:76 - loss: 0.033801
epoch:9 - step:77 - loss: 0.026398
epoch:9 - step:78 - loss: 0.026028
epoch:9 - step:79 - loss: 0.021799
epoch:9 - step:80 - loss: 0.025259
eval precision: 0.990764 - recall: 0.992431 - f1: 0.991597
‘’’

ERNIE + CRF

!python run_ernie_crf.py

‘’’
[eval] Precision: 0.975793 - Recall: 0.983179 - F1: 0.979472
[TRAIN] Epoch:9 - Step:73 - Loss: 0.111980
[TRAIN] Epoch:9 - Step:74 - Loss: 0.152896
[TRAIN] Epoch:9 - Step:75 - Loss: 0.274099
[TRAIN] Epoch:9 - Step:76 - Loss: 0.294602
[TRAIN] Epoch:9 - Step:77 - Loss: 0.231813
[TRAIN] Epoch:9 - Step:78 - Loss: 0.225045
[TRAIN] Epoch:9 - Step:79 - Loss: 0.180734
[TRAIN] Epoch:9 - Step:80 - Loss: 0.171899
[eval] Precision: 0.975000 - Recall: 0.984020 - F1: 0.979489
‘’’

ErnieGram + CRF

!python run_erniegram_crf.py

‘’’
[eval] Precision: 0.992437 - Recall: 0.993272 - F1: 0.992854
[TRAIN] Epoch:9 - Step:73 - Loss: 0.100207
[TRAIN] Epoch:9 - Step:74 - Loss: 0.189141
[TRAIN] Epoch:9 - Step:75 - Loss: 0.051093
[TRAIN] Epoch:9 - Step:76 - Loss: 0.230366
[TRAIN] Epoch:9 - Step:77 - Loss: 0.271885
[TRAIN] Epoch:9 - Step:78 - Loss: 0.342371
[TRAIN] Epoch:9 - Step:79 - Loss: 0.050146
[TRAIN] Epoch:9 - Step:80 - Loss: 0.257951
[eval] Precision: 0.990764 - Recall: 0.992431 - F1: 0.991597
‘’’

4. 部署

4.1 动转静导出模型

import os
import paddle
import paddlenlp
from utils import load_dict
label_vocab = load_dict(’./conf/tag.dic’)
model = paddlenlp.transformers.ErnieForTokenClassification.from_pretrained(“ernie-1.0”, num_classes=len(label_vocab))

params_path = ‘./ernie_result/model_80.pdparams’
state_dict = paddle.load(params_path)
model.set_dict(state_dict)
print(“Loaded parameters from %s” % params_path)

model.eval()
model = paddle.jit.to_static(
model,
input_spec=[
paddle.static.InputSpec(
shape=[None, None], dtype=“int64”),  # input_ids
paddle.static.InputSpec(
shape=[None, None], dtype=“int64”)  # segment_ids
])

output_path = ‘./ernie_result’
save_path = os.path.join(output_path, “inference”)
paddle.jit.save(model, save_path)

4.2  使用推理库预测

获得静态图模型之后,我们使用Paddle Inference进行预测部署。Paddle Inference是飞桨的原生推理库,作用于服务器端和云端,提供高性能的推理能力。

Paddle Inference 采用 Predictor 进行预测。Predictor 是一个高性能预测引擎,该引擎通过对计算图的分析,完成对计算图的一系列的优化(如OP的融合、内存/显存的优化、 MKLDNN,TensorRT 等底层加速库的支持等),能够大大提升预测性能。另外Paddle Inference提供了Python、C++、GO等多语言的API,可以根据实际环境需要进行选择,例如使用 Paddle Inference 开发 Python 预测程序可参考示例,相关API已安装在Paddle包,直接使用即可。

PaddleNLP更多教程


 
 
更多>同类产业范例库

推荐产业范例库
点击排行

北京人工智能高质量数据集服务平台

创新数据服务,积极推进数据拓展应用

关于我们

联系我们