第3章:知识图谱构建技术栈——我的“兵器库”

说实话,理论聊再多,不如直接把袖子撸起来干活。这一章,咱们来盘一盘真正要上手的家伙事儿。

工具选不对,后面全是泪。我用这些年踩过的坑跟你保证,选对技术栈,项目进度能快一倍。下面我把自己的“兵器库”摆出来,一个个给你讲透。

3.1 Neo4j:图数据库里的“老大哥”

如果你问我现在做知识图谱用啥最多,我会毫不犹豫选Neo4j。市场上图数据库不下几十种,Neo4j的使用率稳居第一。为什么?因为它真的“懂”开发者。

我记得刚入行那会儿,团队里还有人纠结用RDBMS还是图数据库。结果一个三跳查询下去,SQL写了三十行,Neo4j只需要三行Cypher,当场就把同事征服了。

核心优势: Neo4j用属性图模型,节点和关系都能带属性,天然适配知识图谱。

你想想看,一个癌症药物知识图谱里,有药物、靶点、疾病、基因、临床试验……这些东西之间的关系错综复杂。用传统关系型数据库,各种JOIN表我都怕。但在Neo4j里,每个实体就是一个节点,关系就是边,直观得很。

安装方面,我建议下载桌面版(Neo4j Desktop),省心。命令行的玩法虽然更geek,但新手容易在环境配置上卡壳。

# 拉取Neo4j Docker镜像(我个人更推荐这种方式)
docker pull neo4j:5.12.0

# 启动容器,挂载数据目录和日志目录
docker run \
  -d \
  --name cancer-kg-neo4j \
  -p 7474:7474 -p 7687:7687 \
  -v /data/neo4j/data:/data \
  -v /data/neo4j/logs:/logs \
  -e NEO4J_AUTH=neo4j/cancerKG2024 \
  neo4j:5.12.0

启动后,打开浏览器访问 http://localhost:7474,用你设置的密码登录。看到那个浏览器界面了吗?那就是你的主战场。写Cypher查询、可视化图谱、做数据探索,全在那里面完成。

踩过的坑: 我曾经在生产环境忘了设置JVM堆内存上限,结果数据量一大直接OOM。一定要先在conf/neo4j.conf里设好dbms.memory.heap.max_size,别偷懒。

3.2 RDF与SPARQL:标准化的“另一种活法”

聊完Neo4j,咱们得说说RDF。你可能听过一句话:知识图谱有两种流派,LPG(属性图)和RDF。我做项目时经常被问到“选哪个好?”我的回答是——看你场景。

RDF这玩意儿,说白了就是一种描述资源的框架。你用三元组(主语-谓语-宾语)来陈述事实。比如“吉非替尼治疗非小细胞肺癌”,在RDF里就写成:

<http://example.org/drug/gefitinib>
  <http://example.org/property/treats>
  "非小细胞肺癌" .

你看,这就是RDF最基础的样子。它的强项在于语义互操作性——数据之间能用URI打通。你在PubMed、DrugBank、ClinicalTrials.gov拿到的数据,转换成RDF后,天然就能融合。

而SPARQL,就是RDF世界的SQL。别看它语法有点怪,但查询能力是真的强。

小技巧: 我开始学SPARQL时总记不住PREFIX,后来养成习惯,先在开头把所有命名空间定义好,后面写查询清爽多了。

举个场景:你想查所有作用于EGFR靶点的肺癌药物,以及对应的临床试验阶段。SPARQL写起来是这样的:

PREFIX drug: <http://example.org/kg/drug/>
PREFIX target: <http://example.org/kg/target/>
PREFIX disease: <http://example.org/kg/disease/>
PREFIX trial: <http://example.org/kg/trial/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT ?drugName ?trialPhase ?status
WHERE {
  ?drug rdf:type drug:Drug .
  ?drug drug:hasTarget target:EGFR .
  ?drug drug:treats disease:LungCancer .
  ?drug drug:hasTrial ?trial .
  ?trial trial:phase ?trialPhase .
  ?trial trial:status ?status .
  ?drug drug:name ?drugName .
}

这段查询返回的结果是:作用于EGFR的非小细胞肺癌药物,以及它们的临床试验阶段和状态。在真实项目中,这类查询能帮科学家快速锁定候选药物。

实用对比: Neo4j适合做应用开发、实时查询;RDF/SPARQL适合做数据交换、跨机构共享。我通常两者都用——Neo4j做业务系统,RDF做开放数据。

3.3 Python:黏合一切的“万能胶”

Python在知识图谱里扮演什么角色?我的理解是——没有它,上面的工具就是孤岛。

我个人习惯用Python来串联整个流程。数据采集用requests+BeautifulSoup,文本处理用spaCy,图谱操作用py2neo,最后还能用Flask搭个API露出来。一套下来,Python全包了。

环境配置我建议用conda,项目隔离做得好,不会出现莫名其妙的包冲突。下面是我常用的环境创建命令:

conda create -n cancer-kg python=3.10
conda activate cancer-kg

pip install neo4j py2neo spacy pandas requests lxml
python -m spacy download zh_core_web_sm

这里面有个容易忽略的点:spaCy的中文模型zh_core_web_sm只有几MB,做基础分词和命名实体识别够了。但如果你想做更精准的医学实体识别,建议用zh_core_web_trf(基于transformers),准确率能提5-8个点。

3.4 NLP工具链:文档里“挖金子”

癌症药物知识图谱的数据来源,大部分是PubMed摘要、FDA标签、临床试验报告。这些全是非结构化文本。如何把它们变成结构化知识?NLP就是你的挖掘机。

我主要用两套工具:

工具 适用场景 我的使用习惯
spaCy 中文文本处理、实体识别、依存分析 先训练一个医学领域NER模型,再批量跑PDF
StanfordNLP / Stanza 英文文献分析、多语言联合 处理PubMed英文摘要时切换到它

举个实际例子。假设你有一篇文献说“Gefitinib demonstrates significant anti-tumor activity in NSCLC patients with EGFR mutations”。用spaCy处理,写出来大概这样:

import spacy

nlp = spacy.load("zh_core_web_sm")
# 实际项目中会自定义医学专用模型
# 这里以简单演示为主

text = "吉非替尼在携带EGFR突变的非小细胞肺癌患者中显示出显著的抗肿瘤活性"
doc = nlp(text)

for ent in doc.ents:
    print(f"{ent.text} -> {ent.label_}")

输出结果应该是:

吉非替尼 -> DRUG
EGFR突变 -> GENE
非小细胞肺癌 -> DISEASE

你看,实体已经被打上标签了。这些标签后面会映射成图谱里的节点。DRUG、GENE、DISEASE就是我们图谱里的四种核心实体类型中的三种(再加上TRIAL)。

心得: 别指望开箱即用的模型能达到100%准确。我通常会在spaCy上做迁移学习,用少量标注数据(1000-2000条)微调,效果明显提升。

3.5 py2neo:Python操作Neo4j的“遥控器”

前面说了Neo4j、说了Python,怎么让它俩对话?py2neo就是我用的遥控器。

这个库封装了对Neo4j的各种操作。你不需要在浏览器里写Cypher,直接在Python脚本里就能完成图的创建、查询、删除。

我一般这样连接:

from py2neo import Graph, Node, Relationship

# 连接Neo4j数据库
graph = Graph(
    "bolt://localhost:7687",
    auth=("neo4j", "cancerKG2024")
)

# 创建两个节点
drug_node = Node("Drug", name="吉非替尼", mechanism="EGFR抑制剂")
trial_node = Node("Trial", name="NCT12345678", phase="III", status="已完成")

# 创建关系
rel = Relationship(drug_node, "HAS_TRIAL", trial_node)

# 写入图数据库
graph.create(rel)

print(f"创建成功:{drug_node} --[HAS_TRIAL]--> {trial_node}")

看到没?代码很简洁。Node创建节点,Relationship创建关系,最后用graph.create()提交。这里面有个细节——节点标签(Drug、Trial)最好跟你的本体设计保持一致。我曾在项目里换过一次标签命名规范,结果还要重新映射,被搞得很痛苦。

避坑指南: 我曾经在循环里逐条提交数据,5万条数据跑了一小时。后来改成批量提交,5分钟搞定。批量操作代码长这样:
from py2neo.bulk import create_nodes, create_relationships

# 批量创建节点
drugs = [
    ("Drug", {"name": "吉非替尼", "mechanism": "EGFR抑制剂"}),
    ("Drug", {"name": "奥希替尼", "mechanism": "EGFR T790M抑制剂"}),
    ("Drug", {"name": "克唑替尼", "mechanism": "ALK抑制剂"}),
]
batch_size = 1000
create_nodes(graph, drugs, batch_size=batch_size)

3.6 技术栈俯瞰:一张图说清

上面这些工具各有分工。我把它们之间的关系画了张图,这样你看着更清楚:

知识图谱构建技术栈·整体架构 数据层 PubMed 文献 | FDA 标签 | ClinicalTrials.gov | DrugBank | 临床指南 数据来源:PDF / JSON / XML / CSV NLP 处理层 spaCy(中文医学NER) | StanfordNLP/Stanza(英文文献) 实体识别 · 关系抽取 · 属性提取 存储与查询层 Neo4j(属性图 · Cypher查询) | RDF三元组(SPARQL查询) py2neo / neo4j driver · Apache Jena · RDFLib 应用层 知识问答 · 药物推荐 · 临床试验匹配 · 靶点发现 💡 Python贯穿全部

这张图我把整个技术栈分成四层:数据来源一头扎进去,NLP工具负责“消化”成结构化信息,Neo4j和RDF负责“存储+查询”,最上面应用层把知识“吃”出来。Python就像一根管道,把每一层串在一起。

3.7 我的选型建议

说了这么多,你可能在纠结:到底用不用RDF?要不要上StanfordNLP?

我的建议很简单——看项目规模:

  • 小项目(< 1万实体): 只用Neo4j + py2neo + spaCy 就够了。RDF可以先不考虑。
  • 中型项目(1-10万实体): 加上StanfordNLP处理英文文献,py2neo批量写入。
  • 大型项目(> 10万实体,多机构协作): 必须上RDF/OWL做语义互操作。SPARQL endpoint搭建好,方便外部调用。

我自己做癌症药物知识图谱时,起步阶段先用Neo4j快速建原型,等数据源稳定了,再导出RDF做标准化发布。两条腿走路,稳得很。

一句话总结: Neo4j帮你快速跑起来,RDF让你能跟别人玩到一块去。Python是那个坐在中间喝咖啡联络两边的人。这三样用熟了,知识图谱基本就通了。