跳转到内容

医疗知识图谱的搭建

  • 理解如何通过图数据库构建医疗知识图谱
  • 学会如何将结构化的医疗数据转化为知识图谱
  • 掌握如何使用Python代码与Neo4j交互,构建实体和关系

  • 步骤1: 选择图数据库
  • 步骤2: 图谱搭建
  • 步骤3: 图谱展示

  • 图数据库是存储和管理知识图谱的核心。选择合适的数据库工具对图谱的效率和可扩展性有重要影。
    • Neo4j:最常用的图数据库,支持Cypher查询语言,适合大规模医疗知识图谱的构建和查询。
    • RDF-based数据库(了解):如GraphDB、Virtuoso,适合处理语义网标准的知识图谱,如使用RDF、OWL等格式。
  • 相比传统Mysql等数据,图数据库的优势:
    • Neo4j图数据库能高效处理复杂关系、灵活的数据结构、简洁的查询语言、适合大规模图数据和推理能力等特点。而MySQL等关系型数据库在面对多层次、动态变化的复杂关联时,表现不佳,查询性能较差,且模型的扩展性较低。因此,构建医疗知识图谱时,选择Neo4j这样的图数据库会更有优势。

  • 前面章节,我们已经学习了如何从文本中抽取实体、以及关系。那么如果要想搭建图谱,需要提前准备好相应的实体和关系等数据。
  • 说明:
    • 由于企业数据隐私性,本次搭建图谱我们将基于一个开源的医疗数据集来实现图谱的搭建。该数据集名称为:medical.json,对应存放位置:./NLP/MedicalKB/graph_data/medical.json
    • 整个图谱搭建的代码实现集中于一个py脚本,该脚本中共包含:一个方法和一个类;
      • 方法:用于查询medical.json的数量以及数据格式
      • 类:用于实现三元组抽取、实体插入、关系插入等
    • 代码路径:./NLP/MedicalKB/build_kg_utils.py

  • 目标:读取并理解原始医疗数据(如JSON文件中的疾病、症状、药品等信息)。

  • 任务:解析medical.json文件,展示数据的结构,准备好用于构建图谱的实体和关系。

  • 构造方法:print_data_info()

  • 代码实现:

    def print_data_info(data_path):
    '''
    展示数据格式
    '''
    i = 0
    with open(data_path, 'r', encoding='utf8') as f:
    lines = f.readlines()
    print(f'数据长度:{len(lines)}') # 8808条样本
    for line in lines:
    data = json.loads(line)
    print(json.dumps(data, sort_keys=True, indent=4, separators=(', ', ': '), ensure_ascii=False))
    i += 1
    if i >= 5:
    break
    # 运行该函数,展示数据内容:
    data_path = "./graph_data/medical.json"
    print_data_info(data_path)
  • 结果展示:

    • medical.json共包含8808条样本,每个样本格式为字典样式,每个键都代表了与医疗相关的信息,它们的中文意义解释如下:
    {
    # 数据库中的唯一标识符。
    "_id": {
    "$oid": "5bb578b6831b973a137e3ee8"
    },
    # 伴随疾病或并发症。
    "acompany": [
    "贫血"
    ],
    # 疾病的分类,表明疾病所属的类别和科室。
    "category": [
    "疾病百科",
    "急诊科"
    ],
    # 疾病的病因
    "cause": "吸入苯蒸气或皮肤接触苯而引起的中毒,有急性、慢性之分。急性苯中毒主要对中枢神经系统产生麻醉作用,出现昏迷和肌肉抽搐;高浓度的苯对皮肤有刺激作用。长期接角低浓度的苯可引起慢性苯中毒,出现造血障碍,早期常见血白细胞数减低,进而出现血小板数减少和贫血。患者可有鼻出血、牙龈出血、皮下出血、月经过多等临床表现。\n急性苯中毒多为误服或急性吸入含苯的有机溶剂所致。这些有机溶剂包括油漆、稀料、工业胶水等。慢性苯中毒是长期吸入低浓度苯及代谢产物酚类所致。",
    # 检查方法,列出确诊该疾病需要进行的医学检查项目。
    "check": [
    "血常规",
    "骨髓象分析",
    "先令氏指数"
    ],
    # 推荐药品
    "common_drug": [
    "布美他尼片",
    "十一味金色丸"
    ],
    # 治疗费用,表示在不同医院治疗此疾病的费用范围。
    "cost_money": "根据不同病情,不同医院,收费标准不一致,市三甲医院约(5000——8000元)",
    # 治疗科室,列出推荐治疗该疾病的医院科室
    "cure_department": [
    "急诊科"
    ],
    # 治疗持续时间,表示从诊断到治愈所需的大概时间。
    "cure_lasttime": "根据不同病情一般为2-4周",
    # 治疗方式,列出常用的治疗方案或方法。
    "cure_way": [
    "药物治疗",
    "支持性治疗"
    ],
    # 治愈概率,指治愈该疾病的预期成功率。
    "cured_prob": "约75%",
    # 疾病描述,简要说明该疾病的概况和特征。
    "desc": "苯(benzene)是从煤焦油分馏及石油裂解所得的一种芳香烃化合物,系无色有芳香气味的油状液体。挥发甚速,易燃易爆。工业上用作溶剂、稀释剂和化工原料。苯属中等毒类,可引起急性或慢性中毒。\n急性苯中毒是指口服含苯的有机溶剂或吸入高浓度苯蒸气后,出现以中枢神经系统麻醉作用为主要表现的病理生理过程,主要症状轻者为醉酒状,步态不稳,哭笑失常,重者意识丧失,抽搐,可因呼吸中枢麻痹或循环衰竭死亡。慢性苯中毒是指苯及其代谢产物酚类影响了骨髓的造血功能,临床表现为白细胞计数和血小板立秋持续减少,最终发展为再生障碍性贫血或白血病。也可影响神经系统,表现为神经衰弱和自主神经功能紊乱。",
    # 易吃食物
    "do_eat": [
    "鸡蛋",
    "大豆",
    "猪肉(瘦)",
    "樱桃番茄"
    ],
    # 药物详情,列出该疾病相关的推荐药物。
    "drug_detail": [
    "桂林南药布美他尼片(布美他尼片)",
    "雄巴拉曲神水十一味金色丸(十一味金色丸)",
    "西藏甘露仁青芒觉(仁青芒觉)",
    "金诃藏药大月晶丸(大月晶丸)",
    "金诃藏药仁青芒觉(仁青芒觉)"]
    # 容易患病
    "easy_get": "多见于制鞋工人及接触化工染料的工人",
    # 患病概率,指该疾病在人群中的发生概率。
    "get_prob": "0.065%",
    # 传播途径,描述该疾病的传染性和传播方式。
    "get_way": "无传染性",
    # 疾病名称,表示该疾病的正式名称。
    "name": "苯中毒",
    # 忌吃食物
    "not_eat": [
    "海蟹",
    "海虾",
    "海参(水浸)",
    "辣椒(青、尖)"
    ],
    # 预防措施,列出预防该疾病的建议或注意事项。
    "prevent": "对于急性中毒患者,可以立即脱离现场至空气新鲜处,脱去污染的衣着,并用肥皂水或清水冲洗污染的皮肤。口服中毒者,要给患者洗胃,中毒者应卧床静息,并接受对症、支持治疗,可给予葡萄糖醛酸。要注意防止患者出现脑水肿,切记勿给心搏未停者使用肾上腺素,对由于苯中毒引起的再生障碍性贫血症患者,可给予小量多次输血及糖皮质激素治疗。",
    # 推荐药物,列出治疗该疾病的常用药物。
    "recommand_drug": [
    "布美他尼片",
    "十一味金色丸",
    "注射用布美他尼",
    "注射用呋塞米",
    "盐酸利多卡因注射液"]
    # 推荐食物
    "recommand_eat": [
    "豆腐干炒韭菜",
    "素炒小白菜",
    "白菜蛋花粥"
    ],
    # 症状,列出该疾病的典型临床症状。
    "symptom": [
    "恶心",
    "抽搐",
    "感觉障碍"
    ],
    # 医保状态,指该疾病的治疗是否被纳入医保。
    "yibao_status": ""
    }

  • 任务:从数据中提取实体及对应的关系

  • 构造类:class MedicalExtractor()

  • 说明:本次我们基于medical.json数据,提取了4种实体,以及4种关系。

    • 实体类型:

      疾病:diseases
      症状:symptoms
      食物:foods
      药品:drugs
    • 关系类型

      疾病-症状:[disease,'has_symptom', symptom]
      疾病-忌吃食物:[disease,'not_eat', food]
      疾病-易吃食物:[disease,'do_eat', food]
      疾病-推荐药品:[disease,'recommand_drug', drug]
  • 代码实现:

    class MedicalExtractor(object):
    def __init__(self):
    super(MedicalExtractor, self).__init__()
    self.graph = Graph("http://localhost:7474", auth=("neo4j", "12345"))
    # 共4类节点
    self.drugs = [] # 药品
    self.foods = [] # 食物
    self.diseases = [] #疾病
    self.symptoms = []#症状
    # 构建节点实体关系
    self.rels_noteat = [] # 疾病-忌吃食物关系
    self.rels_doeat = [] # 疾病-宜吃食物关系
    self.rels_recommanddrug = [] # 疾病-热门药品关系
    self.rels_symptom = [] # 疾病症状关系
    def extract_triples(self, data_path):
    print("从json文件中转换抽取三元组")
    with open(data_path,'r',encoding='utf8') as f:
    for line in tqdm(f.readlines(), ncols=80):
    data_json = json.loads(line)
    disease = data_json['name']
    self.diseases.append(disease)
    if 'symptom' in data_json:
    self.symptoms += data_json['symptom']
    for symptom in data_json['symptom']:
    self.rels_symptom.append([disease,'has_symptom', symptom])
    if 'acompany' in data_json:
    for acompany in data_json['acompany']:
    self.diseases.append(acompany)
    if 'recommand_drug' in data_json:
    recommand_drug = data_json['recommand_drug']
    self.drugs += recommand_drug
    for drug in recommand_drug:
    self.rels_recommanddrug.append([disease,'recommand_drug', drug])
    if 'not_eat' in data_json:
    not_eat = data_json['not_eat']
    for _not in not_eat:
    self.rels_noteat.append([disease,'not_eat', _not])
    self.foods += not_eat
    do_eat = data_json['do_eat']
    for _do in do_eat:
    self.rels_doeat.append([disease,'do_eat', _do])
    self.foods += do_eat
    if 'drug_detail' in data_json:
    for det in data_json['drug_detail']:
    det_spilt = det.split('(')
    if len(det_spilt) == 2:
    p, d = det_spilt
    d = d.rstrip(')')
    self.drugs.append(d)
    else:
    d = det_spilt[0]
    self.drugs.append(d)

  • 任务:将提取的实体插入到Neo4j图数据库

  • 构造方法:在上述定义的类内,构造两个函数分别为write_nodes() 和create_nodes()

  • write_nodes函数实现:

    • 用于构造cypher语句,将实体写入数据库
    def write_nodes(self,entitys, entity_type):
    print("写入 {0} 实体".format(entity_type))
    for node in tqdm(set(entitys), ncols=80):
    cql = """MERGE(n:{label}{{name:'{entity_name}'}})""".format(
    label=entity_type, entity_name=node.replace("'",""))
    try:
    self.graph.run(cql)
    except Exception as e:
    print(e)
    print(cql)
  • create_nodes函数实现

    • 调用wirte_nodes函数,用于将所有实体统一写入数据库
    def create_entitys(self):
    self.write_nodes(self.drugs, '药品')
    self.write_nodes(self.symptoms, '菜谱')
    self.write_nodes(self.foods, '食物')
    self.write_nodes(self.diseases,'疾病')

  • 任务:将实体之间的关系插入到Neo4j图数据库。

  • 构造方法:在上述定义的类内,构造两个函数分别为write_edges() 和 create_relations()

  • write_edges函数实现:

    • 用于构造cypher语句,将实体对应关系写入数据库
    def write_edges(self,triples,head_type,tail_type):
    print("写入 {0} 关系".format(triples[0][1]))
    for head,relation,tail in tqdm(triples,ncols=80):
    cql = """MATCH(p:{head_type}),(q:{tail_type})
    WHERE p.name='{head}' AND q.name='{tail}'
    MERGE (p)-[r:{relation}]->(q)""".format(
    head_type=head_type,tail_type=tail_type,head=head.replace("'",""),
    tail=tail.replace("'",""),relation=relation)
    try:
    self.graph.run(cql)
    except Exception as e:
    print(e)
    print(cql)
  • create_relations()函数实现:

    • 调用write_edges函数,用于将所有实体关系统一写入数据库
    def create_relations(self):
    self.write_edges(self.rels_noteat, '疾病', '食物')
    self.write_edges(self.rels_doeat, '疾病', '食物')
    self.write_edges(self.rels_symptom, '疾病', '症状')
    self.write_edges(self.rels_recommanddrug, '疾病', '药品')

  • 下图是基于Neo4j数据库导入SPO三元组后展示效果:



  • 本小节讲解了如何选择图数据库构建医疗知识图谱,并通过定义实体和关系、导入数据及使用查询语言进行操作,完成图谱的搭建和展示。