基于知识图谱的问答系统答即根据一个问题,抽出一条三元组,生成类 sql 语句,知识图谱查询返回答案。本文是基于知识图谱的问答系统,通过 BERT+CRF 做命名实体识别和句子相似度比较,最后实现线上的部署。
作者:走在前方
博客:https://wenjie.blog.csdn.net/
技术交流群: 访问博客首页,加入 "NLP技术交流群" ,祝共同进步。
0-学什么
针对知识图谱的问题系统,重点解决的几个重要问题。通过本文学习,让你掌握知识图谱涉及的主要内容,通过本文分享,大家搭建快速搭建一个知识图谱的智能问答应该没什么问题了。
- 知识库创建( 一般情况下,这个工作工作非常大,也非常重要。针对数据一般来自公司、比赛、以及通过爬虫来构建数据知识库)
- 命名实体识别(一般通过bert_crf,bilstm_crf 等模型来完成,实际工作一般也需要工作增加规则来处理)
- 实体链接到知识库进行检索(我们识别实体后,如何正确去对应到我们的知识库,也是我们的重点,这块很多思路大家可以提前在nlp 很多比赛都有提及)
- 基于知识库的问答(一般我们都是基于图数据库来完成的,根据数据量的大小来选择,例如:neo4j、JanusGraph等。如果是简单的知识库关系可以mysql,redis,mongo存储即可)
- 知识图谱一般垂直领域图谱或者开放领域图谱,持续优化的过程关注在知识库创建上。创建完成,可以直接应用到搜索,推荐,智能客服等对话系统。。。
本文主要研究内容如下
* 问答 QA 系统简单介绍
* 数据集介绍(实体间歧义问题、检索模型效果)
* KBQA整理流程介绍
* 评价标准回顾
* 命名实体识别数据准备
* 数据预处理
* 数据集准备(切分、ner数据、文本相似度数据、知识库数据)
* BERT-CRF命名实体识别训练
* BERT原理
* BERT-CRF 模型实现NER
* 在线预测服务
* 文本语义相似度模型训练
* 数据可视化
* BERT分类模型
* 文本相似度在线服务
* 知识库构建
* 知识库介绍
* 知识库构建
* 基于知识检索方案
* 基于知识库的QA系统检索
* 搭建QA检索服务(依赖NER模型、文本相似度模型、知识库)
* QA检索服务案例验证
* 扩展以及回顾
* 不足以及未来的发展
* 扩展解决方案思路
1-项目介绍
- 1.1-数据集介绍
NLPCC 全称自然语言处理与中文计算会议(The Conference on Natural Language Processing and Chinese Computing),它是由中国计算机学会(CCF)主办的 CCF 中文信息技术专业委员会年度学术会议,专注于自然语言处理及中文计算领域的学术和应用创新。
数据集来自 NLPCC ICCPOL 2016 KBQA 任务集,其包含 14609 个问答对的训练集和包含 9870 个问答对的测试集。
提供的知识库
- 6502738 个实体
- 587875 个属性
- 43063796 个 实体-实体关系
知识库文件中每行存储即三元组 ( 实体、属性、属性值) 。
各文件统计如下:
训练集:14609
开发集:9870
知识库:43063796
知识库样例如下:
"希望之星"英语风采大赛|||中文名|||“希望之星”英语风采大赛
"希望之星"英语风采大赛|||主办方|||中央电视台科教节目中心
"希望之星"英语风采大赛|||别名|||"希望之星"英语风采大赛
"希望之星"英语风采大赛|||开始时间|||1998
"希望之星"英语风采大赛|||比赛形式|||全国选拔
"希望之星"英语风采大赛|||节目类型|||英语比赛
原数据中本只有问答对(question-answer)无标注三元组(triple),如下所示
<question id=1> 《机械设计基础》这本书的作者是谁?
<answer id=1> 杨可桢,程光蕴,李仲生
==================================================
<question id=2> 《高等数学》是哪个出版社出版的?
<answer id=2> 武汉大学出版社
==================================================
<question id=3> 《线性代数》这本书的出版时间是什么?
<answer id=3> 2013-12-30
本人所用问答对数据来自该比赛第一名的预处理 https://github.com/huangxiangzhou/NLPCC2016KBQA 。构造 Triple 的方法为从知识库中反向查找答案,根据问题过滤实体,最终筛选得到,也会存在少量噪音数据。该 Triple 之后用于构建实体识别和属性选择等任务的数据集。
问答对样例如下所示(重点增加 triple,这里实体抽取和属性抽取的基本数据-知识抽取问题)
<question id=1> 《机械设计基础》这本书的作者是谁?
<triple id=1> 机械设计基础 ||| 作者 ||| 杨可桢,程光蕴,李仲生
<answer id=1> 杨可桢,程光蕴,李仲生
==================================================
<question id=2> 《高等数学》是哪个出版社出版的?
<triple id=2> 高等数学 ||| 出版社 ||| 武汉大学出版社
<answer id=2> 武汉大学出版社
==================================================
<question id=3> 《线性代数》这本书的出版时间是什么?
<triple id=3> 线性代数 ||| 出版时间 ||| 2013-12-30
<answer id=3> 2013-12-30
==================================================
- 1.1.1-知识库实体间的歧义
以“贝拉克·奥巴马”为例,涉及该实体的问答对如下:
<question id=9687> 谁是贝拉克·奥巴马的妻子?
<triple id=9687> 贝拉克·奥巴马 ||| 妻子 ||| 米歇尔·奥巴马
<answer id=9687> 米歇尔·奥巴马
在知识库中查询包含该实体的三元组,结果如下(部分):
贝拉克·奥巴马(美国现任总统) ||| 别名 ||| 贝拉克·奥巴马
贝拉克·奥巴马(美国现任总统) ||| 姓名 ||| 贝拉克·侯赛因·奥巴马
贝拉克·奥巴马(美国现任总统) ||| 妻子 ||| 米歇尔·奥巴马
......
贝拉克·奥巴马 ||| 主要成就 ||| 1996 年伊利诺伊州参议员 美国第 56 届、57 届总统 2009 年诺贝尔和平奖获得者 时代周刊年度风云人物 2008、2011 任期内清除本·拉登
贝拉克·奥巴马 ||| 代表作品 ||| 《我相信变革》《我父亲的梦想》《无畏的希望》
贝拉克·奥巴马 ||| 妻子 ||| 米歇尔·拉沃恩·奥巴马
......
贝拉克·奥巴马(美国第 44 任总统) ||| 血型 ||| ab
贝拉克·奥巴马(美国第 44 任总统) ||| 学院 ||| 西方学院
贝拉克·奥巴马(美国第 44 任总统) ||| 妻子 ||| 米歇尔·拉沃恩·奥巴马
首先,知识库中存在“贝拉克·奥巴马”的多条实体,有可能是多数据来源的融合或其他原因,从而并不能完全保证信息的对齐。我们查看“妻子”这一属性,发现有的是“米歇尔·拉沃恩·奥巴马”有的是“米歇尔·奥巴马”,而我们问答对中给出的答案是“米歇尔·奥巴马”。因此当我们的模型检索到正确三元组时:
贝拉克·奥巴马(美国第 44 任总统) ||| 妻子 ||| 米歇尔·拉沃恩·奥巴马
虽然在实体和属性都映射正确的情况下,最终答案仍可能被判定为错误。
- 1.1.2-问题实体歧义
以“博士来拜”为例,涉及该实体的问答对如下:
<question id=249> 博士来拜是什么年代的作品?
<triple id=249> 博士来拜 ||| 年代 ||| 1461 年
<answer id=249> 1461 年
在知识库中查询包含该实体的三元组,结果如下(部分):
博士来拜(曼特尼亚画作) ||| 别名 ||| 博士来拜
博士来拜(曼特尼亚画作) ||| 中文名 ||| 博士来拜
博士来拜(曼特尼亚画作) ||| 类别 ||| 油画,壁画
博士来拜(曼特尼亚画作) ||| 年代 ||| 1461 年
博士来拜(曼特尼亚画作) ||| 作者 ||| 曼特尼亚
......
博士来拜(维登画作) ||| 别名 ||| 博士来拜
博士来拜(维登画作) ||| 中文名 ||| 博士来拜
博士来拜(维登画作) ||| 类别 ||| 油画
博士来拜(维登画作) ||| 年代 ||| 1455 年
博士来拜(维登画作) ||| 作者 ||| 维登
博士来拜(维登画作) ||| 属地 ||| 慕尼黑画廊藏
......
博士来拜(达·芬奇画作) ||| 别名 ||| 博士来拜
博士来拜(达·芬奇画作) ||| 中文名 ||| 博士来拜
博士来拜(达·芬奇画作) ||| 类别 ||| 油画
博士来拜(达·芬奇画作) ||| 年代 ||| 1481-1482
博士来拜(达·芬奇画作) ||| 作者 ||| 达芬奇
博士来拜(达·芬奇画作) ||| 现藏 ||| 佛罗伦萨乌菲兹美术馆
博士来拜(达·芬奇画作) ||| 规格 ||| 246 x 243 厘米
问句中的问题是:“博士来拜是什么年代的作品?“,涉及到”年代“这个属性,而这幅作品被不同时期的很多人创作过,我们无法从当前问句下得到要询问的是哪位艺术家的创作年代。因此该问题的涉及的实体具有歧义性,同样的,当模型检索到我们认为的正确实体和正确属性后,依然有可能会被判定为错误答案。
- 1.1.3-检索模型的效果影响
在知识库中相关实体三元组数量过多的情况下,对检索模型的效果、效率也是个挑战
在具有 4300W 条三元组的知识库中,同一个实体会检索出大量(几十、几百条)的相关三元组,而且在存在上述两个歧义性问题的情况下,识别的效果和效率都是很大的问题。以上的两个问题在实体识别实验部分和属性抽取部分的影响较小,但在实体链接知识库检索最终答案三元组的部分会有较大的影响。
- 1.2-KBQA 整体流程
query-> 实体识别模型+属性抽取模型-> 知识库检索
KBQA 实现业务流程:
- 输入问句 query
- 通过实体识别模型检测问句中的实体,得到 mention(采用 BERT+CRF 进行实体抽取,可以看成中文命名实体识别问题)
- 通过检索模型在知识库中检索 mention,得到候选集(K 个候选实体的所有三元组,通过类 sql 去知识库检索)
- 通过属性抽取模型在候选集中挑选最合适的属性,得到唯一三元组
- 规则匹配:如果前 n 个三元组有某个三元组的属性是 query 问题字符串的子集(相当于字符串匹配),将这个三元组的答案作为正确答案返回
- 语义匹配:采用 BERT,多个结果与 query 进行相似度匹配进行打分
- 输出答案
- 1.3-评价标准
- 命名实体识别和属性映射评价标准
召回率 (Recall),精确率 (Precision) ,F1-Score。
- 对话系统的评价指标可以分为客观评价指标和主观评价指标。
其中客观评价指标又可以分为:
- 词重叠评价指标:(包括 BLEU、ROUGE、METEOR)
- 词向量评价指标:(包括 Greedy matching、Embedding Average、Vector Extrema、perplexity 困惑度)
2-命名实体识别数据准备
- 2.1-数据预处理
清洗训练数据、测试数据、知识库过滤属性,去除‘-’,‘•’,空格等噪音符号;同时把每一行lower()转成小写。
https://github.com/huangxiangzhou/NLPCC2016KBQA
- 2.2-数据集准备
- 2.2.1-数据集切分
'nlpcc-iccpol-2016.kbqa.testing-data','nlpcc-iccpol-2016.kbqa.training-data'
-> 合并-> 按照比例切分 train,dev、test
- 输入数据 'nlpcc-iccpol-2016.kbqa.testing-data','nlpcc-iccpol-2016.kbqa.training-data' 如下
<question id=6> 《高等数学一(微积分)》是哪一门课的通用教材?
<triple id=6> 高等数学 ||| 书名 ||| 高等数学一(微积分)
<answer id=6> 高等数学一(微积分)
==================================================
<question id=7> 有谁知道万达广场英文怎么说?
<triple id=7> 万达广场 ||| 外文名 ||| amoy wanda plaza
<answer id=7> amoy wanda plaza
- 比例切分 train,dev、test
<question id=1> 《机械设计基础》这本书的作者是谁?
<triple id=1> 机械设计基础 ||| 作者 ||| 杨可桢,程光蕴,李仲生
<answer id=1> 杨可桢,程光蕴,李仲生
==================================================
<question id=2> 《高等数学》是哪个出版社出版的?
<triple id=2> 高等数学 ||| 出版社 ||| 武汉大学出版社
<answer id=2> 武汉大学出版社
- 2.2.2-构建实体识别训练数据: 实体识别
通过 NLPCC2016KBQA 中的原始数据,构建用来训练 NER 的样本集合。构造 NER 训练集,实体序列标注,训练 BERT+CRF。其中:LOC 表示其他类型,最终我们可是识别 BIO 标签即可
《O
线 B-LOC
性 I-LOC
代 I-LOC
数 I-LOC
》O
的 O
i O
s O
b O
n O
码 O
是 O
什 O
么 O
?O
最终我们的数据保存 train.txt,dev.txt,test.txt 三个文件
另外,我们需要文本长度分析
最要考虑 bert 模型,需要我们指定 seq_length,即句子的最大长度序列。
train.txt,dev.txt,test.txt -> 统计句子的长度-> seq_length = 64 合适。
- 2.2.3-构建属性识别训练数据: 属性识别
query-> 实体识别模型-> 获取实体名称-> 知识库检索-> 返回多个三元组
注:三元组组成(实体,属性名称,属性数值)
问题:究竟哪个三元组是我们需要的呢?
解决方案
- query 去掉识别的实体后的内容,通过字符串匹配三元组属性名称
- query 去掉识别的实体后的内容,和 三元组列表中的每个属性进行一个相似度打分,然后进行排序,最终得到一个最合适的答案。可以转换成一个二分类的问题。
那么接下来我们面临的问题。
- 正样本: 当前 query 就是我们的正样本
- 负验证:我们可以随机选择一部分属性名称,然后凭接 query 后构成我们的负样本
一个 sample 由“问题+属性+Label”构成,原始数据中的属性值置为 1。
原始的输入样本
<question id=1> 《机械设计基础》这本书的作者是谁?
<triple id=1> 机械设计基础 ||| 作者 ||| 杨可桢,程光蕴,李仲生
<answer id=1> 杨可桢,程光蕴,李仲生
通过采样构建的 属性分类样本数据
《机械设计基础》这本书的作者是谁? 作者 1
《机械设计基础》这本书的作者是谁?imdb 链接 0
《机械设计基础》这本书的作者是谁? 周点击 0
《机械设计基础》这本书的作者是谁? 建校时间 0
《机械设计基础》这本书的作者是谁? 主讲 0
《机械设计基础》这本书的作者是谁? 注意 0
当前我们完成了实体识别+属性识别-> 就可以直接去知识库中锁定唯一的答案了。
- 2.2.4-知识库搭建: 实体-属性名称-属性数值
entity,attribute,answer
机械设计基础,作者,杨可桢,程光蕴,李仲生
高等数学,出版社,武汉大学出版社
线性代数,出版时间,2013-12-30
例如:
query = 《机械设计基础》这本书的作者是谁?
query -> 实体识别模型-> 机械设计基础
query-> 属性识别模型-> 分类问题?文本相似比较( # TODO)
3-BERT-CRF 命名实体识别
- 3.1-BERT 原理介绍
BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
he Illustrated BERT, ELMo, and co.
- MRPC(Microsoft Research Paraphrase Corpus,也有的成其为 MSRP)是一些句子对,有的是同义的,有的是不同义的
- CoNLL 2003 是进行 NER 经典的一个数据集
- 3.2-BERT+CRF 模型
pytorch中bilstm-crf部分code解析
最通俗易懂的BiLSTM-CRF模型中的CRF层介绍
Bidirectional LSTM-CRF Models for Sequence Tagging(论文翻译)
- 3.3-实体识别模型训练
标签: ["O", "B-LOC", "I-LOC"] 。
另外,我们把 BIO 编码格式数据转换为平台的统一标准,然后直接借助平台统一进行模型的训练,看看效果如何。
- 3.3.1-标准数据转换
上述数据处理生成三个文件
- seq.in 每行表示一个完整的句子,然后以空格分割
《 机 械 设 计 基 础 》 这 本 书 的 作 者 是 谁 ?
《 高 等 数 学 》 是 哪 个 出 版 社 出 版 的 ?
- seq.out 对应每个单词的 TAG,空格分割
O B-LOC I-LOC I-LOC I-LOC O O O O O O O O O O O O O
B-LOC I-LOC I-LOC O O O O O O O O O
- slot_label.txt NER 标签的类型,这里增加 PAD 和 UNK 两个特殊的标签
PAD
UNK
O
B-LOC
I-LOC
- 3.3.2-标准数据可视化分析
- 句子数量整体分析
- 句子长度分析
我们可以设置 seq_length = 64 做为 bert 模型的 max_seq_length 超参数
- 数据集标签分布
- 3.3.3-模型训练
python3 main.py \
--model_name_or_path ../pretrained_models/bert-base-chinese/ \
--task NLPCC2016KBQA \
--model_type bert \
--model_dir kbqa_bert_zh_model \
--do_train --do_eval --use_crf \
--num_train_epochs 3 \
--train_batch_size 128 \
--eval_batch_size 64 \
--max_seq_len 60 \
--dropout_rate 0.9
模型的效果如下所示,整个感觉训练效果还不错
dev:
06/05/2020 21:04:54 - INFO - train - loss = 0.3480187522040473
06/05/2020 21:04:54 - INFO - train - sementic_frame_acc = 0.9718463553033609
06/05/2020 21:04:54 - INFO - train - slot_f1 = 0.9731967748964916
06/05/2020 21:04:54 - INFO - train - slot_precision = 0.9717145343777197
06/05/2020 21:04:54 - INFO - train - slot_recall = 0.9746835443037974
test:
06/05/2020 21:05:09 - INFO - train - loss = 0.36994590636376024
06/05/2020 21:05:09 - INFO - train - sementic_frame_acc = 0.9704555705908886
06/05/2020 21:05:09 - INFO - train - slot_f1 = 0.970283656010806
06/05/2020 21:05:09 - INFO - train - slot_precision = 0.9685393258426966
06/05/2020 21:05:09 - INFO - train - slot_recall = 0.9720342805593144
- 3.4-预测
- 3.4.1-在线服务启动
我们通过 gpu 下训练的模型,也可以直接运行在 cpu 下。
python3 api.py \
--model_name_or_path ../pretrained_models/bert-base-chinese/ \
--task NLPCC2016KBQA \
--model_type bert \
--model_dir kbqa_bert_zh_model \
--use_crf
注意:model_name_or_path 和 model_dir 换成自己预训练模型路径和模型文件路径
预测效率对比:gpu 单条记录预测 13ms 左右,cpu 大概 60ms(和服务器配置有关系)。对于 cpu 下推理速度慢的问题我们也可以针对模型进行优化(比如:albert-tiny....)
在线预测命令行如下:
我们抽查的原始数据
<question id=8> 王平的出生日期是哪一年呀?
<triple id=8> 王平 ||| 出生日期 ||| 1954 年
<answer id=8> 1954 年
在线服务预测,希望 query = xxx. 模型可以识别出" 王平" 这个实体
curl http://127.0.0.1:8000/predict \
-H "Content-Type:application/json" \
-X POST \
--data '{"text": "王平的出生日期是哪一年呀?","lang":"zh"}'
返回的结果
$ curl http://127.0.0.1:8000/predict -H "Content-Type:application/json" -X POST --data '{"text": "王平的出生日期是哪一年呀?","lang":"zh"}'
{
"errno": 0,
"errmsg": "success",
"time": 13,
"data": [
{
"word": "王",
"tag": "B-LOC"
},
{
"word": "平",
"tag": "I-LOC"
},
{
"word": "的",
"tag": "O"
},
{
"word": "出",
"tag": "O"
},
{
"word": "生",
"tag": "O"
},
{
"word": "日",
"tag": "O"
},
{
"word": "期",
"tag": "O"
},
{
"word": "是",
"tag": "O"
},
{
"word": "哪",
"tag": "O"
},
{
"word": "一",
"tag": "O"
},
{
"word": "年",
"tag": "O"
},
{
"word": "呀",
"tag": "O"
},
{
"word": "?",
"tag": "O"
}
]
}
后台日期服务器的数据(是我们正确识别出来的实体)
127.0.0.1 - - [05/Jun/2020 21:09:19] "POST /predict HTTP/1.1" 200 -
text = 王平的出生日期是哪一年呀?
seq_len = 13
0 王 B-LOC
1 平 I-LOC
2 的 O
3 出 O
4 生 O
5 日 O
6 期 O
7 是 O
8 哪 O
9 一 O
10 年 O
11 呀 O
12?O
- 3.4.2-批量数据核查
提供一批 问题列表,调用在线 api.py 中提供的服务,查看效果怎么样?
我们的数据,一定不能参与模型训练的数据,然后拿过来进行测试。
4-文本语义相似度模型
关于属性映射我们看下提供的数据:
《机械设计基础》这本书的作者是谁? 作者 1
《机械设计基础》这本书的作者是谁?imdb 链接 0
《机械设计基础》这本书的作者是谁? 周点击 0
《机械设计基础》这本书的作者是谁? 建校时间 0
《机械设计基础》这本书的作者是谁? 主讲 0
《机械设计基础》这本书的作者是谁? 注意 0
- 4.1-数据集处理
- 原始预料数据
0《机械设计基础》这本书的作者是谁? 作者 1
1《机械设计基础》这本书的作者是谁?imdb 链接 0
2《机械设计基础》这本书的作者是谁? 周点击 0
3《机械设计基础》这本书的作者是谁? 建校时间 0
4《机械设计基础》这本书的作者是谁? 主讲 0
5《机械设计基础》这本书的作者是谁? 注意 0
针对每行的句子的含义说明如下:
- id: 表示句子唯一标识
- text_a:句子1
- text_b:句子2
- label:句子1和句子是否相关 (0-不同;1-相同)
- 数据离散化数据
- 4.2-数据可视化分析
- 句子标签数量分布
- 句子对的长度可视化分析
句子的最大的长度我们可以设置 64
- 4.3-BERT 分类模型训练
python sim_main.py --data_dir ./data/ \
--task_name NLPCCKBQA2016 \
--num_train_epochs 2 \
--pre_train_model ../pretrained_models/bert-base-chinese \
--max_seq_length 64 \
--do_train \
--train_batch_size 64 \
--eval_batch_size 64 \
--gradient_accumulation_steps 4 \
--output ./NLPCCKBQA2016_bert_zh_model
- data_dir:训练预料数据
- pre_train_model : BERT 中文预训练的模型
- output:训练完成模型保持路径
- max_seq_length:表示 bert 模型输入句子最大的长度 (通过数据分析获得)
- train_batch_size、eval_batch_size: 批量进行数据的处理
- task_name:表示我们计算任务的名称,对应数据集加载方法
- num_train_epochs 训练的迭代次数
模型训练完成后,我们看看在验证集上的效果(当前数据好坏影响非常大。)
15:10:17 - INFO - __main__ - acc = 0.981087
15:10:17 - INFO - __main__ - Precision = 0.920916
15:10:17 - INFO - __main__ - Recall = 0.969807
15:10:17 - INFO - __main__ - f1_score = 0.944730
15:10:17 - INFO - __main__ - Evaluating EPOCH = [2/2] global_step = 684 eval_loss = 0.082648 eval_acc = 0.981087
best_acc = 0.9810874704491725
这里仅仅进行2次迭代,我们发现效果非常不错,f1_score = 94%
- 4.4-文本相似度在线服务
在线服务预测,希望 query = 丽江子馨小院客栈离车站多远啊?. 模型可以识别出" 丽江子馨" 这个实体。 那么这个时候会检索出该实体名称相关的列表(一个三元组结构),然后我们需要检索出 query 对应具体的那条记录(可以看成文本相似度问题-> 转为二分类问题求相似)
- 启动在线服务应用服务
python3 api.py \
--model_dir ./NLPCCKBQA2016_bert_zh_model/best_sim.bin \
--bert_model ../pretrained_models/bert-base-chinese/ \
--max_seq_length 64
- model_dir 训练好文本相似度的模型
- bert_model 预训练模型的路径
- max_seq_length 模型文本最大的句子长度(数据分析获取)
- 测试案例1:API服务文本相似度调用(同一个含义案例)
通过curl 命令行模式执行
curl http://127.0.0.1:8000/v1/predict \
> -H "Content-Type:application/json" \
> -X POST \
> --data '{"text_a": "丽江子馨小院客栈离车站多远啊?","text_b":"汽车站"}'
返回的结果
{
"errno": 0,
"errmsg": "success",
"time": 50,
"data": {
"text_a": "丽江子馨小院客栈离车站多远啊?",
"text_b": "汽车站",
"result": "相同",
"prob": "95.13%"
}
}
- 测试案例2:API服务文本相似度调用(不是同一个含义的案例)
通过curl执行
curl http://127.0.0.1:8000/v1/predict \
> -H "Content-Type:application/json" \
> -X POST \
> --data '{"text_a": "丽江子馨小院客栈离车站多远啊?","text_b":"组织门派"}'
返回结果如下
{
"errno": 0,
"errmsg": "success",
"time": 28,
"data": {
"text_a": "丽江子馨小院客栈离车站多远啊?",
"text_b": "组织门派",
"result": "不同",
"prob": "96.12%"
}
}
5-知识库导入数据库,并抽取三元组
- 5.1-知识库导入数据库
- 5.1.1-三元组数据格式
本篇知识问答实战来源 NLPCC2016 的任务:Open Domain Question Answering;其包含 14,609 个问答对的训练集和包含 9870 个问答对的测试集。并提供一个知识库,包含 6,502,738 个实体、587,875 个属性以及 43,063,796 个三元组。
- 5.1.2-MySQL 安装以及数据表设计
- MySQL 安装以及使用
https://www.jianshu.com/p/2906abd8fd47
https://downloads.mysql.com/archives/community/
https://www.jianshu.com/p/2a23264b63d8
https://blog.csdn.net/memory6364/article/details/82426052
https://blog.csdn.net/skh2015java/article/details/80156278
我们这里设置:root/12345678
- 知识库表设计
create databases KB_QA;
use KB_QA;
CREATE TABLE `nlpcc_qa` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`entity` text CHARACTER SET utf8,
`attr_name` text CHARACTER SET utf8,
`attr_value` text CHARACTER SET utf8,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
字段说明:
entity : 实体
attr_name: 属性名称
attr_value: 属性数值
- 5.1.3-数据导入知识库
我们直接把知识库: 实体-属性名称-属性数值 (三元组) 倒入 mysql 数据库中。
注意: 数据比较大,考虑 4000 万+ 条数据比较大,在进行 mysql 插入数据操作可以进行批量进行插入 。
我们看下知识库的数据:kbqa.kb
空气干燥 ||| 别名 ||| 空气干燥
空气干燥 ||| 中文名 ||| 空气干燥
空气干燥 ||| 外文名 ||| air drying
空气干燥 ||| 形式 ||| 两个
空气干燥 ||| 作用 ||| 将空气中的水份去除
罗育德 ||| 别名 ||| 罗育德
罗育德 ||| 中文名 ||| 罗育德
- 通过命令行导入数据(注:如果数据出现不支持的数据特殊字符会出现错误)
load data local infile '/data01/tt/kbqa.kb' \
into table nlpcc_qa \
CHARACTER SET 'utf8' \
fields terminated by '|||' enclosed by '"' \
ignore 0 lines \
(entity,attr_name,attr_value);
- 通过程序批量数据导入
'''
知识库数据-批量导入
'''
import pymysql
api_server_host = 'localhost'
def connect():
conn = pymysql.connect(
user="root",
password="12345678",
host=api_server_host,
port=3306,
db="KB_QA",
charset="utf8"
)
return conn
def execute_insert():
conn = connect()
cursor = conn.cursor()
# 打开文件,读取所有文件存成列表
with open("kbqa.kb", "r", encoding='utf-8') as file:
"""
kbqa.kb 知识库格式如下(4000万+数据大概15分钟内就可以完成):
空气干燥 ||| 别名 ||| 空气干燥
空气干燥 ||| 中文名 ||| 空气干燥
空气干燥 ||| 外文名 ||| air drying
空气干燥 ||| 形式 ||| 两个
空气干燥 ||| 作用 ||| 将空气中的水份去除
"""
# 可以选择readline或者read的方式,但下面的代码要有所变化
data_list = file.readlines()
# 遍历列表
count = 0
for t in data_list:
t = t.strip()
text = t.split("|||")
# sql语句
sql = "insert into nlpcc_qa(entity,attr_name,attr_value) values (%s,%s,%s)"
# 参数化方式传参
try:
cursor.execute(sql, [text[0], text[1], text[2]])
if count % 100000 == 0:
print('commit......count={}'.format(count))
conn.commit()
except Exception as e:
# 这里错误数据直接忽略-考虑考虑错误的数据放入一个文本,然后check
# print("Error: unable to fecth data: %s ,%s" % (repr(e), sql))
pass
# 显示操作结果
count = count + 1
# 统一提交
conn.commit()
# 关闭游标
cursor.close()
# 关闭连接
conn.close()
if __name__ == '__main__':
execute_insert()
导入后的数据效果如下
- 5.2-返回分数最高的三元组
- query = 高等数学出版社是是哪家?
- 然后通过 NER 识别: 高等数学
- 知识库过程 :根据高高等数学的实体名称去查询,获取多条记录
sql = "select * from nlpcc_qa where entity = '高等数学'
结果如新:
高等数学 出版社 武汉大学出版社
高等数学 书名 高等数学一(微积分)
我们都指导,根据 query,我们的答案是 "武汉大学出版社" ,那么技术解决方案:
query 和 出版社
query 和书名
上述对应的 <question,attr_name> 进行相似度比较,然后获取正确的答案。
- 5.3-预测
NER 模型和文本相似度模型
NER 模型: kbqa_bert_zh_model/pytorch_model.bin
文本相似度模型: NLPCCKBQA2016_bert_zh_model/best_sim.bin
训练好上述两个模型后,就可以直接使用了,这里提供对应的服务供调用(也可以单独提供一个api的web 服务,同时加载两个模型,实际这样效率会高点)
- 服务对应:8080 端口
python3 api.py \
--model_name_or_path ../pretrained_models/bert-base-chinese/ \
--task NLPCC2016KBQA \
--model_type bert \
--model_dir kbqa_bert_zh_model \
--use_crf
curl http://127.0.0.1:8000/predict \
-H "Content-Type:application/json" \
-X POST \
--data '{"text": "王平的出生日期是哪一年呀?","lang":"zh"}'
- 服务对应:8081 端口
python3 api.py \
--model_dir ./NLPCCKBQA2016_bert_zh_model/best_sim.bin \
--bert_model ../pretrained_models/bert-base-chinese/ \
--max_seq_length 64
curl http://127.0.0.1:8001/v1/predict \
-H "Content-Type:application/json" \
-X POST \
--data '{"text_a": "王平的出生日期是哪一年呀","text_b":"出生日期"}'
6-基于知识库QA检索
通过前面内容,已经提供了满足QA 检索的服务,下面我们按照下面的过程就可以提供服务了.
- query = xxxxx
- query->调用NER 模型,返回多个元组列表
- 多个元组列表-> 依次通过文本相似度模型进行打分,提取相关度最高的三元组为符合条件的内容,然后抽取predicate.
- 根据实体名称subject + 关系predicate-> 知识库检索答案
- 6.1-搭建QA检索服务
下面是上述步骤的完整代码过程如下
import requests
import json
import pymysql
aheaders = {'Content-Type': 'application/json'}
api_server_host = 'localhost'
def connect():
conn = pymysql.connect(
user="root",
password="12345678",
host=api_server_host,
port=3306,
db="KB_QA",
charset="utf8"
)
return conn
def close(conn):
if conn:
conn.close()
def execute_sql(conn, sql):
cursor = conn.cursor()
try:
cursor.execute(sql)
results = cursor.fetchall()
except Exception as e:
print("Error: unable to fecth data: %s ,%s" % (repr(e), sql))
finally:
cursor.close()
return results
def get_ner_list(json_data):
'''
curl http://127.0.0.1:8000/predict \
-H "Content-Type:application/json" \
-X POST \
--data '{"text": "王平的出生日期是哪一年呀?","lang":"zh"}'
'''
url = "http://{}:8000/predict".format(api_server_host)
response = requests.post(url, headers=aheaders, data=json.dumps(json_data))
json_ner_result = json.loads(response.text)
entities = [] # 实体列表
entity = [] # 每个实体临时变量
for data in json_ner_result['data']:
word = data['word']
tag = data['tag']
if tag != 'O':
entity.append(word)
else:
if len(entity) > 0:
entities.append("".join(entity))
entity = []
if len(entity) > 0:
entities.append("".join(entity))
return entities
def get_similarity(text_a, text_b):
"""
curl http://127.0.0.1:8001/v1/predict \
-H "Content-Type:application/json" \
-X POST \
--data '{"text_a": "王平的出生日期是哪一年呀","text_b":"出生日期"}'
"""
url = "http://{}:8001/v1/predict".format(api_server_host)
input_json = {'text_a': text_a, 'text_b': text_b}
response = requests.post(url, headers=aheaders, data=json.dumps(input_json))
json_sim_result = json.loads(response.text)
if json_sim_result['data']['result'] == '相同':
return json_sim_result
else:
return None
def search(query):
print('query = ',query)
json_ner = {
"text": query,
"lang": "zh"
}
# 1. ner模型获取实体名称
entities = get_ner_list(json_ner)
# 2. 实体名称-> 知识库检索 得到三元组
conn = connect()
#
for entity in entities:
print('entity = ',entity)
sql = "select * from nlpcc_qa where entity = '{}'".format(entity)
data_list = execute_sql(conn, sql)
print('*********** 识别实体名称: [{}] 检索的三元组列表 文本相似度比较***********'.format(entity))
max_sim_text = ''
max_prob = 0.
for index, spo in enumerate(data_list):
text_a = json_ner['text']
text_b = spo[2]
json_sim_result = get_similarity(text_a, text_b)
if json_sim_result:
print(json_sim_result['data'])
prob = json_sim_result['data']['prob']
prob = float(prob.split('%')[0])
if prob > max_prob:
max_prob = prob
max_sim_text = json_sim_result
# 最终的结果
print('*********** 识别实体名称: [{}] 检索的三元组列表 文本相似度比较 最终结果***********'.format(entity))
predicate = max_sim_text['data']['text_b']
print('predicate = ',predicate)
# 3。 entitiy + predicate -> 知识库检索给出最终的答案
print('*********** 识别实体名称: [{}] , 关系: [{} ] 知识库检索答案 ***********'.format(entity,predicate))
sql = "select * from nlpcc_qa where entity = '{}' and attr_name = '{}' ".format(entity,predicate)
data_list = execute_sql(conn, sql)
print('*********** 知识库检索答案 结果如下 ')
for data in data_list:
entity = data[1]
attr_name = data[2]
attr_value = data[3]
print('search result: {} - {} - {}' .format(entity,attr_name,attr_value))
#
conn.close()
if __name__ == '__main__':
#query = "谁知道中华人民共和国环境保护部的职能是什么?"
query = "奥巴马毕业哪个大学"
search(query)
- 6.2-案例分析
- 案例测试1: query = 谁知道中华人民共和国环境保护部的职能是什么?
- 案例测试2: query = 北京大学的地址在哪里?
- 案例测试3:query = 杨幂有多重?
- 案例测试4:query = 中国的国土面积是多少
- 案例测试5:query = 奥巴马毕业哪个大学
7-总结
我们在上述内容基础上,提出一些可以优化的知识点,供参考。
- 知识库需要不断更新,知识库可以导入 图数据库,快速检索
- 关于命名实体识别部分可以增加规则,提升效果(基于知识库过滤所有的实体,检索过程通过AC自动机快速检索)
- 文本相似度文本,可以把识别的实体进行隐藏,重新训练模型查看效果
- 根据query 识别出的实体,在知识库检索目前是精确匹配,也可以考虑基于语义进行匹配,提供知识库检索的召回内容