简介:本资源是一套基于知识图谱与推荐系统的药物靶点相互作用预测Python项目源码,面向计算机相关专业学生,适用于课程设计、期末大作业或项目实战练习,也可作为生物信息学交叉方向的入门参考。压缩包共40个文件,约56KB,以9个py脚本为核心,涵盖deepdti、kge_rf、kge_nfm等模型实现与train_all.py训练入口,另含Pipfile、requirements.txt等依赖配置、README.md操作说明及LICENSE等辅助文件,目录按data、output、logs、eg_model等模块划分,结构清晰。项目完整呈现数据预处理、知识图谱构建、推荐算法实现与模型评估流程,涉及Pandas、NetworkX、Scikit-learn等常用库,并附操作指南帮助快速跑通。目前已有94人学习下载,适合希望将理论落地、积累完整项目经验的学生参考借鉴。
1. 药物靶点交互预测:从知识图谱到推荐排序的工程化落地
药物靶点交互(Drug-Target Interaction, DTI)预测这件事,本质上是在回答一个排序问题:给定一个药物,它最可能作用在哪些靶点上,以及这个相互作用的置信度有多高。传统做法靠湿实验筛选,周期长、成本高,一个靶点验证动辄数月。而把知识图谱和推荐系统拼在一起做,思路就变了——药物和靶点都是图里的节点,已知的交互是边,预测任务就变成了在图上做链接预测,再套一层推荐排序的框架把候选靶点按概率排出来。这套方案适合有 Python 基础、手头有 DTI 数据集、想快速跑通一个可复现 baseline 的从业者。我下面讲的路径,核心是用知识图谱构建药物-靶点-疾病的关系网络,再用推荐系统里的矩阵分解和神经协同过滤做打分排序,全程 Python 实现,不依赖任何闭源工具。
2. 知识图谱构建:药物、靶点、疾病三元组怎么落成可计算的图
2.1 为什么 DTI 预测需要知识图谱而不是一张交互表
如果只把药物-靶点交互存成一张二维表,你丢掉了大量上下文信息。同一个药物可能通过不同通路作用于多个靶点,靶点又关联到疾病、通路、基因本体,这些关系恰恰是提升预测精度的关键。知识图谱把这些实体和关系显式建模成三元组(头实体, 关系, 尾实体),比如(阿司匹林, 靶向, COX-1)、(COX-1, 关联疾病, 炎症)。有了这层结构,模型不仅能学到药物和靶点的直接交互,还能通过路径推理捕捉间接信号。常见做法是从 DrugBank、STRING、DisGeNET 这类公开库导出数据,清洗后统一成三元组格式。我一般会先把实体类型固定下来:药物、靶点、疾病、通路四类,关系类型控制在十种以内,避免图太稀疏导致 embedding 学不出来。
2.2 用 Python 把原始数据转成三元组并构建图结构
数据源拿到手通常是 CSV 或 TSV,字段命名五花八门。第一步是统一实体 ID,药物用 DrugBank ID,靶点用 UniProt ID,疾病用 UMLS CUI,这样跨库关联时不会因为名称拼写差异断链。下面这段代码做的是读取原始交互文件、生成三元组列表、并用 NetworkX 建图。
import pandas as pd import networkx as nx # 读取药物-靶点交互数据,假设列名为 drug_id, target_id, label dt = pd.read_csv("drug_target.csv") # 读取药物-疾病关联 dd = pd.read_csv("drug_disease.csv") # 读取靶点-疾病关联 td = pd.read_csv("target_disease.csv") triples = [] for _, row in dt.iterrows(): triples.append((row["drug_id"], "targets", row["target_id"])) for _, row in dd.iterrows(): triples.append((row["drug_id"], "treats", row["disease_id"])) for _, row in td.iterrows(): triples.append((row["target_id"], "associated_with", row["disease_id"])) G = nx.DiGraph() for h, r, t in triples: G.add_edge(h, t, relation=r) print(f"节点数: {G.number_of_nodes()}, 边数: {G.number_of_edges()}")这段逻辑的关键点在于:三元组的关系类型用边属性relation存,而不是拆成多张图。这样后续做图神经网络采样时,可以按关系类型过滤邻居。参数上,drug_target.csv至少要有三列,label 列暂时不用,因为知识图谱构建阶段只关心结构,不关心正负样本。如果数据量超过百万级边,NetworkX 内存会吃紧,这时候换 DGL 或 PyG 的异构图接口更合适,但中小规模用 NetworkX 足够跑通。
2.3 图结构存进 Neo4j 做可视化验证与路径查询
建完图别急着喂模型,先存进 Neo4j 看一眼。很多数据问题——比如某个药物孤立、某个靶点连接了上千个药物——在可视化阶段一眼就能发现。用py2neo或官方neo4jdriver 批量写入,注意用MERGE而不是CREATE,避免重复节点。
from neo4j import GraphDatabase driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password")) def insert_triples(tx, triples): for h, r, t in triples: tx.run(f"MERGE (a {{id: $h}}) MERGE (b {{id: $t}}) MERGE (a)-[:{r}]->(b)", h=h, t=t) with driver.session() as session: session.execute_write(insert_triples, triples)这里关系类型直接拼进 Cypher 语句,是因为 Neo4j 不支持参数化关系类型。实际用的时候要确保r来自白名单,别把用户输入拼进去。写入完成后跑一句MATCH (n) RETURN count(n)确认节点数对得上。如果发现节点数远大于预期,多半是 ID 大小写没统一,比如P12345和p12345被当成两个节点。
3. 推荐系统建模:把 DTI 预测转成排序问题
3.1 矩阵分解做 baseline:为什么它仍然是第一选择
知识图谱 embedding 方法(比如 TransE、RotatE)能学到实体向量,但直接拿来做 DTI 预测有个问题:它们优化的是链接预测的全局损失,对「某个药物最可能交互的 top-k 靶点」这个排序目标不够敏感。推荐系统里的矩阵分解天然就是为排序设计的——把药物-靶点交互矩阵分解成两个低秩矩阵,药物向量和靶点向量的内积就是交互得分。这个 baseline 训练快、可解释、调参直观,我一般先用它把 pipeline 跑通,再上更复杂的模型做对比。
3.2 用 PyTorch 实现带负采样的矩阵分解
DTI 数据有个特点:正样本(已知交互)少,负样本(未验证交互)多,而且负样本里混着大量「尚未发现但可能为真」的交互。直接随机采样负样本会引入噪声,常见做法是按药物-靶点度分布做加权采样,降低热门靶点被采成负样本的概率。
import torch import torch.nn as nn import numpy as np class MF(nn.Module): def __init__(self, n_drugs, n_targets, dim=64): super().__init__() self.drug_emb = nn.Embedding(n_drugs, dim) self.target_emb = nn.Embedding(n_targets, dim) nn.init.normal_(self.drug_emb.weight, std=0.01) nn.init.normal_(self.target_emb.weight, std=0.01) def forward(self, drug_ids, target_ids): d = self.drug_emb(drug_ids) t = self.target_emb(target_ids) return (d * t).sum(dim=1) # 负采样:对每个正样本,随机替换靶点生成负样本 def negative_sampling(pos_pairs, n_targets, neg_ratio=4): neg_pairs = [] for d, t in pos_pairs: for _ in range(neg_ratio): neg_t = np.random.randint(0, n_targets) while neg_t == t: neg_t = np.random.randint(0, n_targets) neg_pairs.append((d, neg_t)) return neg_pairs模型维度dim=64是常用起点,数据量大可以加到 128 或 256。负采样比例neg_ratio=4是经验值,太高会让训练偏向负样本,太低则学不到区分边界。损失函数用 BPR(Bayesian Personalized Ranking)比交叉熵更合适,因为它直接优化正样本得分高于负样本的概率。训练时注意把药物和靶点 ID 重新映射成连续整数,别直接用原始字符串 ID。
3.3 神经协同过滤:用 MLP 替代内积捕捉非线性交互
矩阵分解的内积假设药物和靶点向量在同一空间里线性交互,但实际 DTI 关系往往是非线性的。Neural Collaborative Filtering(NCF)的思路是把药物和靶点 embedding 拼接后过几层 MLP,让网络自己学交互函数。
class NCF(nn.Module): def __init__(self, n_drugs, n_targets, dim=64, hidden=[128, 64]): super().__init__() self.drug_emb = nn.Embedding(n_drugs, dim) self.target_emb = nn.Embedding(n_targets, dim) layers = [] input_dim = dim * 2 for h in hidden: layers.append(nn.Linear(input_dim, h)) layers.append(nn.ReLU()) layers.append(nn.Dropout(0.2)) input_dim = h layers.append(nn.Linear(input_dim, 1)) self.mlp = nn.Sequential(*layers) def forward(self, drug_ids, target_ids): d = self.drug_emb(drug_ids) t = self.target_emb(target_ids) x = torch.cat([d, t], dim=1) return self.mlp(x).squeeze()Dropout 设 0.2 是防止 embedding 过拟合,尤其当某些药物只有个位数交互记录时。隐藏层结构[128, 64]适合万级节点规模,再大就加宽第一层。训练时用 Adam 优化器,学习率 1e-3,batch size 256,跑 50 个 epoch 基本收敛。验证指标别只看 AUC,DTI 场景更关心 top-10 命中率,因为实际应用是给实验人员推荐候选靶点,不是做全局二分类。
4. 知识图谱 embedding 与推荐模型的融合:两条路怎么选
4.1 先 embedding 再排序:两阶段方案的工程优势
把知识图谱和推荐系统串起来,最稳的做法是两阶段:第一阶段用 TransE 或 RotatE 在知识图谱上预训练实体 embedding,第二阶段把药物和靶点的 embedding 作为特征喂给推荐模型。这样做的好处是解耦——图 embedding 可以离线跑,推荐模型可以快速迭代,不用每次改排序逻辑都重新训图网络。
from torch_geometric.nn import TransE # 假设已构建 PyG 的异构图数据对象 data model = TransE(num_nodes=data.num_nodes, num_relations=data.num_edges, hidden_channels=128) optimizer = torch.optim.Adam(model.parameters(), lr=0.001) for epoch in range(100): optimizer.zero_grad() loss = model(data.edge_index, data.edge_type) loss.backward() optimizer.step()TransE 的 hidden_channels 设 128 是平衡表达能力和内存的常用值。训练完后把药物和靶点节点的 embedding 取出来,拼到 NCF 的输入层,相当于给模型加了先验知识。注意 TransE 对一对多、多对一关系处理不好,如果图谱里存在大量「一个药物靶向多个靶点」的情况,换 RotatE 或 ComplEx 更合适。
4.2 端到端联合训练:什么时候值得上
两阶段方案的缺点是图 embedding 的优化目标和排序目标不一致。端到端联合训练把图神经网络和推荐排序损失接在一起,梯度可以从排序损失回传到图网络。代价是训练慢、调参难、容易过拟合。我的经验是:数据量超过十万级交互、且图谱关系类型丰富时,端到端能比两阶段提升 3-5 个点的 top-10 命中率;数据量小的时候,两阶段反而更稳。别一上来就搞端到端,先用两阶段把 baseline 跑出来,再决定要不要换。
5. 避坑与排查:DTI 预测 pipeline 里最容易翻车的五个地方
5.1 负样本泄漏导致验证指标虚高
现象:验证集 AUC 跑到 0.95 以上,但拿真实候选靶点做预测时命中率很低。原因:构建负样本时没有排除「已知但未标注」的交互,模型在训练时见过这些样本的正例版本。解决:负采样时从「确认无交互」的集合里抽,或者用药物-靶点对的文献共现频率做过滤,把高频共现但未标注的 pair 从负样本里剔除。
5.2 实体 ID 不统一导致图谱断链
现象:Neo4j 里查某个药物的邻居,发现只有两三个靶点,但原始数据里明明有几十条交互。原因:不同数据源用了不同的 ID 体系,比如 DrugBank ID 和 PubChem CID 混用,MERGE 时当成两个节点。解决:建图前先做 ID 映射表,所有实体统一到一套主键上,映射不上的记录宁可丢掉也别硬拼。
5.3 embedding 维度选太大导致过拟合
现象:训练集损失持续下降,验证集损失先降后升,top-10 命中率卡在低位。原因:embedding 维度设了 256 甚至 512,而实际交互数据只有几千条,参数量远超样本量。解决:从 32 或 64 起步,用验证集 top-k 命中率做早停,别盯着训练损失调维度。
5.4 忽略靶点冷启动问题
现象:模型对训练集中出现过的靶点预测很准,但新靶点(没有任何已知交互)的预测得分全是随机水平。原因:纯协同过滤方法无法处理冷启动,靶点 embedding 没有学到有效表示。解决:把知识图谱里的靶点-疾病、靶点-通路关系作为辅助特征,用图神经网络聚合邻居信息,让新靶点也能拿到有意义的初始 embedding。
5.5 评估指标选错导致模型选型偏差
现象:AUC 最高的模型在实际推荐场景里表现最差。原因:DTI 预测是 top-k 推荐任务,AUC 衡量的是全局排序能力,而实际只关心前十个候选里有多少是真阳性。解决:主指标用 Hit@10 和 NDCG@10,AUC 作为辅助参考。另外要做时间切分验证,用早期数据训练、后期数据测试,模拟真实发现场景。
6. 进阶技巧:用路径排序做可解释的靶点推荐
模型跑通之后,真正让实验人员愿意用你结果的关键是可解释性。他们不关心 embedding 内积是多少,他们想知道「为什么这个药物可能作用于这个靶点」。知识图谱在这件事上有天然优势——可以提取药物到靶点之间的路径,按路径置信度排序,作为推荐理由。
具体做法是:在 Neo4j 里查两跳和三跳路径,比如药物 -> 靶向 -> 靶点A -> 关联疾病 -> 疾病X <- 治疗 <- 药物B -> 靶向 -> 靶点B,把路径上的关系类型序列作为特征,训练一个轻量级 GBDT 做路径打分。路径特征包括路径长度、关系类型组合、中间节点度数。下面这段 Cypher 查的是药物到靶点的两跳路径:
MATCH path = (d:Drug {id: $drug_id})-[*2..3]-(t:Target {id: $target_id}) RETURN [r IN relationships(path) | type(r)] AS rel_types, length(path) AS path_len ORDER BY path_len ASC LIMIT 20拿到路径后,用 LightGBM 训练一个二分类器,标签是路径是否对应已知交互。预测时,对每个候选靶点提取 top-20 路径,取平均分作为可解释性得分,和协同过滤得分加权融合。权重我一般设 0.7 给协同过滤、0.3 给路径得分,具体看验证集表现调。
还有一个实用技巧是 embedding 可视化排查。用 UMAP 把药物 embedding 降到二维,按药理类别着色,如果同类药物聚在一起,说明 embedding 学到了有意义的表示;如果散得到处都是,回去检查图构建阶段是不是丢了关键关系。这个步骤花不了十分钟,但能帮你省下大量盲目调参的时间。
我自己踩过最深的坑是早期太迷信模型复杂度,上来就搭端到端图神经网络,结果数据量不够,调了两周还不如矩阵分解加好的负采样。后来养成习惯:任何新数据集,先用最简单的模型跑通全流程,把评估指标和可视化做扎实,再逐步加复杂度。每一步改动都要有验证集指标支撑,别凭感觉堆模块。希望帮到你。
本文还有配套的精品资源,点击获取