作者:陆平
人工智能技术的应用领域日趋广泛,新的智能应用层出不穷。本项目将利用人工智能技术来对快递单文字中的命名实体信息进行识别,包括姓名、电话、省、市、区、详细地址等实体。相关工作者使用该方法,能够从快递单文本信息中自动提取关键命名实体,期望有助于提升快递物流中信息处理效率。
本项目旨在构建BERT模型,来做命名实体识别。部分代码参考了『PaddleNLP预热』04。
这里我们采用PaddleNLP2.0模块,使用pip安装PaddleNLP2.0命令如下:
#安装paddlenlp2.0
!pip install paddlenlp
接着,导入必要的模块。
import paddle
import paddlenlp as ppnlp
from paddlenlp.data import Stack, Pad, Tuple
from paddlenlp.metrics import ChunkEvaluator
import paddle.nn.functional as F
import numpy as np
from functools import partial #partial()函数可以用来固定某些参数值,并返回一个新的callable对象
在创建项目时,可以为该项目挂载“快递单数据集”,即便项目重启,该挂载的数据集也不会被自动清除。具体方法如下:首先采用notebook方式构建项目,项目创建框中的最下方有个数据集选项,选择“+添加数据集”。然后,弹出搜索框,在关键词栏目输入“快递单数据”,便能够查询到该数据集。最后,选中该数据集,可以自动在项目中挂载该数据集了。
需要注意的是,每次重新打开该项目,data文件夹下除了挂载的数据集,其他文件都将被删除。
被挂载的数据集会自动出现在data目录之下,通常是压缩包的形式。在data/data16246目录,其中有一个压缩文件,express_ner.tar.gz。大家也可以利用AI Studio Notebook下载功能把数据集下载到本地。
#本项目已经挂载了快递单数据,可以使用tar命令把数据集解压到/home/aistudio/文件
!tar -zxvf /home/aistudio/data/data16246/express_ner.tar.gz -C /home/aistudio/
express_ner/
express_ner/test.txt
express_ner/dev.txt
express_ner/train.txt
要让机器自动学会识别快递单信息中的实体,需要让它去学习那些已经标注好标签的数据集。
快递单数据集中,“姓名、电话、省、市、区、详细地址”等命名实体,对应的标签集为:
label = {P, T, A1, A2, A3, A4, O}
其中,P代表姓名,T代表电话,A1代表省份,A2代表城市,A3代表县区,A4代表详细地址。O代表无关字符。
快递单数据集是按字符来打标签的。
假设有一个快递信息如下:北 京 北 京 市 南 城 区 北 草 厂 街 戊 0 0 号 1 2 3 4 5 6 7 8 9 0 1 王 五
它对应的标签为:B-A1 I-A1 B-A2 I-A2 I-A2 B-A3 I-A3 I-A3 B-A4 I-A4 I-A4 I-A4 I-A4 I-A4 I-A4 I-A4 B-T I-T I-T I-T I-T I-T I-T I-T I-T I-T I-T B-P I-P
"B-A1"代表省份起始位置,"I-A1"代表省份中间位置或结束位置。
"B-A2"代表城市起始位置,"I-A2"代表城市中间位置或结束位置。
"B-A3"代表县区起始位置,"I-A3"代表县区中间位置或结束位置。
"B-A4"代表详细地址起始位置,"I-A4"代表详细地址中间位置或结束位置。
"B-T"代表电话号码起始位置,"I-T"代表电话号码中间位置或结束位置。
"B-P"代表姓名的起始位置,"I-P"代表姓名的中间位置或结束位置。
def load_dict(dict_path):
"""加载词汇表"""
vocab = {}
for line in open(dict_path, 'r', encoding='utf-8'):
value, key = line.strip('\n').split('\t')
vocab[key] = int(value)
return vocab
class Reader(paddle.io.Dataset):
def __init__(self, file_path):
#加载label标签
self.label_vocab = load_dict('./tag.dic')
self.examples = []
with open(file_path, 'r', encoding='utf-8') as f:
next(f)
for line in f.readlines():
data = line.strip('\n').split('\t')
#样本中的字由\002字符分割
text = data[0].split('\002')
label = data[1].split('\002')
self.examples.append([text, label])
self.num_label = max(self.label_vocab.values()) + 1
def __getitem__(self, index):
return self.examples[index]
def __len__(self):
return len(self.examples)
# 训练集数据
train_dataset = Reader('./express_ner/train.txt')
# 验证集数据
dev_dataset = Reader('./express_ner/dev.txt')
# 测试集数据
test_dataset = Reader('./express_ner/test.txt')
#分别打印前两条数据
print("训练集数据:{}\n".format(train_dataset[0:2]))
print("验证集数据:{}\n".format(dev_dataset[0:2]))
print("测试集数据:{}\n".format(test_dataset[0:2]))
训练集数据:[[['1', '6', '6', '2', '0', '2', '0', '0', '0', '7', '7', '宣', '荣', '嗣', '甘', '肃', '省', '白', '银', '市', '会', '宁', '县', '河', '畔', '镇', '十', '字', '街', '金', '海', '超', '市', '西', '行', '5', '0', '米'], ['B-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'B-P', 'I-P', 'I-P', 'B-A1', 'I-A1', 'I-A1', 'B-A2', 'I-A2', 'I-A2', 'B-A3', 'I-A3', 'I-A3', 'B-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4']], [['1', '3', '5', '5', '2', '6', '6', '4', '3', '0', '7', '姜', '骏', '炜', '云', '南', '省', '德', '宏', '傣', '族', '景', '颇', '族', '自', '治', '州', '盈', '江', '县', '平', '原', '镇', '蜜', '回', '路', '下', '段'], ['B-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'B-P', 'I-P', 'I-P', 'B-A1', 'I-A1', 'I-A1', 'B-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'B-A3', 'I-A3', 'I-A3', 'B-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4']]]
验证集数据:[[['喻', '晓', '刚', '云', '南', '省', '楚', '雄', '彝', '族', '自', '治', '州', '南', '华', '县', '东', '街', '古', '城', '路', '3', '7', '号', '1', '8', '5', '1', '3', '3', '8', '6', '1', '6', '3'], ['B-P', 'I-P', 'I-P', 'B-A1', 'I-A1', 'I-A1', 'B-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'I-A2', 'B-A3', 'I-A3', 'I-A3', 'B-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'B-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T']], [['1', '3', '4', '2', '6', '3', '3', '8', '1', '3', '5', '寇', '铭', '哲', '黑', '龙', '江', '省', '七', '台', '河', '市', '桃', '山', '区', '风', '采', '路', '朝', '阳', '广', '场'], ['B-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'B-P', 'I-P', 'I-P', 'B-A1', 'I-A1', 'I-A1', 'I-A1', 'B-A2', 'I-A2', 'I-A2', 'I-A2', 'B-A3', 'I-A3', 'I-A3', 'B-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4']]]
测试集数据:[[['黑', '龙', '江', '省', '双', '鸭', '山', '市', '尖', '山', '区', '八', '马', '路', '与', '东', '平', '行', '路', '交', '叉', '口', '北', '4', '0', '米', '韦', '业', '涛', '1', '8', '6', '0', '0', '0', '0', '9', '1', '7', '2'], ['B-A1', 'I-A1', 'I-A1', 'I-A1', 'B-A2', 'I-A2', 'I-A2', 'I-A2', 'B-A3', 'I-A3', 'I-A3', 'B-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'B-P', 'I-P', 'I-P', 'B-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T']], [['广', '西', '壮', '族', '自', '治', '区', '桂', '林', '市', '雁', '山', '区', '雁', '山', '镇', '西', '龙', '村', '老', '年', '活', '动', '中', '心', '1', '7', '6', '1', '0', '3', '4', '8', '8', '8', '8', '羊', '卓', '卫'], ['B-A1', 'I-A1', 'I-A1', 'I-A1', 'I-A1', 'I-A1', 'I-A1', 'B-A2', 'I-A2', 'I-A2', 'B-A3', 'I-A3', 'I-A3', 'B-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'I-A4', 'B-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'I-T', 'B-P', 'I-P', 'I-P']]]
#调用ppnlp.transformers.BertTokenizer进行数据处理,tokenizer可以把原始输入文本转化成模型model可接受的输入数据格式。
tokenizer = ppnlp.transformers.BertTokenizer.from_pretrained("bert-base-chinese")
#数据预处理
def convert_example(example,tokenizer,label_vocab,max_seq_length=256,is_test=False):
if is_test:
text = example
else:
text, label = example
#tokenizer.encode方法能够完成切分token,映射token ID以及拼接特殊token
encoded_inputs = tokenizer.encode(text=text, max_seq_len=None, pad_to_max_seq_len=False)
input_ids = encoded_inputs["input_ids"]
segment_ids = encoded_inputs["segment_ids"]
seq_len = encoded_inputs["seq_len"]
if not is_test:
label = ['O'] + label + ['O']
label = [label_vocab[x] for x in label]
return input_ids, segment_ids, seq_len, label
else:
return input_ids, segment_ids, seq_len
#使用partial()来固定convert_example函数的tokenizer, label_vocab, max_seq_length等参数值
trans_fn = partial(convert_example, tokenizer=tokenizer, label_vocab=train_dataset.label_vocab, max_seq_length=128)
batchify_fn = lambda samples, fn=Tuple(Pad(axis=0,pad_val=tokenizer.vocab[tokenizer.pad_token]),
Pad(axis=0, pad_val=tokenizer.vocab[tokenizer.pad_token]),
Stack(),
Pad(axis=0, pad_val=-1)):fn(list(map(trans_fn, samples)))
#训练集迭代器
train_loader = paddle.io.DataLoader(
dataset=train_dataset,
batch_size=64,
shuffle=True,
return_list=True,
collate_fn=batchify_fn)
dev_loader = paddle.io.DataLoader(
dataset=dev_dataset,
batch_size=64,
return_list=True,
collate_fn=batchify_fn)
test_loader = paddle.io.DataLoader(
dataset=test_dataset,
batch_size=64,
return_list=True,
collate_fn=batchify_fn)
print(train_dataset.label_vocab)
[2021-01-28 13:41:31,100] [ INFO] - Downloading bert-base-chinese-vocab.txt from https://paddle-hapi.bj.bcebos.com/models/bert/bert-base-chinese-vocab.txt
100%|██████████| 107/107 [00:00<00:00, 3253.45it/s]
{'B-P': 0, 'I-P': 1, 'B-T': 2, 'I-T': 3, 'B-A1': 4, 'I-A1': 5, 'B-A2': 6, 'I-A2': 7, 'B-A3': 8, 'I-A3': 9, 'B-A4': 10, 'I-A4': 11, 'O': 12}
#加载预训练BERT模型用于token分类任务的Fine-tune网络BertForTokenClassification。
model = ppnlp.transformers.BertForTokenClassification.from_pretrained("bert-base-chinese", num_classes=train_dataset.num_label)
[2021-01-28 13:41:36,463] [ INFO] - Downloading http://paddlenlp.bj.bcebos.com/models/transformers/bert/bert-base-chinese.pdparams and saved to /home/aistudio/.paddlenlp/models/bert-base-chinese
[2021-01-28 13:41:36,514] [ INFO] - Downloading bert-base-chinese.pdparams from http://paddlenlp.bj.bcebos.com/models/transformers/bert/bert-base-chinese.pdparams
100%|██████████| 696494/696494 [00:10<00:00, 67881.74it/s]
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/layers.py:1245: UserWarning: Skip loading for classifier.weight. classifier.weight is not found in the provided dict.
warnings.warn(("Skip loading for {}. ".format(key) + str(err)))
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/layers.py:1245: UserWarning: Skip loading for classifier.bias. classifier.bias is not found in the provided dict.
warnings.warn(("Skip loading for {}. ".format(key) + str(err)))
#设置训练超参数
#学习率
learning_rate = 1e-5
#训练轮次
epochs = 20
#学习率预热比率
warmup_proption = 0.1
#权重衰减系数
weight_decay = 0.01
num_training_steps = len(train_loader) * epochs
num_warmup_steps = int(warmup_proption * num_training_steps)
def get_lr_factor(current_step):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
else:
return max(0.0,
float(num_training_steps - current_step) /
float(max(1, num_training_steps - num_warmup_steps)))
#学习率调度器
lr_scheduler = paddle.optimizer.lr.LambdaDecay(learning_rate, lr_lambda=lambda current_step: get_lr_factor(current_step))
#优化器
optimizer = paddle.optimizer.AdamW(
learning_rate=lr_scheduler,
parameters=model.parameters(),
weight_decay=weight_decay,
apply_decay_param_fun=lambda x: x in [
p.name for n, p in model.named_parameters()
if not any(nd in n for nd in ["bias", "norm"])
])
#损失函数
criterion = paddle.nn.loss.CrossEntropyLoss()
#评估函数
metric = ChunkEvaluator(train_dataset.label_vocab.keys(), suffix=True)
#评估函数
def evaluate(model, metric, data_loader):
model.eval()
metric.reset()
for input_ids, seg_ids, lens, labels in data_loader:
logits = model(input_ids, seg_ids)
preds = paddle.argmax(logits, axis=-1)
n_infer, n_label, n_correct = metric.compute(None, lens, preds, labels)
metric.update(n_infer.numpy(), n_label.numpy(), n_correct.numpy())
precision, recall, f1_score = metric.accumulate()
print("评估准确度: %.6f - 召回率: %.6f - f1得分: %.6f" % (precision, recall, f1_score))
model.train()
metric.reset()
global_step = 0
for epoch in range(1, epochs+1):
for step, (input_ids, segment_ids, seq_lens, labels) in enumerate(train_loader, start=1):
logits = model(input_ids, segment_ids)
preds = paddle.argmax(logits, axis=-1)
n_infer, n_label, n_correct = metric.compute(None, seq_lens, preds, labels)
metric.update(n_infer.numpy(), n_label.numpy(), n_correct.numpy())
precision, recall, f1_score = metric.accumulate()
loss = paddle.mean(criterion(logits.reshape([-1, train_dataset.num_label]), labels.reshape([-1])))
global_step += 1
if global_step % 10 == 0 :
print("训练准确度: %.6f, 召回率: %.6f, f1得分: %.6f" % (precision, recall, f1_score))
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.clear_gradients()
evaluate(model, metric, dev_loader)
训练准确度: 0.774794, 召回率: 0.479581, f1得分: 0.592449
训练准确度: 0.804517, 召回率: 0.533068, f1得分: 0.641248
评估准确度: 0.935560 - 召回率: 0.836417 - f1得分: 0.883215
训练准确度: 0.921431, 召回率: 0.822775, f1得分: 0.869313
训练准确度: 0.949503, 召回率: 0.884844, f1得分: 0.916034
训练准确度: 0.958352, 召回率: 0.916231, f1得分: 0.936818
评估准确度: 0.978279 - 召回率: 0.984861 - f1得分: 0.981559
训练准确度: 0.984211, 召回率: 0.980084, f1得分: 0.982143
训练准确度: 0.985173, 召回率: 0.982851, f1得分: 0.984010
评估准确度: 0.985368 - 召回率: 0.991169 - f1得分: 0.988260
训练准确度: 0.988482, 召回率: 0.990037, f1得分: 0.989259
训练准确度: 0.991358, 召回率: 0.990666, f1得分: 0.991012
训练准确度: 0.991933, 召回率: 0.990791, f1得分: 0.991362
评估准确度: 0.993272 - 召回率: 0.993272 - f1得分: 0.993272
训练准确度: 0.994360, 召回率: 0.992537, f1得分: 0.993448
训练准确度: 0.994568, 召回率: 0.994178, f1得分: 0.994373
评估准确度: 0.995370 - 召回率: 0.994533 - f1得分: 0.994952
训练准确度: 0.995550, 召回率: 0.992950, f1得分: 0.994248
训练准确度: 0.995984, 召回率: 0.995115, f1得分: 0.995549
训练准确度: 0.996229, 召回率: 0.995291, f1得分: 0.995760
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.996071, 召回率: 0.996593, f1得分: 0.996332
训练准确度: 0.996402, 召回率: 0.996011, f1得分: 0.996206
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.995557, 召回率: 0.996599, f1得分: 0.996077
训练准确度: 0.994865, 召回率: 0.995732, f1得分: 0.995298
训练准确度: 0.995713, 召回率: 0.996442, f1得分: 0.996077
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.996076, 召回率: 0.996598, f1得分: 0.996337
训练准确度: 0.996604, 召回率: 0.996994, f1得分: 0.996799
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.997381, 召回率: 0.996337, f1得分: 0.996859
训练准确度: 0.997384, 召回率: 0.996950, f1得分: 0.997167
训练准确度: 0.997384, 召回率: 0.997332, f1得分: 0.997358
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.997647, 召回率: 0.997386, f1得分: 0.997517
训练准确度: 0.998627, 召回率: 0.998366, f1得分: 0.998496
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.998168, 召回率: 0.996604, f1得分: 0.997386
训练准确度: 0.997733, 召回率: 0.997385, f1得分: 0.997559
训练准确度: 0.997908, 召回率: 0.998116, f1得分: 0.998012
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.998168, 召回率: 0.998168, f1得分: 0.998168
训练准确度: 0.998495, 召回率: 0.998234, f1得分: 0.998364
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 1.000000, 召回率: 1.000000, f1得分: 1.000000
训练准确度: 0.999389, 召回率: 0.999041, f1得分: 0.999215
训练准确度: 0.998953, 召回率: 0.998849, f1得分: 0.998901
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.999607, 召回率: 0.999346, f1得分: 0.999476
训练准确度: 0.998691, 召回率: 0.998299, f1得分: 0.998495
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.998955, 召回率: 0.998955, f1得分: 0.998955
训练准确度: 0.999128, 召回率: 0.998779, f1得分: 0.998953
训练准确度: 0.998953, 召回率: 0.998849, f1得分: 0.998901
评估准确度: 0.993277 - 召回率: 0.994113 - f1得分: 0.993695
训练准确度: 0.998691, 召回率: 0.999214, f1得分: 0.998953
训练准确度: 0.998365, 召回率: 0.998888, f1得分: 0.998627
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.997910, 召回率: 0.997910, f1得分: 0.997910
训练准确度: 0.998430, 召回率: 0.997734, f1得分: 0.998082
训练准确度: 0.998901, 召回率: 0.998483, f1得分: 0.998692
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 0.998040, 召回率: 0.999084, f1得分: 0.998562
训练准确度: 0.998824, 召回率: 0.999477, f1得分: 0.999150
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
训练准确度: 1.000000, 召回率: 1.000000, f1得分: 1.000000
训练准确度: 0.999390, 召回率: 0.999564, f1得分: 0.999477
训练准确度: 0.999267, 召回率: 0.999163, f1得分: 0.999215
评估准确度: 0.994533 - 召回率: 0.994533 - f1得分: 0.994533
这里采用『PaddleNLP预热』04项目中的parse_decodes模块。
def parse_decodes(dataset, decodes, lens):
decodes = [x for batch in decodes for x in batch]
lens = [x for batch in lens for x in batch]
id_label = dict(zip(dataset.label_vocab.values(), dataset.label_vocab.keys()))
outputs = []
for idx, end in enumerate(lens):
sent = dataset.examples[idx][0][:end]
tags = [id_label[x] for x in decodes[idx][1:end]]
sent_out = []
tags_out = []
words = ""
for s, t in zip(sent, tags):
if t.startswith('B-') or t == 'O':
if len(words):
sent_out.append(words)
tags_out.append(t.split('-')[1])
words = s
else:
words += s
if len(sent_out) < len(tags_out):
sent_out.append(words)
outputs.append(' '.join(
[str((s, t)) for s, t in zip(sent_out, tags_out)]))
return outputs
def predict(model, data_loader, dataset):
pred_list = []
len_list = []
for input_ids, seg_ids, lens, labels in data_loader:
logits = model(input_ids, seg_ids)
pred = paddle.argmax(logits, axis=-1)
pred_list.append(pred.numpy())
len_list.append(lens.numpy())
preds = parse_decodes(dataset, pred_list, len_list)
return preds
pred = predict(model, test_loader, test_dataset)
# 前10个预测数据的预测标签
for results in pred[:10]:
print(results)
('黑龙江省', 'A1') ('双鸭山市', 'A2') ('尖山区', 'A3') ('八马路与东平行路交叉口北40米', 'A4') ('韦业涛', 'P') ('18600009172', 'T')
('广西壮族自治区', 'A1') ('桂林市', 'A2') ('雁山区', 'A3') ('雁山镇西龙村老年活动中心', 'A4') ('17610348888', 'T') ('羊卓卫', 'P')
('15652864561', 'T') ('河南省', 'A1') ('开封市', 'A2') ('顺河回族区', 'A3') ('顺河区', 'A4') ('公园路32号', 'A4') ('赵本山', 'P')
('河北省', 'A1') ('唐山市', 'A2') ('玉田县', 'A3') ('无终大街159号', 'A4') ('18614253058', 'T') ('尚汉生', 'P')
('台湾', 'A1') ('台中市', 'A2') ('北区', 'A3') ('北区锦新街18号', 'A4') ('18511226708', 'T') ('蓟丽', 'P')
('廖梓琪', 'P') ('18514743222', 'T') ('湖北省', 'A1') ('宜昌市', 'A2') ('长阳土家族自治县', 'A3') ('贺家坪镇贺家坪村一组临河1号', 'A4')
('江苏省', 'A1') ('南通市', 'A2') ('海门市', 'A3') ('孝威村孝威路88号', 'A4') ('18611840623', 'T') ('计星仪', 'P')
('17601674746', 'T') ('赵春丽', 'P') ('内蒙古自治区', 'A1') ('乌兰察布市', 'A2') ('凉城县', 'A3') ('新建街', 'A4')
('云南省', 'A1') ('临沧市', 'A2') ('耿马傣族佤族自治县', 'A3') ('鑫源路法院对面', 'A4') ('许贞爱', 'P') ('18510566685', 'T')
('四川省', 'A1') ('成都市', 'A2') ('双流区', 'A3') ('东升镇北仓路196号', 'A4') ('耿丕岭', 'P') ('18513466161', 'T')
开源项目详见百度AI Studio:
https://aistudio.baidu.com/aistudio/projectdetail/1477098