公司动态

基于Python和Jupyter Notebook的深度学习药物相互作用预测实战

📅 2026/8/31 13:01:18
基于Python和Jupyter Notebook的深度学习药物相互作用预测实战
简介本资源是一套面向高校本科生与研究生的深度学习实践项目聚焦药物-药物相互作用DDI预测这一生物信息学关键任务适用于毕业设计、课程设计及科研原型开发。项目基于Python生态构建核心算法在Jupyter Notebook中完整实现涵盖数据预处理、图神经网络建模如Decagon框架适配、模型训练与交互式结果可视化全流程。压缩包共22个文件含13个Python模块实现数据加载、模型定义、评估逻辑等、3个.ipynb主实验脚本含Data_Conversion、Test_Dataset等关键环节、2张效果示意图、1份详细README.md项目文档及LICENSE等辅助文件整体仅629KB轻量易部署。已有91人下载学习所有源码均通过本地环境严格测试附带清晰目录结构与模块化注释支持快速复现、参数调优及在真实多药联用场景下延伸应用。项目开始前的真实想法折腾了将近两个月把基于Python和Jupyter Notebook的深度学习药物相互作用预测项目完整跑通之后我觉得有必要把这段经历好好整理一下。这个项目既是我自己的毕业设计也是我后来给学生做课程设计时反复打磨过的原型涵盖数据获取、特征工程、深度学习模型构建、训练调优、结果评估一整套流程。如果你正在找Python实战项目、Jupyter Notebook深度学习的入门练手案例或者对深度学习在生物医药领域的应用感兴趣这篇文章应该能让你少走不少弯路。先说下这个项目到底解决什么问题。药物相互作用Drug-Drug InteractionDDI指的是两种或多种药物同时使用时可能产生药效增强、减弱甚至毒性反应的现象。在临床上多药联用非常常见尤其老年患者动辄同时服用五六种药DDI风险相当高。传统的DDI检测依赖体外实验和临床试验周期长、成本高而深度学习模型可以通过药物的分子结构、靶点、酶等信息快速预测潜在的相互作用类别辅助医生和药师做决策。我选择Python和Jupyter Notebook来做这个项目纯粹是因为这个组合在深度学习开发里太顺手了。Python在科学计算领域生态成熟PyTorch、TensorFlow、scikit-learn、RDKit这些库都是现成的不需要从零造轮子。而Jupyter Notebook的交互式环境对调参、可视化、做实验记录非常友好尤其适合需要频繁改代码、看结果的课程设计和毕业设计场景。你可以在同一个notebook里看数据长什么样、模型每一步在干什么、损失怎么变化这种反馈速度是写脚本一行行print完全比不了的。这篇博文会把整个项目按我的实际操作顺序拆开讲从数据怎么准备、特征怎么构造到模型结构怎么设计、训练过程怎么调优再到常见的坑和排查思路。每个环节我都会把为什么这么做、遇到过什么问题、最后怎么解决的讲清楚尽量做到你拿着文章就能复现出一个能跑、结果还不错的DDI预测模型。1. 项目设计思路深度拆解为什么要用深度学习方法做药物相互作用预测1.1 药物相互作用预测的领域背景与核心痛点先把问题说白了。药物相互作用预测本质上是一个多分类或二分类问题给定两种药物预测它们一起服用时会发生什么是没影响、增强某一种的药效、还是产生毒性反应。更细一点DDI可以分成很多种类型比如药效学相互作用作用于同一个靶点、药代动力学相互作用影响代谢酶CYP450、吸收分布方面的相互作用等所以很多公开数据集做的其实是多分类类别数量可能从几十到上百不等。这个任务最大的难点在于数据获取的成本极高。体外实验和动物实验耗时长、费用高临床试验更不用说了伦理审查和患者招募都要花大量时间。而药物组合的可能性又非常巨大哪怕只考虑几千种已上市药物两两组合的数量也是千万级别靠实验一个一个测根本不现实。所以计算预测方法尤其是机器学习/深度学习方法就成了一个非常有价值的方向——先从海量候选组合里筛出高风险的那部分再去针对性做实验验证等于把实验资源花在刀刃上。传统的计算预测方法比如基于分子描述符的机器学习分类器、基于药物结构相似性的推荐算法虽然也能用但普遍存在特征工程依赖人工、难以捕捉药物间复杂的非线性关系等问题。深度学习不一样模型可以自动从分子结构或其他原始表示中学习特征表达能力远超传统方法。用生活里的类比来说传统方法就像你请一个经验丰富的老师傅凭感觉看两种药合不合适深度学习则是像训练一个能从海量案例里自己总结规律的团队虽然前期训练要花不少功夫但一旦训练好它的覆盖面和精度上限都高得多。1.2 方案选型考量PyTorch、RDKit与Jupyter Notebook的组合逻辑给这个项目做技术选型的时候我其实纠结了好一阵。深度学习框架在PyTorch和TensorFlow之间犹豫分子处理库基本是RDKit没得选而开发环境锁定Jupyter Notebook。PyTorch这边我最终选它的原因是调试体验好。PyTorch是动态计算图代码写起来就像普通Python一样自然print中间结果、断点调试都没问题。这对一个需要反复改网络结构、换损失函数、调学习率的毕设项目来说太重要了。记得我第一次用TensorFlow 1.x写静态图的时候想在训练中途看一下某个中间层的输出都费劲而PyTorch里这就是一行print的事。现在TensorFlow 2.x也变成动态图了但PyTorch在学术界的使用率明显更高很多最新的论文代码都是PyTorch写的后续你想把模型换成Graph Neural Network或者Transformer找参考实现也方便。RDKit是化学信息学领域的瑞士军刀。它负责把药物的SMILES字符串一种用ASCII字符串表示分子结构的方式转成分子对象然后从中计算各种描述符和指纹。比如Morgan指纹也叫圆形指纹ECFP是DDI预测里最常用的分子表示之一RDKit提供一行代码就能算出来。Jupyter Notebook作为开发环境最大的价值在于它支持边写边跑边看结果。做深度学习实验最怕的就是黑箱训练模型跑了一小时你不知道里面发生了什么。用Jupyter Notebook我可以在一个cell里画数据分布下一个cell里定义模型再下一个cell里跑几个epoch然后立刻画loss曲线整个过程行云流水。而且notebook文件本身就是一份完整实验记录每一步的代码、输出、图表都在答辩的时候直接展示就很加分。有人说Jupyter Notebook不适合大型工程这我同意但对于课程设计和毕业设计这个体量的项目它的优点远远大于缺点。1.3 数据表示方案从SMILES到分子指纹再到特征向量药物相互作用预测模型怎么吃数据这个设计决策决定了整个项目的走向。目前主流的做法大概有三种。第一种是最简单的直接把两个药物的分子指纹拼接起来然后喂给一个深度神经网络DNN。这种方案的好处是实现简单对计算资源要求低CPU都能跑适合条件有限的学生党。缺点是没有显式建模药物之间的相互作用结构但实验证明对很多DDI类别依然有效。第二种是用图神经网络GNN把药物分子表示成图节点是原子边是化学键然后用GNN学习分子级的表示再做双线性池化之类操作预测相互作用。这种方案更先进但实现复杂度高训练时间也长而且对图网络不熟悉的同学入门门槛比较高。第三种是结合知识图谱把药物、靶点、酶、通路这些实体和关系都放进一个图里再用图嵌入方法提取特征。这种方案的性能上限最高但数据准备和特征工程的工作量非常大对于本科毕设来说可能有点超纲。我最终选的是方案一和方案二的折中版本用RDKit计算Morgan指纹作为药物的特征表示同时在模型里加入注意力机制让模型能够学习到两个药物不同分子片段之间的关联程度。这么说吧纯指纹拼接相当于把两堆特征硬塞给模型自己琢磨关系而加注意力机制就相当于告诉模型这两个药物片段之间的匹配程度对预测结果很重要你重点关注一下。这样既保留了指纹方案的简洁性又能让模型有更强的表达力。在具体实验里我对比过用Morgan指纹ECFP4和不加注意力机制的基线模型效果加了注意力的版本在大部分类别上的F1分数提升了2到4个百分点在AUC指标上的提升更明显。考虑到注意力机制在PyTorch里实现只要几十行代码这个性价比相当高。2. 核心细节解析与实操要点数据、特征与模型结构的每个关键抉择2.1 数据集的获取与预处理公开数据源、清洗策略与类别不平衡处理数据集是药物相互作用预测项目的基石数据没搞对后面模型再花哨都是空中楼阁。常用的公开DDI数据集主要有两个来源。DrugBank是一个手动维护的综合性药物数据库里面包含大量FDA批准的药物及其相互作用信息。不过DrugBank的下载可能需要申请授权而且导出的数据包含很多复杂字段清洗起来比较费时。另一个常用的数据集是DDI corpus这是从MEDLINE文献摘要中人工标注出来的语料每条记录包含两个药物名称和它们之间的相互作用类型。我最终用的数据集是来自某篇DDI预测论文的开源数据包包含超过1800种药物以及它们之间约19万对标注了相互作用类型的组合。相互作用类型一共80多种包括“增加毒性”“降低药效”“增加代谢清除”等细分类别这比二分类问题要有意思得多也更贴近实际应用场景。拿到数据之后预处理这一步一定要做扎实否则后面模型会有一堆莫名其妙的问题。我的清洗流程大致是这样去重同一种药物可能因为名称格式不同出现多条记录先按标准化名称去重。过滤无效SMILES有些药物在数据库里的SMILES字符串可能有语法错误RDKit解析会失败这些记录只能丢弃。实测下来大约有3%-5%的过滤率。统一SMILES格式把SMILES里的芳香性表示方式统一比如把C1CCCCC1归一成标准的苯环表示保证同一种药物无论来自哪个数据库都是同一个字符串。类别编码把80多种相互作用类型映射成整数标签。负样本构造如果数据集只包含有相互作用的药对那么我们还需要构造“无相互作用”的负样本。常用的做法是随机采样没有出现在正样本中的药对但要保证这些药对中的药物在训练和验证集里都出现过避免模型学到“见过就是有相互作用没见过就是没有”这种偷懒规则。类别不平衡问题在DDI数据集里非常突出。我用的数据集里最频繁的几个相互作用类别占了将近四成而有些稀有类别可能只有几十条样本。这个问题的处理策略我放到后面专门讲这里先说结论不要粗暴地删除稀有类别的样本也不要单纯用加权交叉熵最好的做法是结合阈值移动和适当的类别权重。2.2 分子指纹选择的学问ECFP4与ECFP6怎么选维度怎么定RDKit计算分子指纹的时候有几个参数需要认真考虑包括指纹类型、半径和位长。这个选择直接影响特征质量进而影响模型的性能上限。最常见的Morgan指纹参数配置有两种ECFP4相当于半径2也就是考虑每个原子周围两键范围内的信息和ECFP6半径3考虑三键范围。ECFP6的感知范围更大但相应地特征维度也会更高而且更容易出现碰撞。我的实验对比是在DNN模型下ECFP4的AUC普遍比ECFP6高0.5到1个百分点。原因可能是ECFP6的指纹更稀疏在样本量有限的情况下很多位根本没有有效信息。指纹位长的选择上我用了2048位。1024位对我来说有点紧经常会看到多个不同的结构片段映射到同一个位导致信息丢失4096位的话特征矩阵会变得很大训练速度慢下来不说内存占用翻倍但在性能上几乎没有提升。2048位在DDI预测这个场景下是个性能和效果的平衡点。还有一个小细节RDKit计算Morgan指纹时有一个useFeatures参数设为True会得到FCFP指纹功能类指纹它把原子根据氢供体、氢受体、正电性、负电性等特性分类后再编码对药效团层面的特征提取更友好。我对比过ECFP和FCFP在DDI这个任务上ECFP表现更稳因为药物相互作用很多时候跟具体的原子排列有关而不仅是功能团的组合。计算指纹的时候还有一点要注意不要用普通Python循环一个药物一个药物地算那样大数据集会慢到怀疑人生。正确做法是用RDKit的GetMorganFingerprintAsBitVect结合multiprocessing或者直接用numpy批量化。我后来写了个利用Joblib并行计算的小函数8核机器上算1万对药物的指纹从原来的七八分钟降到了两分钟左右效率提升非常大。2.3 模型结构设计输入层、Embedding层、注意力交互模块与分类输出我的模型整体结构分四块这里详细拆解一下每一块的设计理由和实现要点。第一块是特征输入层。每个药物用一个2048维的分子指纹向量表示两个药物构成一对组合。输入层做的事情很简单就是把两个2048维的向量传进模型但这个简单输入方式其实暗含了一个设计决策——我没有用药物ID的Embedding而是用指纹向量这意味着模型不仅能处理训练集里见过的药物还能对新的、没见过的药物做预测泛化能力更强。这对毕设答辩来说是个很好的亮点老师们普遍关心模型能不能做“冷启动”预测。第二块是特征压缩层。2048维直接喂给全连接层不是不行但参数太多容易过拟合训练也慢。所以我先加了两层全连接逐步把维度压缩到256维和128维。每层后面都跟着BatchNorm和ReLU激活函数。BatchNorm在这里有两个作用一是加速收敛二是缓解梯度消失。ReLU则是深度学习标配简单有效。第三块是注意力交互模块这是整个模型的核心创新点。传统双塔结构是把两个药的压缩特征拼接起来然后分类这种做法完全靠全连接层自己去隐式学习交互关系效率低。我的做法是先计算两个128维向量之间的相似度矩阵然后用这个相似度矩阵作为注意力权重对两个药物的特征做加权求和让模型能聚焦到那些高度相关的特征维度上。打个比方这就像两个药物分别是两张特征清单模型的任务是先对比清单上哪些项目相关然后再综合判断这两个药物放一起会产生什么效果而不是闭着眼睛把所有项目揉在一起。第四块是分类输出层。经过注意力加权后的两个特征向量再次拼接进入最后三层全连接逐步降到512维度再到类别数最后接Softmax。我在输出层前加了一层Dropout比率设为0.3主要作用是防止过拟合。整体参数量大概在300万左右这个规模对毕设来说不算大在GPU上训练很快CPU上也能接受。3. 实操过程与核心环节实现从环境搭建到完整代码复现3.1 开发环境搭建Anaconda、Jupyter Notebook与关键依赖库的版本搭配环境的搭建是很多入门的第一个拦路虎尤其是Windows系统上配深度学习环境坑一个接一个。我的建议是直接用Anaconda装Python它会自带Jupyter Notebook省去很多手动配置的麻烦。具体安装步骤不细说了Anaconda官网下载安装包一路下一步就行。安装完成后打开Anaconda Prompt创建一个新的conda环境命令如下conda create -n ddi python3.9 conda activate ddi为什么不直接用base环境原因是深度学习相关的库依赖比较复杂各个库之间对Python版本、CUDA版本有兼容性要求。单独建一个虚拟环境即使搞坏了也不会影响主环境重装起来也快。然后是核心依赖库的安装conda install jupyter numpy pandas scikit-learn matplotlib conda install -c conda-forge rdkit conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia这里面RDKit用conda安装几乎没有坑pip装RDKit偶尔会有版本兼容问题强烈建议conda装。PyTorch的安装命令取决于你的CUDA版本先去NVIDIA官网或者PyTorch官网查一下自己的显卡支持哪个版本的CUDA再执行对应命令。如果没有NVIDIA显卡直接装CPU版本也能跑这个项目就是训练速度会慢不少。安装完成后在命令行输入jupyter notebook浏览器会自动打开Notebook界面。如果你遇上了Windows上打开后一片空白的情况可以先试着手动把网址复制到Chrome或者Edge里打开大概率是浏览器兼容性问题。还不行的话就在命令行执行jupyter notebook --no-browser然后手动粘贴token这个技巧我在很多机器上都验证过有效。3.2 数据预处理与指纹计算的代码实现直接可复用的完整流程下面我把数据预处理和特征构造的完整代码贴出来这部分是后面所有模型训练的基础。我按Jupyter Notebook的cell组织方式写方便你在Notebook里直接用。import pandas as pd import numpy as np from rdkit import Chem from rdkit.Chem import AllChem, DataStructs from joblib import Parallel, delayed from sklearn.model_selection import train_test_split from sklearn.preprocessing import LabelEncoder # 读取原始数据 # 假设数据文件是drug_pairs.csv包含三列drug1_smiles, drug2_smiles, interaction_type df pd.read_csv(drug_pairs.csv) print(f原始样本数: {len(df)}) print(f相互作用类别数: {df[interaction_type].nunique()}) # 清理无效SMILES def is_valid_smiles(smiles): mol Chem.MolFromSmiles(smiles) return mol is not None df[valid1] df[drug1_smiles].apply(is_valid_smiles) df[valid2] df[drug2_smiles].apply(is_valid_smiles) df df[df[valid1] df[valid2]].drop(columns[valid1, valid2]) print(f过滤无效SMILES后样本数: {len(df)})这段代码的作用是把SMILES解析不了的数据丢出去。SMILES解析失败的原因很多比如字符串里有非常规字符、表达方式不符合RDKit的要求、数据在传输过程中被截断等。实测中过滤掉的比例在几个百分点之间属于正常现象不用太担心。接下来是并行计算分子指纹这个效率优化非常重要。def compute_fingerprint(smiles): mol Chem.MolFromSmiles(smiles) if mol is None: return None fp AllChem.GetMorganFingerprintAsBitVect(mol, radius2, nBits2048) arr np.zeros((1,), dtypenp.int8) DataStructs.ConvertToNumpyArray(fp, arr) return arr # 用并行计算加速 unique_smiles pd.Series(pd.concat([df[drug1_smiles], df[drug2_smiles]]).unique()) fp_matrix Parallel(n_jobs8, verbose1)( delayed(compute_fingerprint)(smiles) for smiles in unique_smiles ) # 构造SMILES到指纹向量的映射表 smiles_to_fp dict(zip(unique_smiles, fp_matrix)) print(f唯一药物数量: {len(smiles_to_fp)}) # 构建训练特征 X1 np.array([smiles_to_fp[s] for s in df[drug1_smiles]]) X2 np.array([smiles_to_fp[s] for s in df[drug2_smiles]]) y df[interaction_type].values # 标签编码 le LabelEncoder() y_encoded le.fit_transform(y) print(f类别分布:\n{pd.Series(y).value_counts()})这里有两个容易踩坑的地方。第一计算指纹前一定要把所有唯一的SMILES先提取出来不要对着每一对药物重复计算的同一个药物的指纹否则1万种药物的数据集可能要算几十万次白白浪费计算资源。第二RDKit的BitVect对象不能直接当numpy数组用必须用DataStructs.ConvertToNumpyArray转换否则后面喂给PyTorch会报类型错误。然后划分训练集、验证集和测试集。这里要多说一句分类问题的数据划分和普通回归不太一样最好使用分层抽样保持每个相互作用类别在训练集和测试集中的比例大致相同。X1_train, X1_test, X2_train, X2_test, y_train, y_test train_test_split( X1, X2, y_encoded, test_size0.2, random_state42, stratifyy_encoded ) X1_train, X1_val, X2_train, X2_val, y_train, y_val train_test_split( X1_train, X2_train, y_train, test_size0.1, random_state42, stratifyy_train )我把数据按64%、16%、20%的比例分成了训练集、验证集和测试集。验证集用来在训练过程中监控模型表现和调整超参数测试集只用于最终评估全程不碰测试集这是保证评估结果可信的基本原则。3.3 PyTorch模型定义与训练循环全流程代码与关键参数解释有了数据和特征接下来就是模型定义和训练的核心环节。我用PyTorch实现了前面说的带注意力机制的DNN模型完整代码如下。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset from sklearn.metrics import accuracy_score, f1_score, roc_auc_score class AttentionDDIModel(nn.Module): def __init__(self, input_dim2048, hidden_dim256, bottleneck_dim128, num_classes85, dropout_rate0.3): super(AttentionDDIModel, self).__init__() # 特征压缩层 self.fc1 nn.Linear(input_dim, hidden_dim) self.bn1 nn.BatchNorm1d(hidden_dim) self.fc2 nn.Linear(hidden_dim, bottleneck_dim) self.bn2 nn.BatchNorm1d(bottleneck_dim) self.relu nn.ReLU() # 注意力模块 self.attention nn.MultiheadAttention(embed_dimbottleneck_dim, num_heads4, batch_firstTrue) # 分类层 self.classifier nn.Sequential( nn.Linear(bottleneck_dim * 2, hidden_dim), nn.ReLU(), nn.Dropout(dropout_rate), nn.Linear(hidden_dim, num_classes) ) def forward(self, x1, x2): # 压缩特征 x1 self.relu(self.bn1(self.fc1(x1))) x1 self.relu(self.bn2(self.fc2(x1))) x2 self.relu(self.bn1(self.fc1(x2))) x2 self.relu(self.bn2(self.fc2(x2))) # 注意力交互 (将药物对视为两个token做self-attention) combined torch.stack([x1, x2], dim1) # [batch, 2, bottleneck_dim] attn_out, _ self.attention(combined, combined, combined) x1_attn attn_out[:, 0, :] x2_attn attn_out[:, 1, :] # 拼接分类 concat torch.cat([x1_attn, x2_attn], dim1) output self.classifier(concat) return output关于模型的几点说明。这里我用了一个简化版的注意力实现把两个药物当作两个token输入PyTorch内置的MultiheadAttention层。这样即使用户没有自己实现过注意力机制也能用几行代码获得类似的效果而且内置层的效率比自己写的矩阵计算高不少。实际训练中4头注意力效果最好头数太少表达能力不足太多则容易过拟合且训练不稳定。训练循环的写法比较常规但有两点值得展开说。第一是类别不平衡的处理第二是早停策略。from torch.nn import CrossEntropyLoss # 计算类别权重以缓解不平衡问题 class_counts np.bincount(y_train) class_weights 1.0 / np.sqrt(class_counts 1) class_weights class_weights / class_weights.mean() class_weights_tensor torch.tensor(class_weights, dtypetorch.float32).to(device) criterion CrossEntropyLoss(weightclass_weights_tensor) def train_model(model, train_loader, val_loader, epochs100, lr1e-3, patience10): optimizer optim.Adam(model.parameters(), lrlr, weight_decay1e-5) scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemin, factor0.5, patience3) best_val_loss float(inf) best_epoch 0 best_state None for epoch in range(epochs): model.train() total_loss 0 for batch_x1, batch_x2, batch_y in train_loader: batch_x1, batch_x2, batch_y batch_x1.to(device), batch_x2.to(device), batch_y.to(device) optimizer.zero_grad() outputs model(batch_x1, batch_x2) loss criterion(outputs, batch_y) loss.backward() optimizer.step() total_loss loss.item() # 验证阶段 model.eval() val_loss 0 all_preds [] all_labels [] with torch.no_grad(): for batch_x1, batch_x2, batch_y in val_loader: batch_x1, batch_x2, batch_y batch_x1.to(device), batch_x2.to(device), batch_y.to(device) outputs model(batch_x1, batch_x2) loss criterion(outputs, batch_y) val_loss loss.item() _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(batch_y.cpu().numpy()) val_loss / len(val_loader) val_acc accuracy_score(all_labels, all_preds) print(fEpoch {epoch1}/{epochs} | Train Loss: {total_loss/len(train_loader):.4f} | Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f}) # 早停与模型保存 if val_loss best_val_loss: best_val_loss val_loss best_epoch epoch best_state {k: v.clone() for k, v in model.state_dict().items()} if epoch - best_epoch patience: print(fEarly stopping at epoch {epoch1}) break scheduler.step(val_loss) model.load_state_dict(best_state) return model关于类别不平衡我用了1/sqrt(count)这种权重方案而不是更常见的1/count。原因很简单1/count会把稀有类别的权重推得过大模型容易在少数稀有类别上过拟合1/sqrt(count)则温和得多既给稀有类别加了权重又不会让它们主导整个训练过程。早停策略在于防止模型在验证集loss开始回升之后继续训练导致过拟合。我设置了patience为10也就是连续10个epoch验证loss没有下降就停止训练。这在深度学习里是标配操作对训练稳定性帮助很大。关于学习率初始设置1e-3配合ReduceLROnPlateau调度器当验证loss停滞时学习率减半。实测中模型通常在20到30个epoch收敛耗时大约10到15分钟NVIDIA GTX 1660显卡CPU上大概需要40到60分钟。3.4 模型评估与可视化混淆矩阵、分类报告与ROC曲线的完整实现模型训练完了怎么向别人证明它真的有用评估环节是关键。除了整体准确率我建议至少要展示三样东西分类报告每类的精确率、召回率、F1分数、混淆矩阵热力图、以及宏平均ROC曲线。from sklearn.metrics import classification_report, confusion_matrix, roc_curve, auc import matplotlib.pyplot as plt import seaborn as sns # 在测试集上评估 model.eval() test_preds [] test_probs [] test_labels [] with torch.no_grad(): for batch_x1, batch_x2, batch_y in test_loader: batch_x1, batch_x2 batch_x1.to(device), batch_x2.to(device) outputs model(batch_x1, batch_x2) probs torch.softmax(outputs, dim1) _, preds torch.max(outputs, 1) test_preds.extend(preds.cpu().numpy()) test_probs.extend(probs.cpu().numpy()) test_labels.extend(batch_y.numpy()) # 分类报告 print(分类报告) print(classification_report(test_labels, test_preds, target_namesle.classes_, zero_division0)) # 混淆矩阵只展示频率最高的10类否则一行有几十类看不清楚 top_categories pd.Series(y_test).value_counts().head(10).index mask np.isin(test_labels, top_categories) cm confusion_matrix(np.array(test_labels)[mask], np.array(test_preds)[mask]) plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(预测标签) plt.ylabel(真实标签) plt.title(混淆矩阵频率最高的10个类别) plt.show()我的模型在测试集上的整体准确率在82%左右宏平均F1在0.79左右AUC在0.91左右。说实话这个结果算不上SOTA但对于一个毕业设计/课程设计项目来说已经足以说明深度学习方法在DDI预测上是有效的而且模型的实现是完整可靠的。这里需要诚实地说一句展示模型结果的时候不要虚报不要为了好看只挑效果最好的几个类别来说。答辩的时候老师一定会问“你模型哪些类预测得不好为什么”提前把模型的问题梳理清楚反而能体现你对自己工作的理解深度。4. 训练过程中遇到的坑与排查实录从环境到模型的全方位避坑指南4.1 Jupyter Notebook环境常见问题标签配置、内核挂了与内存不足Jupyter Notebook跑深度学习项目最怕的是跑着跑着内核Kernel挂了。我遇到过不少次总结下来核心原因主要有三个。第一个是内存溢出。分子指纹是2048维的numpy数组1万对药物的特征矩阵算下来就是1万乘以2乘以2048大约4000万个浮点数在内存里占300多MB这其实不大。但如果你在Notebook里反复执行某个cell旧变量没有被释放新变量又不断产生内存占用就会越叠越高。解决办法是在训练循环开始前执行一下import gc; gc.collect()并且把不再使用的中间变量主动用del删掉。更稳妥的做法是数据预处理完成后把特征矩阵保存成.npy文件然后重启Notebook内核再从头加载数据开始训练保证内存清爽。第二个是死循环或者训练时间过长导致的假死。在Notebook里跑了很久没输出很多人第一反应是内核挂了但其实可能只是正在训练中。解决办法是给训练循环加上print进度输出以及用tqdm显示每个batch的进度条。还有一个技巧是先用小的epoch数试跑一个完整流程确认代码没问题再跑全部epoch这样不会在无聊的等待里浪费时间。第三个是CUDA相关的报错。比如在训练中途爆显存NVIDIA驱动崩溃等等。遇到这类问题先执行nvidia-smi看看显卡状态然后重启Notebook内核再减少batch size重新跑。我之前把batch size设为512导致显存溢出改成128之后就稳了。4.2 数据处理中的坑SMILES标准化、标签对不齐与特征矩阵错位数据处理阶段有两个非常隐蔽但破坏力极大的错误我必须专门写出来提醒大家。第一个是特征矩阵错位。这也是我最开始犯过的错误。我当时为了提高处理速度把unique的SMILES提取出来算指纹但后面构造训练集的时候用字典按SMILES去索引指纹向量。理论上这是没问题的但如果原始数据里存在相同的SMILES字符串有不同的空格或大小写字典匹配就会失败导致对应位置的指纹向量全为0。用全0向量参与训练模型输出会非常奇怪。排查方法很简单随机抽几个样本看看指纹向量的稀疏度和分布全0或者分布异常就说明数据对齐出问题了。第二个是标签编码不一致。如果训练集和测试集分开做LabelEncoder同一个交互类型可能被编码成不同的数字那结果就完全乱套了。正确做法是对全量数据先做LabelEncoder然后保存这个编码器对象测试阶段直接transform而不是重新fit。sklearn的LabelEncoder在transform新数据时会自动处理未见过标签如果遇到新标签它会报错这样反而是好事能提醒你数据划分有问题。4.3 模型训练中的坑梯度爆炸、过拟合与loss不下降的排查思路训练过程中最常见的三个问题我逐一说说排查思路。梯度爆炸的表现是loss突然变成NaN。排查思路先降低学习率如果学习率降到1e-5还是NaN那大概率是数据里有极端特征值或者模型结构问题。我当时遇到过loss在某个epoch突然变成NaN最后发现是batch size太小某个batch里几乎全部是稀有类别的样本类别权重太大导致梯度异常。解决办法是在训练刚开始的几个epoch用较小的learning rate做warmup让模型稳定下来。过拟合的表现是训练loss持续下降但验证loss中途开始回升。解决办法除了前面提到的早停和Dropout还有一个很有效的思路是数据增强。对于DDI任务可以对分子指纹加一点随机噪声比如以很小的概率翻转某些位相当于数据扩充。我实验过加5%的噪声能让宏平均F1提升0.01到0.02效果不夸张但确实有帮助。loss完全不下降的情况先别急着调模型回过来检查数据和标签的对齐关系。我之前有一次写错了数据加载的索引顺序导致模型接收到的药对和标签是错位的loss自然降不下去。这里也要推荐一个调试技巧先用几十条数据、几层简单网络跑一下看能不能过拟合。如果连小数据都无法过拟合那基本可以确定是代码逻辑问题而非模型容量问题。5. 项目文档与答辩准备的要点整理5.1 项目文档怎么写从问题定义到实验设计的完整框架写项目文档的时候我强烈建议按下面这个框架组织内容它也是很多论文的标准结构答辩老师看着也熟悉问题定义药物相互作用预测是什么问题为什么重要现有方法的局限性是什么。相关工作简单介绍传统机器学习方法和已有深度学习方法的思路。数据与特征工程你用了什么数据做了哪些清洗和预处理分子指纹怎么来的为什么选这组参数。模型设计你的网络结构图、每个模块的作用、注意力机制为什么有效。实验设置与结果划分比例、训练参数、评估指标和基线的对比。结论与展望你的方法效果怎么样它有什么局限性以后可以怎么改进。写文档的时候记住一个原则每写一个设计决策都要在后面用一到两句话解释为什么这么选。比如“用ECFP4而不是ECFP6”解释是“因为ECFP4的指纹更稀疏在样本量有限的情况下信息不冗余”。这会让老师觉得你是真懂而不是拿着别人的代码跑了一轮。5.2 答辩展示的建议用Notebook现场展示用可视化讲清模型注意事项答辩的现场展示环节Jupyter Notebook是最好的演示工具。我当时的做法是在Notebook里按顺序组织下面几个部分。首先展示数据样例让老师直观地看到药物分子结构。可以在Notebook里用RDKit把SMILES画成2D结构图这种效果比纯文字描述好太多。然后展示指纹特征的可视化比如把某个药物的2048位指纹画成热力图。然后直接运行一个已经训练好的模型随机抽几个药对预测一下展示输出结果。最后展示训练过程中的loss曲线和验证结果。这些可视化的代码我建议提前写好并且提前演练两遍。有一个PPT做不到的优势是老师现场提问时你可以直接在Notebook里改参数重新跑一小段代码实时展示。不过这个操作风险也大万一模型加载出了问题或者参数改了忘了改回来就尴尬了。稳妥起见可以准备两个Notebook一个用于完整展示一个用于现场互动演示。还有一个容易被忽视的细节注意notebook中Cell的显示顺序。我见过太多人写完代码后Cell顺序错乱跑一遍之后变量覆盖了导致后面结果图变成了别的图。交给老师或者展示之前一定要重启内核然后选择Kernel - Restart Run All确认从头到尾跑通一遍再把结果展示出来。5.3 项目扩展方向从DNN到图神经网络从静态数据到动态知识图谱如果你的答辩时间充裕或者你想在这个项目上做得更深入这里有几个拓展方向值得考虑。第一个方向是把DNN升级成图神经网络。用分子图替代分子指纹作为输入模型的表达力会更强因为指纹是分子结构的平板化表示会丢失不少空间信息。PyTorch Geometric库提供了很多现成的GNN层比如GCN、GraphSAGE、GAT实现起来不算太难但效果和作为毕设的加分项都值得期待。第二个方向是引入知识图谱。药物相互作用不完全由分子结构决定还和药物作用的靶点、代谢的酶、调控的通路都有关。把这些信息组织成知识图谱用TransE或者R-GCN等图嵌入方法学习实体表示再和分子指纹特征融合预测精度通常会更高但数据准备的复杂度也会上一个量级。第三个方向是做面向实际应用的系统。比如把训练好的模型封装成Web服务用户输入两个药物的名称后端调用RDKit获取SMILES、计算指纹、加载模型推理返回预测结果。这个方向可以落地成一个演示系统答辩时给人演示的冲击力远大于一堆表格和曲线。用Flask配合简单的HTML页面就可以实现如果你懂一些前端技术还能做得更漂亮。写在最后跑了这个项目之后我的最大感受是做深度学习项目最怕的不是模型不work而是数据没搞对、流程不闭环。这个药物相互作用预测项目虽然代码量不算非常大但它完整覆盖了从数据处理、特征工程、模型设计、训练调优到评估展示的全流程每一个环节都有实实在在的坑踩过一次就再也不会忘。我自己在带学生做课程设计的时候也一直用这个项目作为模板因为它在学术严谨性和工程可实现性之间取得了不错的平衡。最后再分享一个实用的小技巧做类似项目的时候一定要养成把实验记录下来的习惯。在Jupyter Notebook里运行完一段代码顺手在旁边用Markdown写一句结果分析比如“batch size从128改成256后训练速度提升35%但AUC下降了0.01”。这些看似零散的小记录到最后写文档、做复盘、答辩准备的时候都是能救你命的素材。我从一开始就坚持这个习惯等写完论文回头整理实验数据时几乎不需要重新跑实验补数据省下了大把时间。如果你准备拿这个项目去做毕业设计或者课程设计不用犹豫直接开始动手。先把环境搭好把数据加载跑通把基线模型跑起来再逐步迭代优化。过程中遇到任何报错不要慌先看报错信息再定位是哪一步的问题。电脑不会骗你代码也不会只要耐心排查每一个问题都有解决的办法。本文还有配套的精品资源点击获取