1 知识图谱
1.1 知识图谱介绍
知识图谱(Knowledge Graph, KG),在图书情报界成为知识域可视化或者知识领域映射地图,是显示知识发展进程与结构关系的一系列各种不同的图像,用可视化技术描述知识资源及其载体,挖掘、分析、构建、绘制和显示知识及他们之间的相互关系。
简而言之,知识图谱就是将不同类型的知识通过他们之间的关联性变成一个网状的结构只是,这个结构结构知识在搜索、问答、NLP、推荐系统的应用中作为基石存在。知识图谱的建设,一般可以简述为如下的几个步骤:数据获取、实体识别、关系抽取、数据存储、图谱应用这几个方面。
为了更快的方便应用,本文中所用的数据库都采用Neo4j数据库作为知识图谱数据库,不同的数据库在查询语言上有所出入。
本文中涉及的编程语言为:python
涉及到的一些python库包括:py2neo==4.3.0(用于py和neo4j进行存储、构建节点、构建边关系、查询)、pyahocorasick(智能问答中用于构建AC自动机来进行分词操作)、streamlit(通过简单代码来展示前端的演示效果,实现ML、DL的快速部署必备。)【不同的streamlit版本差异较小,本文中涉及一个模型缓存的操作,可以根据最新版本装饰器的来也可以根据以往的装饰器来】
1.2 Neo4j的安装的JDK安装
在本项目中采用Neo4j版本为:neo4j-community-4.4.18-windows,在官网下载并且为zip格式,到时候直接解压就行。
Neo4j下载地址:https://neo4j.com/download-center/#community
根据Neo4j的版本发现其对应的JDK版本为11,在官网下载就行:
JDK11下载地址:https://www.oracle.com/java/technologies/downloads/#java11
为了防止版本差异问题,大家按照这个版本进行下载就可以
这部分内比较简单,推荐大家先安装JDK11再安装Neo4j,配置完JDK后,可以在CMD中输入“java -version”查看jdk版本,如果这时候出现一些版本信息就代表成功,如果出现“不是XXX命令”就需要配置一下对应的JDK地址
解压Neo4j的时候,也需要将NEO4J_HOME环境配置到系统环境中,记得也要在path中配置。
系统变量中具体配置如下图所示:
在系统变量中的Path中配置如下信息:
1.3 启动Neo4j
用管理员身份打开CMD:
在CMD中输入:neo4j.bat console
出现“Started”信息就表示neo4j数据库已经启动
打开浏览器,输入http://127.0.0.1:7474/browser/,如下图所示,界面最上方就是交互的输入框。
第一访问时会让你重新设置一下新的账号密码,不要忘记自己设计的密码哦
1.4 Cypher查询
Cypher是Neo4j的图形查询语句,其能力包括:创建节点、更新节点、删除节点和关系,通过模式来查询和修改节点的关系。
现在我们可以初学一下Cypher语句的使用和应用,在代码中我们就会重新介绍一些新的组合了。先展示一下我们的最后预期目标的部分内容:
现在我们修改配置文件,将已经导入的默认图数据库进行更替。(如果你是首次使用就不需要这样操作)
在NEO4J_HOME目录下找到conf目录中的neo4j.conf修改其中的
#dbms.default_database=neo4j
变成
dbms.default_database=hello
然后我们重启一下我们的neo4j,让他更新默认的数据库
重新启动之后,我们就得到了一个干净的数据库啦,可以开始操作了
1.4.1 创建节点
CREATE (n:Person {name:'赵小钱'}) RETURN n
其中,CREATE为创建语句,n表示节点,Person表示该节点的属性为类别为Person
在此基础上继续创建几个节点
1.4.2 查询节点
MATCH (n:Person) RETURN n
1.4.3 创建位置节点
CREATE (n:Location {city:'深圳', province:'广东'});
CREATE (n:Location {city:'桂林', province:'广西'});
CREATE (n:Location {city:'南京', province:'江苏'});
CREATE (n:Location {city:'杭州', province:'浙江'});
这样又创建了4个地址节点,在Neo4j中不同属性的节点将会采用不同的颜色进行标记,所有节点展示如下所示:
1.4.4 创建节点关系
MATCH (a:Person {name:'李小周'}),
(b:Person {name:'周吴'})
MERGE (a)-[:FRIENDS]->(b)
创建对应的关系
在代码中[]表示关系,其中Neo4j还有单项标记这份友谊是单项的
不仅如此,我们还可以在的边关系中加入属性,表示对关系的描述
MATCH (a:Person {name:'赵小钱'}),
(b:Person {name:'钱小孙'})
MERGE (a)-[:FRIENDS {time:2001}]->(b)
其中花括号表示这个关系的描述属性,在图形化的Neo4j中我们可以通过点击按钮看到对应的关系描述
多增加几个关系看看:
MATCH (a:Person {name:'吴郑'}), (b:Person {name:'郑王'}) MERGE (a)-[:FRIENDS {time:2012}]->(b);
MATCH (a:Person {name:'郑王'}), (b:Person {name:'赵小钱'}) MERGE (a)-[:FRIENDS {time:2006}]->(b);
MATCH (a:Person {name:'孙小李'}), (b:Person {name:'李小周'}) MERGE (a)-[:FRIENDS {time:2006}]->(b);
MATCH (a:Person {name:'李小周'}), (b:Person {name:'郑王'}) MERGE (a)-[:MARRIED {time:1998}]->(b);
我们新创建了4条关系,这样我们Neo4j的图形化界面点击Relationship type就可以看到下面的关系了,这样子看看是不是有点知识图谱的味道了。
1.4.5 创建不同节点的关系
MATCH (a:Person {name:'赵小钱'}), (b:Location {city:'深圳'}) MERGE (a)-[:BORN_IN {year:1978}]->(b);
MATCH (a:Person {name:'吴郑'}), (b:Location {city:'南京'}) MERGE (a)-[:BORN_IN {year:1981}]->(b);
MATCH (a:Person {name:'郑王'}), (b:Location {city:'杭州'}) MERGE (a)-[:BORN_IN {year:1960}]->(b);
MATCH (a:Person {name:'周吴'}), (b:Location {city:'桂林'}) MERGE (a)-[:BORN_IN {year:1960}]->(b);
MATCH (a:Person {name:'孙小李'}), (b:Location {city:'南京'}) MERGE (a)-[:BORN_IN {year:1970}]->(b);
这样看节点就更加丰富了,相对于SQL数据库中record记录方式,这种noSQL的更为自由,关系更加有趣。
1.4.5 查询关系
刚才我们通过创建节点弄出来了11个节点和很多不同的关系,接下来我们借助Cypher语句完成节点、关系的查询
查询在南京出生的朋友
MATCH (a:Person)-[:BORN_IN]->(b:Location {city:'南京'}) RETURN a,b
查询复杂一些的关系
MATCH (a)-->() RETURN a # 表示指向其他节点的图,在本次用例中地点节点均没有向外指向,因此不会展示
MATCH (a)--() RETURN a # 表示所有具备关系的节点的图,会将地址信息涵盖进来
MATCH (a)-[r]->() RETURN a.name, type(r) # 将会返回一张表,表示名字和他们之间的关系
1.4.6 生成节点的同时生成关系
CREATE (a:Person {name:'陈哈哈'})-[r:FRIENDS]->(b:Person {name:'李小周'})
这里发现一个问题,有两个李小周任务节点了。我们删除一个李小周试试
MATCH (a:Person) where id(a)=13 DETACH DELETE a
在删除过程中我们发现,在删除一个节点之前要把这个节点的关系都删除掉,其中的DETACH语句实现了该功能。
然后我们将“陈哈哈”和“李小周重新建立关系”
MATCH (a:Person {name:'陈哈哈'}),
(b:Person {name:'李小周'})
MERGE (a)-[:FRIENDS {time:2001}]->(b)
1.4.6 删除有关系节点数据
MATCH (a:Person {name:'李小周'})-[rel]-(b:Person) DELETE a,b,rel
关于Cypher用法的介绍就到这儿,后面在代码介绍中也会有不少的组合语句需要理解。
2 推理规则
这是我们一会要学习的知识图谱的概览,其中的数据采集将会是非常复杂的步骤有趣的是已经收集好了对应的数据。
在智能问答的过程中,我们并不指望我们的模型可以凭空说出一些东西(又不是AIGC任务),因此我们的数据来源都是依赖这个建立好的知识图谱,因此对于提问的方式我们可以参考一些方法,比如关键词切分和识别,这时候我们想到了KMP算法。但是可以在提问中出现多个节点的情况,因此我们采用AC自动机的方法来实现。AC自动机可以理解成高级的KMP算法,判断需要检测的所有关键词是否在提问语句中并返回他的下标。
然后我们将得到的数据进行处理后再反馈到前端界面进行响应集即可。
因此,整体的算法流程设计如下所示:
- 了解语句,提取关键词
- 关键词转换成对应的提问Cypher语句
- 通过语句查询对应的内容
- 将得到的内容进行处理并返回
3 代码介绍
https://github.com/liuhuanyong/QASystemOnMedicalKG
代码模块采用的是刘焕勇老师的代码,代码执行步骤比较简单,易上手,且代码的主要逻辑在于业务和数据处理,一些简单的操作都可以完成。
我在此基础上进行了一些小小的改进,把智能问答机器的回复方式通过前端界面的交互来完成,相对而言看起来更加舒适。
由于代码本身比较久远了,一些需要注意和修改的地方有:
- 将内部的一些neo4j的数据修改成自己的neo4j,一共需要修改两处;
- 有些文件打开时需要将编码格式设置成utf-8,记得在部分的open代码中添加encoding
- 首次加载过程比较缓慢,可以修改内部的数据加载方式帮助模型完成初始化
数据展示:通过代码只展现其中25组不同的关系
为了不再通过脚本的方式进行访问,提供网页的方式给大家用于咨询
import streamlit as st
from chatbot_graph import ChatBotGraph
# @st.cache(allow_output_mutation=True)
# def load_model():
# model = ChatBotGraph()
# return model
# 装饰器用于加载模型到缓存,
@st.cache_resource
def load_model():
model = ChatBotGraph()
return model
def predict(input_text):
model = load_model()
result = model.chat_main(input_text)
return result
if __name__ == '__main__':
answer = "请提问"
st.set_page_config(page_title="处置意见Demo", layout='wide')
st.warning("目前在neo4j中的数据采用的是医疗数据,还未将kb数据进行转换")
st.warning("首次提问会加载模型,需要等待一段时间")
# handler = ChatBotGraph()
left,right = st.columns([4,1])
with left:
title = st.text_input("请输入文本")
with right:
check = st.button("查询处置意见")
if check:
answer = predict(title)
st.write(answer)
4 结果演示
界面整体比较简单
streamlit默认访问:localhost:8501
提问测试:
在Streamlit的代码中把模型加入缓存的好处:
- 不用在每次提问的时候都初始化AC自动机模型【优】需要占用资源【劣】
- 初始化模型只需要一次就可以了,第二个用户访问时就不用在初始化模型了。
5 总结
2023年3月24日经测试,上述代码可以正常运行。
需要修改对应的neo4j地址的文件有:
answer_search.py # 改文件用于将Cpyher进行查询,因此需要访问neo4j进行财讯
build_medicalgraph.py # 用于刚开始创建对应的图节点和边节点信息,第一个执行
优化项:
在question_classifier.py文件中self.wdtype_dict = self.build_wdtype_dict() 可以将wd_dict固定成文件,直接通过json进行加载,可以提升问答助手的加载速度。