一、ChatGPT结合知识图谱
在本专栏的前面文章中构建 ChatGPT
本地知识库问答应用,都是基于词向量检索 Embedding
嵌入的方式实现的,在传统的问答领域中,一般知识源采用知识图谱来进行构建,但基于知识图谱的问答对于自然语言的处理却需要耗费大量的人力和物力,而 ChatGPT
本身就拥有强大的自然语言处理能力,如果将ChatGPT
和知识图谱相结合岂不是更加强大,本文和下篇文章探索将 ChatGPT
结合知识图谱,构建一个基于医疗问答场景的应用。
什么是知识图谱:
知识图谱是一种用于表示和组织结构化知识的图形化模型。它是一种利用图论和语义网络的技术,旨在捕捉信息之间的关联性和语义含义。在问答领域,知识图谱发挥着重要作用。
首先,知识图谱以实体和关系的形式存储知识。实体代表现实世界中的具体事物,例如人、地点、事件等,而关系则描述这些实体之间的联系,例如居住在、发生在等。通过将实体和关系连接为节点和边,知识图谱能够形成一个复杂的网络,反映出知识之间的复杂关系。
其次,知识图谱通过为每个实体和关系添加语义标签,使得计算机能够理解和推理这些知识。这意味着知识图谱能够帮助机器理解实体之间的语义关系,从而回答用户提出的问题。例如,当用户询问“谁是美国第一位总统?”时,知识图谱可以识别到实体“美国”和“总统”,并根据关系“第一位”来回答这个问题。
知识图谱采用经典的 neo4j
图数据来进行构建,在实验前请安装好 neo4j
环境:
本篇文章主要讲述如何构建知识图谱,问答的实现流程在本专栏下篇文章中,文章地址如下:
ChatGPT结合知识图谱构建医疗问答应用 (二) - 构建问答流程
二、医疗数据集
医疗数据集,采用 github
上 刘焕勇
老师 分享的数据集,下载地址:
https://github.com/wangle1218/QASystemOnMedicalKG/blob/master/data/medical.json
数据为 JSON
格式,示例如下:
{"_id":{"$oid":"5bb578b6831b973a137e3ee6"},"name":"肺泡蛋白质沉积症","desc":"肺泡蛋白质沉积症(简称PAP),又称Rosen-Castle-man-Liebow综合征,是一种罕见疾病。该病以肺泡和细支气管腔内充满PAS染色阳性,来自肺的富磷脂蛋白质物质为其特征,好发于青中年,男性发病约3倍于女性。","category":["疾病百科","内科","呼吸内科"],"prevent":"1、避免感染分支杆菌病,卡氏肺囊肿肺炎,巨细胞病毒等。\n2、注意锻炼身体,提高免疫力。","cause":"病因未明,推测与几方面因素有关:如大量粉尘吸入(铝,二氧化硅等),机体免疫功能下降(尤其婴幼儿),遗传因素,酗酒,微生物感染等,而对于感染,有时很难确认是原发致病因素还是继发于肺泡蛋白沉着症,例如巨细胞病毒,卡氏肺孢子虫,组织胞浆菌感染等均发现有肺泡内高蛋白沉着。\n虽然启动因素尚不明确,但基本上同意发病过程为脂质代谢障碍所致,即由于机体内,外因素作用引起肺泡表面活性物质的代谢异常,到目前为止,研究较多的有肺泡巨噬细胞活力,动物实验证明巨噬细胞吞噬粉尘后其活力明显下降,而病员灌洗液中的巨噬细胞内颗粒可使正常细胞活力下降,经支气管肺泡灌洗治疗后,其肺泡巨噬细胞活力可上升,而研究未发现Ⅱ型细胞生成蛋白增加,全身脂代谢也无异常,因此目前一般认为本病与清除能力下降有关。","symptom":["紫绀","胸痛","呼吸困难","乏力","毓卓"],"yibao_status":"否","get_prob":"0.00002%","get_way":"无传染性","acompany":["多重肺部感染"],"cure_department":["内科","呼吸内科"],"cure_way":["支气管肺泡灌洗"],"cure_lasttime":"约3个月","cured_prob":"约40%","cost_money":"根据不同医院,收费标准不一致,省市三甲医院约( 8000——15000 元)","check":["胸部CT检查","肺活检","支气管镜检查"],"recommand_drug":[],"drug_detail":[]
}
其中数据集中常用的字段解释:
字段 | 说明 |
---|---|
name | 疾病名称 |
desc | 疾病简介 |
category | 分类 |
prevent | 预防措施 |
cause | 疾病病因 |
symptom | 疾病症状 |
yibao_status | 是否支持医保 |
get_prob | 发病率 |
get_way | 传染性 |
acompany | 并发症 |
cure_department | 医疗科目 |
cure_way | 治疗方式 |
cure_lasttime | 治疗周期 |
cured_prob | 治愈概率 |
cost_money | 大概花费 |
check | 诊断检查项目 |
recommand_drug | 建议用药 |
drug_detail | 药物详细信息 |
easy_get | 疾病易感人群 |
not_eat | 不适宜吃的食物 |
recommand_eat | 建议吃的食物 |
common_drug | 一般用药 |
三、知识图谱结构规划
由于数据集主要是围绕病症来衍生的,因此除了疾病的属性会多些其余均是为建立关系而创建。
3.1 实体规划
疾病实体(disease)
字段 | 说明 |
---|---|
name | 疾病名称 |
desc | 疾病简介 |
prevent | 预防措施 |
cause | 疾病病因 |
get_prob | 发病率 |
get_way | 传染性 |
cure_lasttime | 治疗周期 |
cured_prob | 治愈概率 |
cost_money | 大概花费 |
科室实体(department)
字段 | 说明 |
---|---|
name | 科室名称 |
疾病症状实体 (symptom)
字段 | 说明 |
---|---|
name | 疾病症状 |
治疗方式实体 (cureWay)
字段 | 说明 |
---|---|
name | 治疗方式 |
检查项目实体 (check)
字段 | 说明 |
---|---|
name | 检查项目 |
用药药物实体 (drug)
字段 | 说明 |
---|---|
name | 药物名称 |
易感染人群实体 (crowd)
字段 | 说明 |
---|---|
name | 感染人群 |
食物实体 (food)
字段 | 说明 |
---|---|
name | 食物 |
3.2 关系规划
开始实体 | 关系 | 结束实体 |
---|---|---|
疾病(disease) | 疾病科室关系 (diseaseDepartmentRelations) | 科室实体(department) |
疾病(disease) | 疾病症状关系 (diseaseSymptomRelation) | 疾病症状实体 (symptom) |
疾病(disease) | 疾病治疗关系 (diseaseCureWayRelation) | 治疗方式实体 (cureWay) |
疾病(disease) | 疾病检查项目关系 (diseaseCheckRelation) | 检查项目实体 (check) |
疾病(disease) | 疾病用药关系 (diseaseDrugRelation) | 药物实体 (drug) |
疾病(disease) | 疾病易感染人群关系 (diseaseCrowdRelation) | 易感染人群实体 (crowd) |
疾病(disease) | 疾病宜吃食物关系 (diseaseSuitableFoodRelation) | 食物实体 (food) |
疾病(disease) | 疾病忌吃食物关系 (diseaseTabooFoodRelation) | 食物实体 (food) |
疾病(disease) | 疾病并发症关系 (diseaseDiseaseRelation) | 疾病(disease) |
四、知识图谱构建
在里采用 Python 语言构建,需要安装 py2neo
库:
pip install py2neo -i https://pypi.tuna.tsinghua.edu.cn/simple
from py2neo import Graph
import os
from tqdm import tqdm
import jsonclass CreateKG():def __init__(self, kg_host, kg_port, kg_user, kg_password, data_path):self.graph = Graph(host=kg_host,http_port=kg_port,user=kg_user,password=kg_password)if not data_path or data_path == '':raise Exception("数据集地址为空")if not os.path.exists(data_path):raise Exception("数据集不存在")self.data_path = data_pathdef saveEntity(self, label, data):print("\n写入实体:", label)for item in tqdm(data, ncols=80):try:property = []for key, value in item.items():value = value.replace("'", "")property.append(key + ":" + "'" + value + "'")if len(property) == 0:continuecql = "MERGE(n:" + label + "{" + ",".join(property) + "})"self.graph.run(cql)except Exception as e:passdef saveRelation(self, s_label, e_label, label, data):print("\n写入关系:", label)for item in tqdm(data, ncols=80):try:s_name = item["s_name"]e_name = item["e_name"]cql = "MATCH(p:" + s_label + "),(q:" + e_label + ") WHERE p.name='" + s_name + "' AND q.name='" + e_name + "' MERGE (p)-[r:" + label + "]->(q)"self.graph.run(cql)except Exception as e:passdef getValue(self, key, data):if key in data:return data[key]return ""def init(self):# 实体# 疾病diseases = []# 科室departments = []# 疾病症状symptoms = []# 治疗方式cureWays = []# 检查项目checks = []# 药物drugs = []# 易感染人群crowds = []# 食物foods = []# 关系# 疾病科室diseaseDepartmentRelations = []# 疾病症状diseaseSymptomRelations = []# 疾病治疗diseaseCureWayRelations = []# 疾病检查diseaseCheckRelations = []# 疾病用药diseaseDrugRelations = []# 疾病易感染人群diseaseCrowdRelations = []# 疾病宜吃食物diseaseSuitableFoodRelations = []# 疾病忌吃食物diseaseTabooFoodRelations = []# 疾病并发症diseaseDiseaseRelations = []print("====数据抽取======")with open(self.data_path, 'r', encoding='utf8') as f:for line in tqdm(f.readlines(), ncols=80):data = json.loads(line)# 疾病实体disease = {"name": data["name"],"desc": self.getValue("desc", data),"prevent": self.getValue("prevent", data),"cause": self.getValue("cause", data),"get_prob": self.getValue("get_prob", data),"get_way": self.getValue("get_way", data),"cure_lasttime": self.getValue("cure_lasttime", data),"cured_prob": self.getValue("cured_prob", data),"cost_money": self.getValue("cost_money", data),}diseases.append(disease)# 科室if "cure_department" in data:for department in data["cure_department"]:# 疾病科室关系diseaseDepartmentRelations.append({"s_name": data["name"],"e_name": department})# 科室实体property = {"name": department}if property not in departments:departments.append(property)# 症状if "symptom" in data:for symptom in data["symptom"]:# 疾病科室关系diseaseSymptomRelations.append({"s_name": data["name"],"e_name": symptom})# 症状实体property = {"name": symptom}if property not in symptoms:symptoms.append(property)# 治疗方式if "cure_way" in data:for cure_way in data["cure_way"]:# 疾病科室关系diseaseCureWayRelations.append({"s_name": data["name"],"e_name": cure_way})# 治疗方式实体property = {"name": cure_way}if property not in cureWays:cureWays.append(property)# 检查项目if "check" in data:for check in data["check"]:# 疾病科室关系diseaseCheckRelations.append({"s_name": data["name"],"e_name": check})# 检查项目实体property = {"name": check}if property not in checks:checks.append(property)# 一般用药if "common_drug" in data:for common_drug in data["common_drug"]:# 疾病科室关系diseaseDrugRelations.append({"s_name": data["name"],"e_name": common_drug})# 用药实体property = {"name": common_drug}if property not in drugs:drugs.append(property)# 易感染人群if "easy_get" in data:easy_get = data["easy_get"]# 疾病科室关系diseaseCrowdRelations.append({"s_name": data["name"],"e_name": easy_get})# 易感染人群实体property = {"name": easy_get}if property not in crowds:crowds.append(property)# 宜吃食物if "recommand_eat" in data:for recommand_eat in data["recommand_eat"]:# 疾病科室关系diseaseSuitableFoodRelations.append({"s_name": data["name"],"e_name": recommand_eat})# 食物实体property = {"name": recommand_eat}if property not in foods:foods.append(property)# 忌吃食物if "not_eat" in data:for not_eat in data["not_eat"]:# 疾病科室关系diseaseTabooFoodRelations.append({"s_name": data["name"],"e_name": not_eat})# 食物实体property = {"name": not_eat}if property not in foods:foods.append(property)# 并发症if "acompany" in data:for acompany in data["acompany"]:# 疾病科室关系diseaseDiseaseRelations.append({"s_name": data["name"],"e_name": acompany})# 疾病self.saveEntity("disease", diseases)# 科室self.saveEntity("department", departments)# 疾病症状self.saveEntity("symptom", symptoms)# 治疗方式self.saveEntity("cureWay", cureWays)# 检查项目self.saveEntity("check", checks)# 药物self.saveEntity("drug", drugs)# 易感染人群self.saveEntity("crowd", crowds)# 食物self.saveEntity("food", foods)# 关系# 疾病科室self.saveRelation("disease", "department", "diseaseDepartmentRelations", diseaseDepartmentRelations)# 疾病症状self.saveRelation("disease", "symptom", "diseaseSymptomRelation", diseaseSymptomRelations)# 疾病治疗self.saveRelation("disease", "cureWay", "diseaseCureWayRelation", diseaseCureWayRelations)# 疾病检查self.saveRelation("disease", "check", "diseaseCheckRelation", diseaseCheckRelations)# 疾病用药self.saveRelation("disease", "drug", "diseaseDrugRelation", diseaseDrugRelations)# 疾病易感染人群self.saveRelation("disease", "crowd", "diseaseCrowdRelation", diseaseCrowdRelations)# 疾病宜吃食物self.saveRelation("disease", "food", "diseaseSuitableFoodRelation", diseaseSuitableFoodRelations)# 疾病忌吃食物self.saveRelation("disease", "food", "diseaseTabooFoodRelation", diseaseTabooFoodRelations)# 疾病并发症self.saveRelation("disease", "disease", "diseaseDiseaseRelation", diseaseDiseaseRelations)if __name__ == '__main__':kg_host = "127.0.0.1"kg_port = 7474kg_user = "neo4j"kg_password = "123456"data_path = "./data/medical.json"kg = CreateKG(kg_host, kg_port, kg_user, kg_password, data_path)kg.init()
运行之后可以看到处理的进度:
等待处理结束后就可以在图谱中看到构建后的效果了:
五、数据探索测试
鼻炎
的病症描述:
match (n:disease) where n.name = '鼻炎' return n.desc
鼻炎
所属的科室:
match (n:disease)-[e:diseaseDepartmentRelations]->(n1:department) where n.name = '鼻炎' return n,n1
鼻炎
的症状:
match (n:disease)-[e:diseaseSymptomRelation]->(n1:symptom) where n.name = '鼻炎' return n,n1
鼻炎
的治疗方式:
match (n:disease)-[e:diseaseCureWayRelation]->(n1:cureWay) where n.name = '鼻炎' return n,n1
鼻炎
应该用什么药:
match (n:disease)-[e:diseaseDrugRelation]->(n1:drug) where n.name = '鼻炎' return n,n1