构建AI智能体:三十七、从非结构化文本到结构化知识:基于AI的医疗知识图谱构建与探索

简介: 知识图谱是一种用图结构表示实体及其关系的技术,通过三元组(主体-关系-客体)构建语义网络。文章以医疗领域为例,详细介绍了知识图谱的构建流程:数据预处理、实体识别、关系抽取、知识融合、存储与可视化等步骤。知识图谱可应用于智能问答、辅助诊断、药物研发等场景,其结构化特性可弥补大语言模型的不足,二者结合能提升AI系统的准确性和可解释性。文章还展示了基于大模型的医疗知识图谱构建代码示例,涵盖实体识别、关系抽取、图谱存储和智能问答等核心功能,体现了知识图谱在专业领域的实用价值。

 一、初识知识图谱

37.2-苹果知识图谱.png

image.gif       一个简单的例子,如果想要了解“苹果”,常规的做法是去查字典,字典会告诉我们苹果是一种蔷薇科植物的果实,酸甜可口。极其简单,字典只会从最基础的角度孤立的解释这个词语。

       如果获得它更多的属性,比如我们知道红富士的品种特别有名,富含维生素c,也可以制作苹果汁,还能了解到它是乔布斯创立的苹果公司的logo,它是一部手机的名字,它还可以叫iphone,牛顿因为苹果发现了万有引力等等非常丰富的信息,像一部百科全书一样,将苹果与公司、神”、人物、科学事件等概念连接起来,形成一个知识网络。

       知识图谱,就是这样一部机器能够理解和处理的智能百科全书。 它不是简单的数据集合,而是一张巨大的、由相互连接的知识点构成的语义网络。

37.3-知识图谱2-1.jpg image.gif

二、知识图谱的定义

1. 核心定义

       知识图谱是一种用图结构来建模和表示现实世界中实体、概念及其相互关系的技术。它的核心思想是事物的价值不在于其本身,而在于它与其他事物的关系。旨在揭示医学概念之间深层的、结构化的关系,形成一个巨大的语义网络。

2. 主要价值

  • 深度推理:支持多跳查询(如“治疗A疾病的药物B有哪些副作用?”)。
  • 发现新知:通过图算法挖掘药物新用途(老药新用)、疾病潜在关联。
  • 可解释性:为大模型的答案提供可视化的推理路径,增强可信度。

3. 基本组成

知识图谱的基本单位是 “三元组”,形式为:(主体,关系,客体)。

  • 主体:图中的节点,代表一个实体或概念。
  • 关系:连接节点的边,描述主体和客体之间的关系。
  • 客体:可以是另一个实体,也可以是一个属性值。


示例:

  • (乔布斯, 创立, 苹果公司)
  • (苹果公司, 生产, iPhone)
  • (iPhone, 操作系统, iOS)
  • (糖尿病, 症状, 多饮)
  • (糖尿病, 治疗方法, 注射胰岛素)
  • 这些三元组相互连接,就形成了一张知识的大网。


三、构建知识图谱

1. 流程图

       知识图谱的构建是一个系统工程,其生命周期如下图所示,涵盖了从原始数据到智能应用的完整流程:

37.4-知识图谱构建流程2deepseek_mermaid_20250918_5912af.png image.gif

2. 详细说明

2.1 数据预处理

       将非结构化的原始文本转换为适合后续处理的格式,操作过程:

  • 加载:从各种来源(TXT, PDF, CSV, 数据库)读取文本数据。
  • 清洗:去除无关字符、格式标记、广告等噪音数据。
  • 分割:将长文档切分为句子或段落,作为后续处理的基本单元。


2.2 实体识别 (NER)

       从文本中识别并分类出关键的医学概念,使用模型(如LLM、专用NER模型)识别文本中的实体。

实体类型通常包括:

  • 疾病:糖尿病、高血压、社区获得性肺炎
  • 症状:咳嗽、发热、胸痛
  • 药物:阿司匹林、二甲双胍、胰岛素
  • 检查:心电图、胸部CT
  • 治疗方法:手术治疗、物理治疗
  • 解剖部位:心脏、肺部、肝脏


2.3 关系抽取

       判断识别出的实体之间存在何种语义关系,并形成“三元组”。分析实体所在的上下文语境。

常用关系类型:

  • 治疗:(阿司匹林, 治疗, 心肌梗死)
  • 引起:(糖尿病, 引起, 糖尿病肾病)
  • 有症状:(肺炎, 有症状, 咳嗽)
  • 是诊断方法:(胸部CT, 是诊断方法, 肺炎)
  • 属于:(冠心病, 属于, 心血管疾病)


2.4 知识融合

       解决从不同来源获取的知识可能存在的不一致、重复或歧义问题,形成统一、洁净的知识库。

融合的方法:

  • 实体对齐:判断不同名称是否指向同一实体(如“心梗”和“心肌梗死”)。
  • 冲突消解:处理矛盾信息(如某文献说药物A治疗疾病B,另一文献说无效)。
  • 数据整合:将清洗后的“三元组”数据合并到统一的知识库中。


2.5 存储与可视化

       将结构化的知识存储起来,并以便于理解和查询的方式展现。

  • 存储:使用图数据库进行存储。
  • 常用数据库:Neo4j, NebulaGraph。
  • 存储方式:实体作为节点,关系作为边。
  • 可视化:利用图数据库的可视化工具或前端库将知识图谱绘制出来,形成直观的网络图。


2.6 图谱应用

       将构建好的知识图谱应用于实际场景,发挥其价值。

典型应用:

  • 智能问答:提供比RAG更精确的答案(例如:“治疗糖尿病的一线药物有哪些副作用?”)。
  • 辅助诊断:根据输入的症状,在图谱中遍历推理,给出可能的诊断路径。
  • 药物重定位:通过分析药物、疾病、基因之间的复杂网络关系,发现药物的新用途。
  • 科研分析:利用图算法挖掘潜在的疾病关联、发现新的生物标志物等。


2.7 人工审核与修正

       确保知识图谱的准确性和可靠性。

  • 必要性:自动抽取过程难免会有错误,尤其在医疗领域,准确性至关重要。
  • 流程:由医学专家对抽取出的实体和关系进行审核、修正和确认。
  • 迭代:知识图谱的构建是一个“构建-评估-修正”的循环迭代过程,而非一蹴而就。


四、 图谱的应用场景

1. 智能搜索

  • 传统搜索:匹配关键词。搜索“苹果”,结果可能是水果、公司、电影,混在一起。
  • 基于知识图谱的搜索:理解搜索意图。它知道“苹果”是一个多义词,会直接给你一个知识面板,清晰地分类出苹果公司、水果苹果的信息,并且提供关联实体(如创始人、产品、营养成分等)。


2. 问答系统(如医疗QA)

  • 传统问答:基于关键词匹配,只能返回包含关键词的文档片段。
  • 基于知识图谱的问答:能理解复杂问题并进行推理。
  • 问题:“为什么糖尿病患者有时会感到脚麻?”
  • 推理路径:糖尿病 -> 并发症 -> 糖尿病周围神经病变 -> 症状 -> 脚部麻木
  • 回答:生成精准的答案:“这是因为长期高血糖可能引发糖尿病周围神经病变,其症状之一就是脚部麻木或刺痛。”

3. 推荐系统

  • 传统推荐:“购买此商品的人也购买了...”(基于行为协同过滤)。
  • 基于知识图谱的推荐:利用实体关系进行更深度的推荐。
  • 示例:用户喜欢《三体》,系统通过知识图谱发现:《三体》是刘慈欣写的科幻小说,获得了雨果奖。于是可以推荐同一作者(刘慈欣的其他作品)、同一类型(其他科幻小说)、同一荣誉(其他雨果奖作品)。推荐更加多样化、可解释。

4. 风险控制(金融领域)

  • 构建企业、个人、担保人之间的关系图谱。
  • 可以轻松识别出欺诈团伙:例如,发现一群申请贷款的人,虽然表面无关,但他们的紧急联系人都指向同一个电话号码,或者住址都集中在某个虚假的地址块。这种隐藏的关系链在传统表格数据中很难发现,但在图上一目了然。


五、与大模型融合

特性 知识图谱 大语言模型
知识表示 结构化,显式地存储事实(三元组) 参数化,知识隐式地存储在模型参数中
优点 精确、可靠、可解释、可更新 灵活、通用、生成能力强、无需繁琐建模
缺点 构建成本高、依赖高质量数据、难以处理模糊性 会产生幻觉、知识更新滞后、推理过程不透明
比喻 结构严谨的图书馆,每本书位置固定 博览群书的天才,但会混淆记忆,编造故事


和大模型形成互补:

  • 增强大模型的可靠性:将知识图谱作为大模型的外部知识库。让大模型在生成答案前,先从知识图谱中检索确凿的事实,从而减少幻觉。这也是一直提到的RAG检索增强生成。
  • 利用大模型构建图谱:利用大模型强大的自然语言理解能力,来自动化地从文本中抽取实体和关系,大幅降低构建知识图谱的成本。


六、构建过程示例

1. 代码主要结构

1.1 数据准备和模型设置

# 设置DashScope API密钥
dashscope.api_key = os.environ.get("DASHSCOPE_API_KEY")
# 模拟医疗文本数据
medical_texts = [
    "糖尿病是一种慢性疾病,其特征是高血糖。常见症状包括多饮、多尿和多食。",
    # ... 更多医疗文本
]
# 定义医疗实体类型
MEDICAL_ENTITY_TYPES = {
    "DISEASE": "疾病",
    "SYMPTOM": "症状", 
    "DRUG": "药物",
    "TREATMENT": "治疗方法",
    "ANATOMY": "解剖部位"
}

image.gif

关键说明:

  • API密钥从环境变量获取,提高安全性
  • 提供了示例医疗文本,涵盖常见疾病、症状和治疗方法
  • 定义了医疗领域的主要实体类型


1.2 初始化方法

class MedicalKnowledgeGraph:
    def __init__(self):
        self.graph = nx.DiGraph()  # 创建有向图
        self.entities = set()      # 存储所有实体
        self.relations = []        # 存储所有关系
        self.entity_types = {}     # 存储实体类型映射

image.gif

关键说明:

  • 使用NetworkX的DiGraph表示有向图结构
  • 使用集合存储实体确保唯一性
  • 分别存储实体、关系和类型信息,便于管理和查询


1.3 实体提取方法

def extract_entities_with_llm(self, text: str) -> List[Tuple[str, str]]:
    # 构建详细的提示词
    prompt = f"""
请从以下医疗文本中识别出所有医疗实体,并按照以下类型分类:
- DISEASE: 疾病
- SYMPTOM: 症状
# ... 其他类型
文本内容:"{text}"
请以JSON格式输出结果...
"""

image.gif

关键说明:

  • 使用精心设计的提示词指导模型识别和分类医疗实体
  • 要求模型以JSON格式输出,便于解析
  • 添加了错误处理和日志记录


1.4 关系提取方法

def extract_relations_with_llm(self, text: str, entities: List[Tuple[str, str]]) -> List[Tuple[str, str, str]]:
    # 构建实体列表字符串
    entities_str = ", ".join([f"{ent}({typ})" for ent, typ in entities])
    
    # 构建关系提取提示词
    prompt = f"""
请分析以下医疗文本,识别其中医疗实体之间的关系:
文本内容:"{text}"
文本中包含的实体:{entities_str}
请识别这些实体之间的关系,使用以下关系类型:
- TREATS: 治疗关系
- CAUSES: 引起关系
# ... 其他关系类型
"""

image.gif

关键说明:

  • 基于已提取的实体进行关系抽取
  • 明确定义了医疗领域的关系类型
  • 使用结构化输出要求,便于解析三元组


1.5 知识图谱构建方法

def build_from_texts(self, texts: List[str]):
    for i, text in enumerate(texts):
        # 提取实体
        entities = self.extract_entities_with_llm(text)
        for entity, type_ in entities:
            self.entities.add(entity)
            self.entity_types[entity] = type_
        
        # 提取关系
        relations = self.extract_relations_with_llm(text, entities)
        self.relations.extend(relations)
        
        # 添加到图结构
        for entity, type_ in entities:
            self.graph.add_node(entity, type=type_)
            
        for src, rel, dst in relations:
            self.graph.add_edge(src, dst, relationship=rel)

image.gif

关键说明:

  • 遍历处理所有文本数据
  • 先提取实体,再基于实体提取关系
  • 将实体作为节点,关系作为边添加到图中
  • 记录处理进度和性能指标


1.6 可视化方法

def visualize(self, title="医疗知识图谱"):
    # 根据节点类型设置颜色
    node_colors = []
    for node in self.graph.nodes():
        node_type = self.entity_types.get(node, "UNKNOWN")
        if node_type == "DISEASE":
            node_colors.append("lightcoral")
        # ... 其他类型颜色设置
    
    # 创建布局和绘制
    pos = nx.spring_layout(self.graph, k=2, iterations=50)
    nx.draw_networkx_nodes(self.graph, pos, node_color=node_colors, node_size=2000, alpha=0.9)
    # ... 更多绘制代码

image.gif

关键说明:

  • 根据实体类型设置不同颜色,提高可视化效果
  • 使用spring_layout算法进行节点布局
  • 添加图例说明节点颜色含义
  • 显示关系标签,便于理解实体间关系


1.7 查询和导出方法

def query(self, entity: str, relation: str = None):
    """查询知识图谱"""
    results = []
    if relation:
        # 查询特定关系
        for src, rel, dst in self.relations:
            if src == entity and rel == relation:
                results.append(dst)
            elif dst == entity and rel == relation:
                results.append(src)
    else:
        # 查询所有相关关系
        for src, rel, dst in self.relations:
            if src == entity or dst == entity:
                results.append((src, rel, dst))
    
    return results
def to_json(self, filepath: str = None):
    """将知识图谱导出为JSON格式"""
    kg_data = {
        "entities": list(self.entities),
        "relations": [{"source": s, "relationship": r, "target": t} for s, r, t in self.relations],
        "entity_types": self.entity_types
    }

image.gif

关键说明:

  • 提供灵活的查询接口,支持按实体和关系类型查询
  • 支持将知识图谱导出为结构化JSON格式,便于保存和共享
  • 包含完整的实体、关系和类型信息


1.8 实现医疗问答系统

class MedicalQA:
    def __init__(self, knowledge_graph):
        self.kg = knowledge_graph
    
    def answer_question(self, question: str) -> str:
        # 首先尝试从知识图谱中直接找到答案
        direct_answer = self._answer_from_kg(question)
        if direct_answer:
            return direct_answer
        
        # 如果知识图谱中没有直接答案,使用大模型生成答案
        return self._answer_with_llm(question)

image.gif

关键说明:

  • 基于已有的知识图谱构建问答系统
  • 采用两阶段策略:先尝试从知识图谱获取精确答案,再使用大模型生成答案
  • 结合了知识图谱的准确性和大模型的灵活性


1.9 主程序流程

if __name__ == "__main__":
    # 构建基础知识图谱
    basic_kg = build_medical_kg_with_dashscope()
    
    # 构建复杂知识图谱
    complex_kg = build_complex_medical_kg()
    
    # 创建问答系统并测试
    qa_system = MedicalQA(basic_kg)
    test_questions = [
        "糖尿病有什么症状?",
        "阿司匹林可以治疗什么病?",
        "高血压应该用什么药?"
    ]
    
    for question in test_questions:
        answer = qa_system.answer_question(question)
        print(f"回答: {answer}")
    
    # 知识图谱分析
    degree_centrality = nx.degree_centrality(basic_kg.graph)
    # ... 更多分析代码

image.gif

关键说明:

  • 完整的端到端流程:从数据准备到图谱构建再到应用
  • 提供示例测试问题,验证系统功能
  • 包含知识图谱分析,如计算节点中心度

输出结果:

37.5-知识图谱1.png image.gif

============================================================
开始使用Qwen大模型构建医疗知识图谱...
============================================================
开始从 8 条文本构建知识图谱
------------------------------------------------------------
处理文本 1/8: 糖尿病是一种慢性疾病,其特征是高血糖。常见症状包括多饮、多尿和多食。...
调用Qwen大模型进行实体识别,耗时: 5.49秒
成功提取到 5 个实体:[('糖尿病', 'DISEASE'), ('高血糖', 'SYMPTOM'), ('多饮', 'SYMPTOM'), ('多尿', 'SYMPTOM'), ('多食', 'SYMPTOM')]
调用Qwen大模型进行关系抽取,耗时: 20.72秒
成功提取到 4 个关系:[('糖尿病', 'HAS_SYMPTOM', '高血糖'), ('糖尿病', 'HAS_SYMPTOM', '多饮'), ('糖尿病', 'HAS_SYMPTOM', '多尿'), ('糖尿病', 'HAS_SYMPTOM', '多食')]
------------------------------------------------------------
处理文本 2/8: 高血压患者需要定期服用降压药,如氨氯地平或缬沙坦。...
调用Qwen大模型进行实体识别,耗时: 3.17秒
成功提取到 4 个实体:[('高血压', 'DISEASE'), ('降压药', 'DRUG'), ('氨氯地平', 'DRUG'), ('缬沙坦', 'DRUG')]
调用Qwen大模型进行关系抽取,耗时: 10.10秒
成功提取到 3 个关系:[('降压药', 'TREATS', '高血压'), ('氨氯地平', 'IS_A', '降压药'), ('缬沙坦', 'IS_A', '降压药')]
------------------------------------------------------------
处理文本 3/8: 阿司匹林可用于预防心脏病发作,但可能引起胃肠道出血。...
调用Qwen大模型进行实体识别,耗时: 11.36秒
成功提取到 3 个实体:[('阿司匹林', 'DRUG'), ('心脏病发作', 'DISEASE'), ('胃肠道出血', 'SYMPTOM')]
调用Qwen大模型进行关系抽取,耗时: 13.55秒
成功提取到 2 个关系:[('阿司匹林', 'TREATS', '心脏病发作'), ('阿司匹林', 'CAUSES', '胃肠道出血')]
------------------------------------------------------------
处理文本 4/8: 肺炎通常由细菌感染引起,症状包括咳嗽、发热和胸痛。治疗常用抗生素如阿莫西林。...
调用Qwen大模型进行实体识别,耗时: 4.02秒
成功提取到 6 个实体:[('肺炎', 'DISEASE'), ('咳嗽', 'SYMPTOM'), ('发热', 'SYMPTOM'), ('胸痛', 'SYMPTOM'), ('抗生素', 'DRUG'), ('阿莫西林', 'DRUG')]
调用Qwen大模型进行关系抽取,耗时: 12.81秒
成功提取到 5 个关系:[('肺炎', 'HAS_SYMPTOM', '咳嗽'), ('肺炎', 'HAS_SYMPTOM', '发热'), ('肺炎', 'HAS_SYMPTOM', '胸痛'), ('抗生素', 'TREATS', '肺炎'), ('阿莫西林', 'TREATS', '肺炎')]
------------------------------------------------------------
处理文本 5/8: 心肌梗死是冠状动脉阻塞导致的心肌缺血坏死,主要症状为胸痛和呼吸困难。...
调用Qwen大模型进行实体识别,耗时: 15.00秒
成功提取到 7 个实体:[('心肌梗死', 'DISEASE'), ('冠状动脉阻塞', 'DISEASE'), ('心肌缺血坏死', 'DISEASE'), ('胸痛', 'SYMPTOM'), ('呼吸困难', 'SYMPTOM'), ('冠状动脉', 'ANATOMY'), ('心肌', 'ANATOMY')]
调用Qwen大模型进行关系抽取,耗时: 22.50秒
成功提取到 4 个关系:[('冠状动脉阻塞', 'CAUSES', '心肌梗死'), ('心肌梗死', 'HAS_SYMYMPTOM', '胸痛'), ('心肌梗死', 'HAS_SYMPTOM', '呼吸困难'), ('冠状动脉阻塞', 'CAUSES', '心肌缺血坏死')]
------------------------------------------------------------
处理文本 6/8: 2型糖尿病患者可能需要使用二甲双胍或胰岛素来控制血糖水平。...
调用Qwen大模型进行实体识别,耗时: 9.91秒
成功提取到 4 个实体:[('2型糖尿病', 'DISEASE'), ('二甲双胍', 'DRUG'), ('胰岛素', 'DRUG'), ('血糖水平', 'SYMPTOM')]
调用Qwen大模型进行关系抽取,耗时: 15.61秒
成功提取到 2 个关系:[('二甲双胍', 'TREATS', '2型糖尿病'), ('胰岛素', 'TREATS', '2型糖尿病')]
------------------------------------------------------------
处理文本 7/8: 哮喘患者通常使用吸入性糖皮质激素如布地奈德来控制症状。...
调用Qwen大模型进行实体识别,耗时: 3.21秒
成功提取到 4 个实体:[('哮喘', 'DISEASE'), ('吸入性糖皮质激素', 'DRUG'), ('布地奈德', 'DRUG'), ('症状', 'SYMPTOM')]
调用Qwen大模型进行关系抽取,耗时: 18.28秒
成功提取到 4 个关系:[('哮喘', 'HAS_SYMPTOM', '症状'), ('吸入性糖皮质激素', 'TREATS', '哮喘'), ('布地奈德', 'IS_A', '吸入性糖皮质激素'), ('哮喘', 'USES', '布地奈德')]
------------------------------------------------------------
处理文本 8/8: 抑郁症的治疗包括心理治疗和抗抑郁药物,如氟西汀和舍曲林。...
调用Qwen大模型进行实体识别,耗时: 3.69秒
成功提取到 5 个实体:[('抑郁症', 'DISEASE'), ('心理治疗', 'TREATMENT'), ('抗抑郁药物', 'TREATMENT'), ('氟西汀', 'DRUG'), ('舍曲林', 'DRUG')]
调用Qwen大模型进行关系抽取,耗时: 15.61秒
成功提取到 2 个关系:[('二甲双胍', 'TREATS', '2型糖尿病'), ('胰岛素', 'TREATS', '2型糖尿病')]
------------------------------------------------------------
知识图谱构建完成,总耗时: 175.97秒
共提取 37 个实体, 28 个关系
提取到 37 个实体,实体示例: ['冠状动脉阻塞', '血糖水平', '二甲双胍', '高血糖', '发热']
提取到 28 个关系,关系示例: [('糖尿病', 'HAS_SYMPTOM', '高血糖'), ('糖尿病', 'HAS_SYMPTOM', '多饮'), ('糖尿病', 'HAS_SYMPTOM', '多尿')]
开始可视化知识图谱: 基于Qwen大模型的医疗知识图谱
知识图谱可视化完成,准备显示图表
开始导出知识图谱到JSON: medical_kg_dashscope.json
知识图谱已成功导出到: medical_kg_dashscope.json
知识图谱已导出到 medical_kg_dashscope.json

示例查询:
查询与'糖尿病'相关的所有关系:
 ('糖尿病', 'HAS_SYMPTOM', '高血糖')
 ('糖尿病', 'HAS_SYMPTOM', '多饮')
 ('糖尿病', 'HAS_SYMPTOM', '多尿')
 ('糖尿病', 'HAS_SYMPTOM', '多食')

查询'阿司匹林'治疗哪些疾病:
 心脏病发作
============================================================
创建医疗问答系统...

问题: 糖尿病有什么症状?
调用Qwen大模型进行实体识别,耗时: 1.97秒
成功提取到 1 个实体:[('糖尿病', 'DISEASE')]
回答: 糖尿病的症状包括高血糖。糖尿病的症状包括多饮。糖尿病的症状包括多尿。糖尿病的症状包括多食

问题: 阿司匹林可以治疗什么病?
调用Qwen大模型进行实体识别,耗时: 10.84秒
成功提取到 1 个实体:[('阿司匹林', 'DRUG')]
回答: 阿司匹林可用于治疗心脏病发作。阿司匹林可能引起胃肠道出血

问题: 高血压应该用什么药?
调用Qwen大模型进行实体识别,耗时: 3.25秒
成功提取到 1 个实体:[('高血压', 'DISEASE')]
回答: 降压药可用于治疗高血压
============================================================
知识图谱分析:

基础图谱中心度最高的节点:
肺炎: 0.139
糖尿病: 0.111
降压药: 0.083
心肌梗死: 0.083
哮喘: 0.083

识别出的疾病: 糖尿病, 高血压, 心脏病发作, 肺炎, 心肌梗死, 冠状动脉阻塞, 心肌缺血坏死, 2型糖尿病, 哮喘, 抑郁症
识别出的药物: 降压药, 氨氯
============================================================

37.6-复杂知识图谱1.png image.gif

生成的json内容:

37.7.8.9合 微信图片_20251129112919_246_155.jpg

       这个系统展示了如何利用大语言模型构建专业领域的知识图谱,并将其应用于实际场景如智能问答,是知识图谱技术与行业应用结合的基础范例,可以按需继续扩展。


七、总结

       知识图谱的本质是将人类知识结构化,让机器能够理解和推理关系。它从最初的搜索引擎技术,已经发展成为人工智能的“基础设施”,是让AI变得更可信、可解释、可推理的关键技术之一。

它的价值在于:

  • 对机器:提供了可计算、可理解的知识背景。
  • 对人:提供了洞察复杂关系的显微镜和望远镜。

       随着与大语言模型的深度融合,知识图谱正在成为构建下一代可信AI应用的核心支柱。


附录:完整示例代码

# 知识图谱构建示例:医疗领域(使用DashScope Generation模型)
import pandas as pd
import numpy as np
import re
import json
from collections import defaultdict
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
matplotlib.rcParams['axes.unicode_minus'] = False    # 用来正常显示负号
import networkx as nx
from typing import List, Dict, Tuple, Set
import dashscope
from dashscope import Generation
import warnings
warnings.filterwarnings('ignore')
import os
import logging
import time
import datetime
# 配置日志
log_file = f"medical_kg_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler(log_file, encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)
# 设置DashScope API密钥
dashscope.api_key = os.environ.get("DASHSCOPE_API_KEY")  # 请替换为您的实际API密钥
# 模拟医疗文本数据
medical_texts = [
    "糖尿病是一种慢性疾病,其特征是高血糖。常见症状包括多饮、多尿和多食。",
    "高血压患者需要定期服用降压药,如氨氯地平或缬沙坦。",
    "阿司匹林可用于预防心脏病发作,但可能引起胃肠道出血。",
    "肺炎通常由细菌感染引起,症状包括咳嗽、发热和胸痛。治疗常用抗生素如阿莫西林。",
    "心肌梗死是冠状动脉阻塞导致的心肌缺血坏死,主要症状为胸痛和呼吸困难。",
    "2型糖尿病患者可能需要使用二甲双胍或胰岛素来控制血糖水平。",
    "哮喘患者通常使用吸入性糖皮质激素如布地奈德来控制症状。",
    "抑郁症的治疗包括心理治疗和抗抑郁药物,如氟西汀和舍曲林。"
]
# 定义医疗实体类型
MEDICAL_ENTITY_TYPES = {
    "DISEASE": "疾病",
    "SYMPTOM": "症状", 
    "DRUG": "药物",
    "TREATMENT": "治疗方法",
    "ANATOMY": "解剖部位"
}
class MedicalKnowledgeGraph:
    def __init__(self):
        self.graph = nx.DiGraph()
        self.entities = set()
        self.relations = []
        self.entity_types = {}
        
    def extract_entities_with_llm(self, text: str) -> List[Tuple[str, str]]:
        """使用Qwen大模型提取医疗实体"""
        # print(f"开始从文本提取实体: {text[:50]}...")
        # 构建实体识别提示词
        prompt = f"""
请从以下医疗文本中识别出所有医疗实体,并按照以下类型分类:
- DISEASE: 疾病
- SYMPTOM: 症状
- DRUG: 药物
- TREATMENT: 治疗方法
- ANATOMY: 解剖部位
文本内容:"{text}"
请以JSON格式输出结果,包含一个"entities"数组,每个实体对象包含"name"和"type"两个字段。
输出示例:
{{
  "entities": [
    {{"name": "糖尿病", "type": "DISEASE"}},
    {{"name": "高血糖", "type": "SYMPTOM"}}
  ]
}}
"""
        
        try:
            # 调用Qwen的大模型
            start_time = time.time()
            response = Generation.call(
                model='qwen-max',
                prompt=prompt,
                result_format='text'
            )
            elapsed_time = time.time() - start_time
            print(f"调用Qwen大模型进行实体识别,耗时: {elapsed_time:.2f}秒")
            
            entities = []
            if response and response.output:
                # 尝试从响应中提取JSON
                # result_text = response.output.choices[0].message.content
                result_text = response.output.text
                
                # 查找JSON部分
                json_match = re.search(r'\{.*\}', result_text, re.DOTALL)
                if json_match:
                    result_json = json.loads(json_match.group())
                    if 'entities' in result_json:
                        for entity in result_json['entities']:
                            if all(k in entity for k in ['name', 'type']):
                                entities.append((entity['name'], entity['type']))
            
            print(f"成功提取到 {len(entities)} 个实体:{entities}")
            return entities
        except Exception as e:
            logger.error(f"实体识别错误: {e}")
            return []
    
    def extract_relations_with_llm(self, text: str, entities: List[Tuple[str, str]]) -> List[Tuple[str, str, str]]:
        """使用Qwen大模型进行关系抽取"""
        # print(f"开始从文本提取实体关系: {text[:50]}...")
        if not entities or len(entities) < 2:
            print("实体数量不足,无法提取关系")
            return []
            
        relations = []
        
        # 构建实体列表字符串
        entities_str = ", ".join([f"{ent}({typ})" for ent, typ in entities])
        
        # 构建提示词
        prompt = f"""
请分析以下医疗文本,识别其中医疗实体之间的关系:
文本内容:"{text}"
文本中包含的实体:{entities_str}
请识别这些实体之间的关系,使用以下关系类型:
- TREATS: 治疗关系,如"药物治疗疾病"
- CAUSES: 引起关系,如"疾病引起症状" 
- HAS_SYMPTOM: 有症状关系,如"疾病有症状"
- IS_A: 是一种关系,如"疾病A是疾病B的一种"
- USES: 使用关系,如"治疗使用药物"
请以JSON格式输出结果,包含一个"relations"数组,每个关系对象包含"source", "relationship", "target"三个字段。
输出示例:
{{
  "relations": [
    {{"source": "糖尿病", "relationship": "HAS_SYMPTOM", "target": "高血糖"}},
    {{"source": "阿司匹林", "relationship": "TREATS", "target": "心脏病"}}
  ]
}}
"""
        
        try:
            # 调用DashScope的大模型
            # print("调用DashScope API进行关系抽取...")
            start_time = time.time()
            response = Generation.call(
                model='qwen-max',
                prompt=prompt,
                result_format='text'
            )
            elapsed_time = time.time() - start_time
            print(f"调用Qwen大模型进行关系抽取,耗时: {elapsed_time:.2f}秒")
            logging.debug(f"API响应: {response}")
            if response and response.output:
                # 尝试从响应中提取JSON
                # result_text = response.output.choices[0].message.content
                result_text = response.output.text
                
                # 查找JSON部分
                json_match = re.search(r'\{.*\}', result_text, re.DOTALL)
                if json_match:
                    result_json = json.loads(json_match.group())
                    if 'relations' in result_json:
                        for rel in result_json['relations']:
                            if all(k in rel for k in ['source', 'relationship', 'target']):
                                relations.append((rel['source'], rel['relationship'], rel['target']))
            
            print(f"成功提取到 {len(relations)} 个关系:{relations}")
            print("-" * 60)
            return relations
        except Exception as e:
            logger.error(f"关系抽取错误: {e}")
            return []
    
    def build_from_texts(self, texts: List[str]):
        """从文本集合构建知识图谱"""
        print(f"开始从 {len(texts)} 条文本构建知识图谱")
        print("-" * 60)
        start_time = time.time()
        for i, text in enumerate(texts):
            print(f"处理文本 {i+1}/{len(texts)}: {text[:50]}...")
            
            # 提取实体
            entities = self.extract_entities_with_llm(text)
            for entity, type_ in entities:
                self.entities.add(entity)
                self.entity_types[entity] = type_
            
            # 提取关系
            relations = self.extract_relations_with_llm(text, entities)
            self.relations.extend(relations)
            
            # 添加到图
            for entity, type_ in entities:
                self.graph.add_node(entity, type=type_)
                
            for src, rel, dst in relations:
                self.graph.add_edge(src, dst, relationship=rel)
                
        total_time = time.time() - start_time
        print(f"知识图谱构建完成,总耗时: {total_time:.2f}秒")
        print(f"共提取 {len(self.entities)} 个实体, {len(self.relations)} 个关系")
    
    def visualize(self, title="医疗知识图谱"):
        """可视化知识图谱"""
        print(f"开始可视化知识图谱: {title}")
        plt.figure(figsize=(14, 10))
        
        # 根据节点类型设置颜色
        node_colors = []
        for node in self.graph.nodes():
            node_type = self.entity_types.get(node, "UNKNOWN")
            if node_type == "DISEASE":
                node_colors.append("lightcoral")
            elif node_type == "SYMPTOM":
                node_colors.append("lightblue")
            elif node_type == "DRUG":
                node_colors.append("lightgreen")
            elif node_type == "TREATMENT":
                node_colors.append("khaki")
            elif node_type == "ANATOMY":
                node_colors.append("violet")
            else:
                node_colors.append("gray")
        
        # 创建布局
        pos = nx.spring_layout(self.graph, k=2, iterations=50)
        
        # 绘制节点
        nx.draw_networkx_nodes(self.graph, pos, node_color=node_colors, node_size=2000, alpha=0.9)
        
        # 绘制边
        nx.draw_networkx_edges(self.graph, pos, width=1.5, alpha=0.7, edge_color="gray")
        
        # 绘制节点标签
        nx.draw_networkx_labels(self.graph, pos, font_size=10, font_family="SimHei")
        
        # 绘制边标签
        edge_labels = nx.get_edge_attributes(self.graph, 'relationship')
        nx.draw_networkx_edge_labels(self.graph, pos, edge_labels=edge_labels, font_size=8)
        
        # 添加图例
        legend_elements = [
            plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='lightcoral', markersize=10, label='疾病'),
            plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='lightblue', markersize=10, label='症状'),
            plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='lightgreen', markersize=10, label='药物'),
            plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='khaki', markersize=10, label='治疗方法'),
            plt.Line2D([0], [0], marker='o', color='w', markerfacecolor='violet', markersize=10, label='解剖部位')
        ]
        plt.legend(handles=legend_elements, loc='best')
        
        plt.title(title, fontsize=16)
        plt.axis('off')
        plt.tight_layout()
        print("知识图谱可视化完成,准备显示图表")
        plt.show()
    
    def to_json(self, filepath: str = None):
        """将知识图谱导出为JSON格式"""
        print(f"开始导出知识图谱到JSON{f': {filepath}' if filepath else ''}")
        kg_data = {
            "entities": list(self.entities),
            "relations": [{"source": s, "relationship": r, "target": t} for s, r, t in self.relations],
            "entity_types": self.entity_types
        }
        
        if filepath:
            try:
                with open(filepath, 'w', encoding='utf-8') as f:
                    json.dump(kg_data, f, ensure_ascii=False, indent=2)
                print(f"知识图谱已成功导出到: {filepath}")
            except Exception as e:
                logger.error(f"导出知识图谱到JSON文件时出错: {e}")
        
        return kg_data
    
    def query(self, entity: str, relation: str = None):
        """查询知识图谱"""
        results = []
        if relation:
            for src, rel, dst in self.relations:
                if src == entity and rel == relation:
                    results.append(dst)
                elif dst == entity and rel == relation:
                    results.append(src)
        else:
            for src, rel, dst in self.relations:
                if src == entity or dst == entity:
                    results.append((src, rel, dst))
        
        return results
# 构建医疗知识图谱
def build_medical_kg_with_dashscope():
    print("开始使用Qwen大模型构建医疗知识图谱...")
    print("=" * 60)
    kg = MedicalKnowledgeGraph()
    kg.build_from_texts(medical_texts)
    
    print(f"提取到 {len(kg.entities)} 个实体")
    print("实体示例:", list(kg.entities)[:5])
    print(f"提取到 {len(kg.relations)} 个关系")
    print("关系示例:", kg.relations[:3] if kg.relations else "无")
    
    # 可视化知识图谱
    kg.visualize("基于Qwen大模型的医疗知识图谱")
    
    # 导出为JSON
    kg_data = kg.to_json("medical_kg_dashscope.json")
    print("知识图谱已导出到 medical_kg_dashscope.json")
    
    # 示例查询
    print("\n示例查询:")
    print("查询与'糖尿病'相关的所有关系:")
    diabetes_relations = kg.query("糖尿病")
    for rel in diabetes_relations:
        print(f"  {rel}")
    
    print("\n查询'阿司匹林'治疗哪些疾病:")
    aspirin_treats = kg.query("阿司匹林", "TREATS")
    for disease in aspirin_treats:
        print(f"  {disease}")
    
    return kg
# 高级功能:基于知识图谱的问答系统
class MedicalQA:
    def __init__(self, knowledge_graph):
        self.kg = knowledge_graph
    
    def extract_entities_with_llm(self, text: str) -> List[Tuple[str, str]]:
        """使用LLM从文本中提取实体"""
        # 构建实体识别提示词
        prompt = f"""
请从以下文本中识别出所有医疗实体,并按照以下类型分类:
- DISEASE: 疾病
- SYMPTOM: 症状
- DRUG: 药物
- TREATMENT: 治疗方法
- ANATOMY: 解剖部位
文本内容:"{text}"
请以JSON格式输出结果,包含一个"entities"数组,每个实体对象包含"name"和"type"两个字段。
输出示例:
{{
  "entities": [
    {{"name": "糖尿病", "type": "DISEASE"}},
    {{"name": "高血糖", "type": "SYMPTOM"}}
  ]
}}
"""
        
        try:
            # 调用DashScope的大模型
            start_time = time.time()
            response = Generation.call(
                model='qwen-max',
                prompt=prompt,
                result_format='text'
            )
            elapsed_time = time.time() - start_time
            print(f"调用Qwen大模型进行实体识别,耗时: {elapsed_time:.2f}秒")
            
            entities = []
            if response and response.output:
                # 尝试从响应中提取JSON
                result_text = response.output.text
                
                # 查找JSON部分
                json_match = re.search(r'\{.*\}', result_text, re.DOTALL)
                if json_match:
                    result_json = json.loads(json_match.group())
                    if 'entities' in result_json:
                        for entity in result_json['entities']:
                            if all(k in entity for k in ['name', 'type']):
                                entities.append((entity['name'], entity['type']))
            
            print(f"成功提取到 {len(entities)} 个实体:{entities}")
            return entities
        except Exception as e:
            logger.error(f"实体识别错误: {e}")
            return []
    
    def answer_question(self, question: str) -> str:
        """基于知识图谱回答问题"""
        # 首先尝试从知识图谱中直接找到答案
        direct_answer = self._answer_from_kg(question)
        if direct_answer:
            return direct_answer
        
        # 如果知识图谱中没有直接答案,使用大模型生成答案
        return self._answer_with_llm(question)
    
    def _answer_from_kg(self, question: str) -> str:
        """从知识图谱中直接提取答案"""
        # 提取问题中的实体
        entities = self.extract_entities_with_llm(question)
        if not entities:
            return None
            
        # 查找与实体相关的关系
        answers = []
        for entity, _ in entities:
            relations = self.kg.query(entity)
            for src, rel, dst in relations:
                if rel == "TREATS" and src == entity:
                    answers.append(f"{entity}可用于治疗{dst}")
                elif rel == "TREATS" and dst == entity:
                    answers.append(f"{src}可用于治疗{entity}")
                elif rel == "HAS_SYMPTOM" and src == entity:
                    answers.append(f"{entity}的症状包括{dst}")
                elif rel == "CAUSES" and src == entity:
                    answers.append(f"{entity}可能引起{dst}")
        
        if answers:
            return "。".join(answers)
        return None
    
    def _answer_with_llm(self, question: str) -> str:
        """使用大模型生成答案"""
        # 构建知识上下文
        context_entities = self.extract_entities_with_llm(question)
        context = "相关知识:\n"
        for entity, _ in context_entities:
            relations = self.kg.query(entity)
            for src, rel, dst in relations:
                context += f"- {src} {rel} {dst}\n"
        
        prompt = f"""
你是一个专业的医疗问答助手。请基于以下知识回答问题。
{context}
问题:{question}
请提供准确、专业的回答,并注明信息来源是基于医学知识图谱。
"""
        
        try:
            response = Generation.call(
                model='qwen-max',
                prompt=prompt,
                result_format='text'
            )
            
            if response and response.output:
                return response.output.text
            return "抱歉,我无法回答这个问题。"
        except Exception as e:
            return f"生成答案时出错: {str(e)}"
# 主函数
if __name__ == "__main__":
    # 构建基础知识图谱
    print("=" * 60)
    basic_kg = build_medical_kg_with_dashscope()
    
    # 创建问答系统
    print("=" * 60)
    print("创建医疗问答系统...")
    qa_system = MedicalQA(basic_kg)
    
    # 测试问答
    test_questions = [
        "糖尿病有什么症状?",
        "阿司匹林可以治疗什么病?",
        "高血压应该用什么药?"
    ]
    
    for question in test_questions:
        print(f"\n问题: {question}")
        answer = qa_system.answer_question(question)
        print(f"回答: {answer}")
    
    # 知识图谱分析
    print("=" * 60)
    print("知识图谱分析:")
    
    # 计算节点中心度
    degree_centrality = nx.degree_centrality(basic_kg.graph)
    top_nodes = sorted(degree_centrality.items(), key=lambda x: x[1], reverse=True)[:5]
    print("\n基础图谱中心度最高的节点:")
    for node, centrality in top_nodes:
        print(f"{node}: {centrality:.3f}")
    
    # 查找最常见的疾病
    diseases = [node for node, type_ in basic_kg.entity_types.items() if type_ == "DISEASE"]
    print(f"\n识别出的疾病: {', '.join(diseases)}")
    
    # 查找最常见的药物
    drugs = [node for node, type_ in basic_kg.entity_types.items() if type_ == "DRUG"]
    print(f"识别出的药物: {', '.join(drugs)}")


相关文章
|
2天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
265 116
|
17天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
11天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
655 220
|
5天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
324 32
Meta SAM3开源:让图像分割,听懂你的话
|
9天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1509 157
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
893 61
|
7天前
|
编解码 Linux 数据安全/隐私保护
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
290 140

热门文章

最新文章