一、初识知识图谱
一个简单的例子,如果想要了解“苹果”,常规的做法是去查字典,字典会告诉我们苹果是一种蔷薇科植物的果实,酸甜可口。极其简单,字典只会从最基础的角度孤立的解释这个词语。
如果获得它更多的属性,比如我们知道红富士的品种特别有名,富含维生素c,也可以制作苹果汁,还能了解到它是乔布斯创立的苹果公司的logo,它是一部手机的名字,它还可以叫iphone,牛顿因为苹果发现了万有引力等等非常丰富的信息,像一部百科全书一样,将苹果与公司、神”、人物、科学事件等概念连接起来,形成一个知识网络。
知识图谱,就是这样一部机器能够理解和处理的智能百科全书。 它不是简单的数据集合,而是一张巨大的、由相互连接的知识点构成的语义网络。
二、知识图谱的定义
1. 核心定义
知识图谱是一种用图结构来建模和表示现实世界中实体、概念及其相互关系的技术。它的核心思想是事物的价值不在于其本身,而在于它与其他事物的关系。旨在揭示医学概念之间深层的、结构化的关系,形成一个巨大的语义网络。
2. 主要价值
- 深度推理:支持多跳查询(如“治疗A疾病的药物B有哪些副作用?”)。
- 发现新知:通过图算法挖掘药物新用途(老药新用)、疾病潜在关联。
- 可解释性:为大模型的答案提供可视化的推理路径,增强可信度。
3. 基本组成
知识图谱的基本单位是 “三元组”,形式为:(主体,关系,客体)。
- 主体:图中的节点,代表一个实体或概念。
- 关系:连接节点的边,描述主体和客体之间的关系。
- 客体:可以是另一个实体,也可以是一个属性值。
示例:
- (乔布斯, 创立, 苹果公司)
- (苹果公司, 生产, iPhone)
- (iPhone, 操作系统, iOS)
- (糖尿病, 症状, 多饮)
- (糖尿病, 治疗方法, 注射胰岛素)
- 这些三元组相互连接,就形成了一张知识的大网。
三、构建知识图谱
1. 流程图
知识图谱的构建是一个系统工程,其生命周期如下图所示,涵盖了从原始数据到智能应用的完整流程:
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": "解剖部位" }
关键说明:
- API密钥从环境变量获取,提高安全性
- 提供了示例医疗文本,涵盖常见疾病、症状和治疗方法
- 定义了医疗领域的主要实体类型
1.2 初始化方法
class MedicalKnowledgeGraph: def __init__(self): self.graph = nx.DiGraph() # 创建有向图 self.entities = set() # 存储所有实体 self.relations = [] # 存储所有关系 self.entity_types = {} # 存储实体类型映射
关键说明:
- 使用NetworkX的DiGraph表示有向图结构
- 使用集合存储实体确保唯一性
- 分别存储实体、关系和类型信息,便于管理和查询
1.3 实体提取方法
def extract_entities_with_llm(self, text: str) -> List[Tuple[str, str]]: # 构建详细的提示词 prompt = f""" 请从以下医疗文本中识别出所有医疗实体,并按照以下类型分类: - DISEASE: 疾病 - SYMPTOM: 症状 # ... 其他类型 文本内容:"{text}" 请以JSON格式输出结果... """
关键说明:
- 使用精心设计的提示词指导模型识别和分类医疗实体
- 要求模型以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: 引起关系 # ... 其他关系类型 """
关键说明:
- 基于已提取的实体进行关系抽取
- 明确定义了医疗领域的关系类型
- 使用结构化输出要求,便于解析三元组
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)
关键说明:
- 遍历处理所有文本数据
- 先提取实体,再基于实体提取关系
- 将实体作为节点,关系作为边添加到图中
- 记录处理进度和性能指标
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) # ... 更多绘制代码
关键说明:
- 根据实体类型设置不同颜色,提高可视化效果
- 使用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 }
关键说明:
- 提供灵活的查询接口,支持按实体和关系类型查询
- 支持将知识图谱导出为结构化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)
关键说明:
- 基于已有的知识图谱构建问答系统
- 采用两阶段策略:先尝试从知识图谱获取精确答案,再使用大模型生成答案
- 结合了知识图谱的准确性和大模型的灵活性
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) # ... 更多分析代码
关键说明:
- 完整的端到端流程:从数据准备到图谱构建再到应用
- 提供示例测试问题,验证系统功能
- 包含知识图谱分析,如计算节点中心度
输出结果:
============================================================
开始使用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型糖尿病, 哮喘, 抑郁症
识别出的药物: 降压药, 氨氯
============================================================
生成的json内容:
这个系统展示了如何利用大语言模型构建专业领域的知识图谱,并将其应用于实际场景如智能问答,是知识图谱技术与行业应用结合的基础范例,可以按需继续扩展。
七、总结
知识图谱的本质是将人类知识结构化,让机器能够理解和推理关系。它从最初的搜索引擎技术,已经发展成为人工智能的“基础设施”,是让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)}")