知识图谱构建实例


1. 前期准备

知识图谱一般用到的数据库是neo4j

neo4j可以从官网中下载,但官网链接下载需要注册账号,嫌麻烦的也可以从这里下载

neo4j下载后解压即可使用,网上对于java环境的安装和使用有很多教程,这里不多加阐述。

neo4j还需要依赖java环境,网上对于java环境的安装也有很多教程,这里也不多加阐述,这里需要注意neo4j的版本和jdk版本的兼容关系。

下面使用py2neo这个python包对neo4j数据库进行操作,这里先导入要用到的package

import os
import json
from py2neo import Node, Graph
from tqdm import tqdm

这里也需要注意py2neo的版本与neo4j的版本的兼容关系

2. 数据处理

现有一份医疗相关的数据集,该数据集由若干个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": []
}

2.1. 设计scheme

根据数据集,可以设计出如下的scheme:

2.2. 定义实体

首先定义中心实体集 diseases

# diseases节点属性
diseases_nodes_attr = ['name', 'desc', 'prevent', 'cause', 'get_prob', 'easy_get',
                       'cure_department', 'cure_way', 'cure_lasttime', 'symptom', 'cured_prob']

# diseases节点集合
diseases_nodes = []

定义其他实体集

nodes_set_dict = {
    'drugs'      : set(),  # 药品
    'producers'  : set(),  # 药品大类
    'foods'      : set(),  # 食物
    'checks'     : set(),  # 检查
    'departments': set(),  # 科室
    'symptoms'   : set(),  # 症状
}

定义每个实体是从原数据的哪些字段中抽取出来

nodes_key_dict = {
    'drugs'      : ['common_drug', 'recommand_drug'],  # 药品
    'producers'  : ['drug_detail'],  # 药品大类
    'foods'      : ['not_eat', 'do_eat', 'recommand_eat'],  # 食物
    'checks'     : ['check'],  # 检查
    'departments': ['cure_department'],  # 科室
    'symptoms'   : ['symptom']  # 症状
}

2.3. 定义关系

定义所有实体间的关系集

edges_set_dict = {
    'acompany'      : set(),  # 疾病并发关系
    'not_eat'       : set(),  # 疾病-忌吃食物关系
    'do_eat'        : set(),  # 疾病-宜吃食物关系
    'recommand_eat' : set(),  # 疾病-推荐吃食物关系
    'common_drug'   : set(),  # 疾病-通用药品关系
    'recommand_drug': set(),  # 疾病-热门药品关系
    'production'    : set(),  # 厂商-药物关系
    'check'         : set(),  # 疾病-检查关系
    'belongs_to'    : set(),  # 科室-科室关系
    'category'      : set(),  # 疾病与科室之间的关系
    'symptom'       : set(),  # 疾病症状关系
}

定义每个关系是从原数据的哪些字段中抽取出来的

edges_key_dict = {
    'acompany'      : ['name', 'acompany'],  # 疾病并发关系
    'not_eat'       : ['name', 'not_eat'],  # 疾病-忌吃食物关系
    'do_eat'        : ['name', 'do_eat'],  # 疾病-宜吃食物关系
    'recommand_eat' : ['name', 'recommand_eat'],  # 疾病-推荐吃食物关系
    'common_drug'   : ['name', 'common_drug'],  # 疾病-通用药品关系
    'recommand_drug': ['name', 'recommand_drug'],  # 疾病-热门药品关系
    'production'    : ['drug_detail', 'drug_detail'],  # 厂商-药物关系
    'check'         : ['name', 'check'],  # 疾病-检查关系
    'belongs_to'    : ['cure_department', 'cure_department'],  # 科室-科室关系
    'category'      : ['name', 'cure_department'],  # 疾病与科室之间的关系
    'symptom'       : ['name', 'symptom'],  # 疾病症状关系
}

定义每个关系的三元组

edges_tuple_dict = {
    'acompany'      : ['diseases', 'diseases', '并发症'],  # 疾病并发关系
    'not_eat'       : ['diseases', 'foods', '忌吃'],  # 疾病-忌吃食物关系
    'do_eat'        : ['diseases', 'foods', '宜吃'],  # 疾病-宜吃食物关系
    'recommand_eat' : ['diseases', 'foods', '推荐食谱'],  # 疾病-推荐吃食物关系
    'common_drug'   : ['diseases', 'drugs', '常用药品'],  # 疾病-通用药品关系
    'recommand_drug': ['diseases', 'drugs', '好评药品'],  # 疾病-热门药品关系
    'production'    : ['producers', 'drugs', '生产药品'],  # 厂商-药物关系
    'check'         : ['diseases', 'checks', '诊断检查'],  # 疾病-检查关系
    'belongs_to'    : ['departments', 'departments', '属于'],  # 科室-科室关系
    'category'      : ['diseases', 'departments', '所属科室'],  # 疾病与科室之间的关系
    'symptom'       : ['diseases', 'symptoms', '症状'],  # 疾病症状关系
}

2.4. 抽取数据

抽取 diseases 实体集

for data in tqdm(open(data_path, 'r', encoding='utf8')):
    data_json = json.loads(data)
    
    disease_info = {}
    for key in diseases_nodes_attr:
        disease_info[key] = data_json.get(key, '')

    diseases_nodes.append(disease_info)

抽取结果样例

抽取其他实体集

for data in tqdm(open(data_path, 'r', encoding='utf8')):
    data_json = json.loads(data)
    
    for node in nodes_set_dict:
        for key in nodes_key_dict[node]:
            if key in data_json:
                # 特殊处理字段
                if key == 'drug_detail':
                    nodes_set_dict[node] |= set([i.split('(')[0] for i in data_json[key]])

                else:
                    nodes_set_dict[node] |= set(data_json[key])

抽取结果样例

抽取关系集

for data in tqdm(open(data_path, 'r', encoding='utf8')):
    data_json = json.loads(data)
    
    for key in edges_set_dict:
        p_key, q_key = edges_key_dict[key]
        if p_key in data_json and q_key in data_json:
            p, q = data_json[p_key], data_json[q_key]

            if key == 'category':
                if len(q) == 1:
                    edges_set_dict[key].add((p, q[0]))

                elif len(q) == 2:
                    edges_set_dict[key].add((p, q[1]))

            elif key == 'belongs_to':
                if len(q) == 2:
                    big, small = q
                    edges_set_dict[key].add((small, big))

            elif key == 'production':
                edges_set_dict[key] |= set([(i.split('(')[0], i.split('(')[-1].replace(')', '')) for i in q])

            else:
                for _ in q:
                    edges_set_dict[key].add((p, _))

抽取结果样例

kg4

最后将实体保存,方便后序使用

tmp = {k: list(v) for k, v in nodes_set_dict.items()}
tmp['diseases'] = [_['name'] for _ in diseases_nodes]

with open('data/dict.json', 'w', encoding='utf8') as f:
    json.dump(tmp, f, indent=4, ensure_ascii=False)

3. 数据导入

连接neo4j数据库

# 老版本连接语句
graph = Graph(host="127.0.0.1", http_port=7474, user="neo4j", password="123456")
  
# 新版本连接语句
graph = Graph('http://localhost:7474', auth=('neo4j', '123456'))

diseases 实体集入库

for disease_info in tqdm(diseases_nodes):
    node = Node('diseases', **disease_info)
    graph.create(node)

其他实体集入库

for key, values in nodes_set_dict.items():
    print(key)
    for name in tqdm(values):
        node = Node(key, name=name)
        graph.create(node)

关系集入库

for rel_type, edges in edges_set_dict.items():
    start_node, end_node, rel_name = edges_tuple_dict[rel_type]
    print(start_node, end_node, rel_name)
    
    for edge in tqdm(edges):
        p, q = edge
        query = f"""
        match(p:{start_node}),(q:{end_node}) 
        where p.name='{p}' and q.name='{q}' 
        merge (p)-[rel:{rel_name}{{name:'{rel_name}'}}]->(q)
        """
        try:
            graph.run(query)
        except Exception as e:
            print(e)

至此,知识图谱基础部分已经构建完毕


评论
  目录