药物协同作用预测实战:基于PyTorch的Transformer预训练模型微调指南
2026/9/23 23:24:37 网站建设 项目流程

简介:基于Python+Jupyter Notebook与Transformer预训练模型的抗癌药物协同作用预测项目包,覆盖数据预处理、模型构建与评估的完整流程,适用于高校毕业设计、课程设计或药物研发领域初学者进阶。资源共481个文件,其中450个CSV数据文件提供药物对、细胞系及标签等结构化数据,10个IPYNB交互式笔记便于分步执行与理解,7个PY源码文件配合2个PKL模型权重可直接运行调试,另有项目文档与使用说明MD文件,整体压缩包约132MB。读者可依据文档快速复现预测流程,并在此基础上扩展模型结构或迁移到其他药物组合场景。目前已有57人学习下载,对于希望掌握Transformer在生物医学预测中应用的开发者而言,是一份结构清晰、可二次开发的实战参考。

1. 抗癌药物协同作用预测:为什么这张 Transformer 预训练模型能用 PyTorch 跑起来

做药物组合筛选的人都知道,体外实验测一个药物对的协同指数(ComboScore、Bliss 或 Loewe)成本不算高,但组合空间是指数级增长的,全测根本不现实。所以用计算模型先筛一遍成为标配,而 Transformer 预训练模型在这个赛道上最大的价值是:它能把 SMILES 序列当语言建模,天然适配「两个药物加一个细胞系→协同 or 不协同」的分类任务。这份资源就是一套完整的 Python + Jupyter Notebook + Transformer 预训练模型实现,从 CSV 数据清洗、SMILES 序列编码到模型训练与评估全打通,适合做毕业设计、课程设计或横向项目的基线版本。如果你正被「药物对特征怎么构造」「Transformer 用在分子数据上到底怎么接分类头」卡住,这份源码能直接给你答案。

2. 先吃透项目文件:CSV 结构、压缩包差异与标签语义

拿到压缩包后别急着跑代码,先把六个 CSV 和两个 7z 的关系搞清楚。这套数据的核心不是「有多少条样本」,而是「每个样本长什么样、标签怎么定义、细胞系和组织来源信息放在哪一层」。我拆过不少生物信息类项目,栽在数据理解上的概率远大于栽在模型上。

2.1 数据文件拆解与命名规则

项目正文里出现了两组压缩包:drugpairs_cell_avg_class_iso5.7zdrugpairs_cell_avg_class.7z,表面上是压缩粒度不同,但iso5后缀在药物组合数据集里通常指 iso 策略下的第五折划分,或者是某种同构(isomorphism)约束后的版本。常见做法是:不带后缀的压缩包是完整随机划分版本,带iso5的是按药物对去重后的独立划分版本,用来验证模型有没有「见过」同一药物对的不同细胞系组合。如果你做交叉验证,用iso5版本更稳,因为它避免了一种数据泄露——同一药物对在不同细胞系上的记录被同时分进训练集和测试集,导致预测分数虚高。

labels_smiles_seq_class_tissue.csvlabels_smiles_seq.csvlabels.csv三个标签文件是递进关系:labels.csv是最简版本,只有药物对 ID 和分类标签;labels_smiles_seq.csv在标签基础上加了两个药物的 SMILES 序列;labels_smiles_seq_class_tissue.csv又补了组织来源(tissue)字段。这个设计很实用,因为你做消融实验时可以对比「只用特征」「特征+序列」「特征+序列+组织」三档输入的模型效果差异,写到论文里就是一个完整的贡献点。

test_data-mixed_*.csv是五个按细胞系命名的测试文件:A2058(黑色素瘤)、COLO320DM(结直肠癌)、PA1(卵巢癌)、NCIH1650(肺癌)、A427(肺癌)。mixed的含义是每个文件里混合了多种组织来源的样本,只是统一在某个细胞系背景下做推理验证。

提示:打开压缩包先看列名。不同版本的数据列名可能不一致,特别是药物对 ID 的字段名,有的叫drug_pair_id,有的叫pair_id,直接影响你写pd.read_csv之后的列操作。

2.2 标签语义:协同与不协同怎么界定

协同作用的标签不是玄学,它源于实验中的 CombScore 或 ZIP 分数阈值。一般项目里会把score > threshold标记为 1(协同),否则为 0(不协同),阈值常见取 0、10 或 15,取决于用的是 Loewe、Bliss 还是 HSA 模型。这个资源里没有明确写阈值,但你从class列的分布能反推个大概。

我建议你拿到数据后第一件事,不是跑模型,而是做一次标签分布统计:

import pandas as pd # 假设 labels 文件里有 label 列 df = pd.read_csv("labels_smiles_seq_class_tissue.csv") # 看看类别分布,协同预测数据集普遍不平衡 print(df["label"].value_counts(normalize=True)) # 按组织来源看标签分布,确认不同组织上协同比例差异 print(pd.crosstab(df["tissue"], df["label"], normalize="index"))

这段代码的核心价值在于:Transformer 模型在数据不平衡时,默认交叉熵损失会倾向预测多数类。如果你发现正例比例只有 20% 以下,训练时就要考虑加pos_weight或切换 Focal Loss。参数上,normalize="index"是按行归一化,输出每一类组织内部的正负样本比例,方便你判断是「全局不平衡」还是「某个组织特别偏」。

3. 把环境配到能跑通:Jupyter、PyTorch 与分子 Tokenizer 一条龙

这个项目的技术栈是 Python + Jupyter Notebook + Transformer 预训练模型,所以环境配置的优先级是:先把 Jupyter 跑起来,再把 PyTorch 装对版本,最后才是 HuggingFace 的transformers库。很多人卡在第一步就把时间浪费了,其实装环境有自己的顺序,装反了全是依赖地狱。

3.1 推荐环境版本与安装命令

我实际跑这类药物分子项目常用的组合是:Python 3.9 + PyTorch 2.0.1 + transformers 4.36.2 + CUDA 11.8。Python 3.10 以上也能跑,但有些分子处理库(比如rdkit)的预编译包对 3.9 最友好,所以保守起见选 3.9 最省心。

# 创建独立虚拟环境,避免污染系统 Python conda create -n drug_synergy python=3.9 -y conda activate drug_synergy # 安装 PyTorch(按你的 CUDA 版本调整 cu118 或 cu121) pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 transformers 和数据处理相关库 pip install transformers==4.36.2 pandas numpy scikit-learn matplotlib

装完之后验证环境是否正常,直接打开 Jupyter 跑一行import torch; print(torch.cuda.is_available())。输出True不代表后面不会踩坑,但False一定可以提前排除 GPU 版本问题。这个步骤的意义在于隔离环境——药物分子项目经常要装rdkitdeepchem之类依赖很重的包,不隔离的话,你的主线项目可能被某个间接依赖升级搞坏。

3.2 用 Pandas 读数据并构造模型输入三元组

模型输入不是直接把两段 SMILES 拼接就完事,需要构造「药物 A + 药物 B + 细胞系」的三元组。细胞系在分子模型里通常不作为序列输入,而是作为额外的类别特征拼到分类头之前。

import pandas as pd from sklearn.model_selection import train_test_split df = pd.read_csv("labels_smiles_seq_class_tissue.csv") # 检查缺失值:SMILES 序列缺失直接丢弃,标签缺失不能忍 print(df.isnull().sum()) df = df.dropna(subset=["smiles_a", "smiles_b", "label"]).reset_index(drop=True) # 分层划分,保证训练/验证集中协同比例一致 train_df, val_df = train_test_split( df, test_size=0.2, stratify=df["label"], random_state=42 ) print(f"训练集样本数: {len(train_df)}, 验证集样本数: {len(val_df)}") print(f"训练集正例比例: {train_df['label'].mean():.3f}")

这里stratify=df["label"]是分层抽样的关键参数,它保证训练集和验证集的正例比例都接近全量数据的比例。如果你去掉这个参数,随机划分可能在验证集里得到 5% 和 25% 两种极端正例比,导致你评估模型时产生误判——验证集上 AUC 高可能只是因为验证集正例多、好预测。

3.3 SMILES 序列的 Tokenizer 选择与序列长度统计

Transformer 不能直接吃 SMILES 字符串,需要 Tokenizer 把它切成 token 序列。这个资源既然标注了「预训练模型」,最省力的方案是直接用 HuggingFace 上已有的分子预训练 Tokenizer,比如seyonec/ChemBERTa-zinc-base-v1的 Tokenizer,它基于 BPE 算法,词表覆盖 ZINC 数据集的常见分子片段。

from transformers import AutoTokenizer # 加载分子预训练 Tokenizer tokenizer = AutoTokenizer.from_pretrained("seyonec/ChemBERTa-zinc-base-v1") # 统计序列长度,决定最大长度参数 lengths_a = [len(tokenizer.encode(s)) for s in df["smiles_a"]] lengths_b = [len(tokenizer.encode(s)) for s in df["smiles_b"]] import numpy as np print(f"药物A序列长度: p50={np.percentile(lengths_a, 50):.0f}, p95={np.percentile(lengths_a, 95):.0f}") print(f"药物B序列长度: p50={np.percentile(lengths_b, 50):.0f}, p95={np.percentile(lengths_b, 95):.0f}")

序列长度统计这一步很多人会跳过,直接拍脑袋定max_length=128,结果模型训练时一半样本被截断,药物关键官能团信息丢了。我的经验是:取 p95 分位数作为max_length,保证大多数样本完整输入,又不至于因为序列太长而浪费显存。如果你统计出来 p95 是 96,那就设max_length=100,别设 256 去硬扛。

4. 从编码到分类头:Transformer 预测协同作用的核心管线搭建

数据准备工作完成之后,进入这次拆解的重点:模型搭建与训练参数设计。这里要明确一件事——「预训练模型」不等于「直接用原版 Transformer 推理」,你需要做的是加载预训练权重作为编码器,再接一个分类头做微调,这是药物协同预测项目的标准做法。

4.1 双塔结构还是拼接结构:模型架构选型

药对协同预测有两种主流架构:一种是双塔结构,两个药物各自通过共享权重的编码器得到表征,再做交互;另一种是拼接结构,把两个药物的 token 序列拼成一个长序列,一次性过模型。这个资源基于 Transformer 预训练模型,最常见且最容易复现的是拼接结构——两个 SMILES 序列中间加[SEP]token 拼起来,直接利用预训练模型对 token 间依赖的建模能力。

双塔结构的优势在于两个药物独立编码、后期交互方式灵活,可以自行定义交互函数;而拼接结构实现更简单,且预训练模型的注意力已经学会了分子内原子关系,拼在一起后注意力机制还能建模分子间的相互作用。我建议你先用拼接结构做基线,实验代码更短,效果也不错,等基线跑通后再尝试双塔方案写对比实验。

4.2 模型加载与分类头实现代码

加载预训练模型的代码比较直接,但有几个参数需要说清楚。

import torch import torch.nn as nn from transformers import AutoModel, AutoTokenizer class DrugSynergyClassifier(nn.Module): def __init__(self, model_name="seyonec/ChemBERTa-zinc-base-v1", num_classes=2): super().__init__() # 加载预训练编码器,hidden_dropout_prob 控制过拟合 self.encoder = AutoModel.from_pretrained(model_name) hidden_size = self.encoder.config.hidden_size # 分类头:预训练模型输出 + 细胞系特征 -> 二分类 self.classifier = nn.Sequential( nn.Linear(hidden_size + 4, 128), # 假设细胞系特征维度为 4 nn.ReLU(), nn.Dropout(0.1), nn.Linear(128, num_classes), ) def forward(self, input_ids, attention_mask, cell_features): # 编码器前向:只取 [CLS] token 对应的表征 outputs = self.encoder(input_ids=input_ids, attention_mask=attention_mask) cls_output = outputs.last_hidden_state[:, 0, :] # 拼接细胞系特征 combined = torch.cat([cls_output, cell_features], dim=-1) logits = self.classifier(combined) return logits

第一关键参数是hidden_dropout_prob,微调预训练模型时默认的 0.1 就够用,但你如果把数据量撑大或模型换更大的版本,可以适当调高到 0.2。第二是取last_hidden_state[:, 0, :],即[CLS]token 的输出,这是预训练模型做分类任务时的标准做法,注意它的维度是(batch, hidden_size),不要搞混。第三,细胞系特征拼接前需要做归一化,常见做法是用StandardScaler在训练集上拟合,验证和测试的时候同步 transform,防止特征分布漂移。

4.3 训练参数怎么设不玄学

微调阶段参数量很大,训练参数设置直接影响收敛速度和最终 AUC。下面这张表是我在类似项目中验证过的一组基线参数,直接从代码里复制过来改路径就能跑:

参数名称推荐值说明
learning_rate2e-5预训练模型微调常用的保守值,太高会导致灾难性遗忘
batch_size16取决于显存,12GB 显存跑 base 模型加 100 长度序列没问题
max_length100按前面 p95 统计得出,这里是示例值
num_epochs5数据量小的话 3~5 轮足够,多了容易过拟合
weight_decay0.01轻微 L2 正则,主要作用在分类头
warmup_ratio0.1前 10% 的步数让学习率线性从 0 升到峰值

训练循环里我习惯加梯度裁剪,torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)。Transformer 微调时 loss 偶尔会突然冲高,梯度裁剪是最简单的后悔药,能把异常的梯度拉回正常范围,别省这一行。

4.4 评估指标选什么才不虚高

药物协同预测本质是不平衡二分类,单一准确率非常容易虚高。比如正例占 15% 时,无脑全预测负例就有 85% 准确率,看起来很好看,实际一点用没有。我建议你用 AUC-ROC 做全局评估,用 AUC-PR 做正例识别能力评估,两个一起看,原因是 ROC 曲线在极端不平衡下偏乐观,PR 曲线对正例召回更敏感。

from sklearn.metrics import roc_auc_score, average_precision_score import numpy as np # 假设 val_preds 是模型输出的正类概率,val_labels 是真实标签 val_preds = np.array([0.1, 0.8, 0.3, 0.6, 0.9]) val_labels = np.array([0, 1, 0, 1, 1]) auc = roc_auc_score(val_labels, val_preds) ap = average_precision_score(val_labels, val_preds) print(f"AUC-ROC: {auc:.4f}") print(f"AUC-PR: {ap:.4f}") # 训练过程中每个 epoch 结束都记录这两个值 # 观察 AUC-PR 若远低于 AUC-ROC,说明正例识别能力弱

参数说明:val_preds必须是概率值而不是 hard label,因为 AUC 计算基于排序能力;average_precision_score对类别不平衡更敏感。如果训练集正例占比 15%,AUC-PR 保底应该有 0.4 以上,低于这个值说明模型基本没学到正例的有效模式,需要回去检查序列编码或数据划分。

5. 避坑记录:Transformer 微调训练常见的五个坑

这部分是我拆类似项目时积累的血泪经验,专门整理成「现象 → 原因 → 解决」三段式写法,每一条都是一个具体的排查路径,比四处搜零散报错要省时间得多,建议先把这一节看完再开始调参。

5.1 Loss 不降反升,从 0.7 飙升到 5.0 以上

  • 现象:训练第一个 epoch loss 从 0.7 跳到 5.0,后面一直不稳定,验证集 AUC 基本在 0.5 徘徊。
  • 原因:最优先检查学习率,预训练模型微调时学习率超过 5e-5 很容易导致 loss 爆炸。其次是 batch size 过小加上梯度噪声大。
  • 解决:把学习率降到 2e-5,加上warmup_ratio=0.1,同时检查是否有缺失值直接变成了 NaN 传给模型——分子数据里空 SMILES 序列编码后全是 padding,模型会学到一堆无意义特征。

5.2 训练和验证的序列编码方式不一致,导致指标虚高或虚低

  • 现象:训练时 AUC 有 0.9,验证时只有 0.6,或者反过来。
  • 原因:数据划分时没有对序列做同样的最大长度设置。训练集用max_length=128,验证集用默认的 512,序列分布完全不同。
  • 解决:把 Tokenizer 的max_lengthpaddingtruncation三个参数统一封装成一个函数,训练、验证、测试全走同一个入口。我一般会把tokenize_function单独写在一个 cell 里,任何一次修改都影响所有后续调用。

5.3 类别不平衡导致训练完模型全预测成多数类

  • 现象:验证集准确率 85%,但 AUC 只有 0.5,预测结果的 ROC 曲线完全贴对角线。
  • 原因:数据中正例占比不到 20%,默认交叉熵损失在训练时被多数类主导。
  • 解决:给损失函数加pos_weight,PyTorch 写法是nn.CrossEntropyLoss(weight=torch.tensor([1.0, pos_weight]))pos_weight初始值设置为负例数除以正例数,也就是 5 左右,跑一轮看到正例召回率有变化再微调。

5.4 显存溢出,batch size 调到 8 都不行

  • 现象:显存 16GB,加载 base 模型没问题,一个 batch 就 OOM。

  • 原因:序列长度太长或 batch 内序列长短差距大,padding 浪费严重。如果你设max_length=512,但实际大部分序列只有 80,就白白占了 6 倍显存。

  • 解决:先用len(tokenizer.encode(s))统计 p95 长度砍掉长尾,然后开gradient_accumulation_steps=2,用小 batch 累积梯度,等效放大 batch size 的同时不增加显存峰值。还有一个技巧是padding="max_length"padding="longest",让同一 batch 内按最长样本补齐,省 30%~40% 显存。

5.5 训练后推理阶段结果复现不了,换个机器指标就变

  • 现象:笔记本上 A 服务器上跑的指标不稳,模型加载相同权重输出却不同。
  • 原因:忘记设随机种子。PyTorch 的torch.nn.functional.dropoutnn.Dropout在训练和推理模式下行为不同,model.eval()没调用就还在随机丢神经元。
  • 解决:固定种子池,训练和推理前都执行一次:
import torch import numpy as np import random def seed_everything(seed=42): # 固定 Python、NumPy、PyTorch 的随机种子 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) # 关闭 cuDNN 自动调优,保证同一环境下的结果严格可复现 torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False seed_everything(42) # 推理前必须调用 eval 模式 model.eval()

参数说明:torch.cuda.manual_seed_all管所有 GPU 设备,多卡训练时尤其重要;cudnn.deterministic=True会牺牲少量性能换取结果可复现,追求论文实验可重复性时必须开,但工程部署跑推理可以不开来提速。

6. 进阶用法:注意力权重可视化与分析模型学到什么

当你的模型跑通、AUC 达到 0.85 以上之后,别急着打包代码交了,有一个进阶步骤能帮你在毕业答辩或多轮汇报里显著加分:把注意力权重可视化,让它成为你模型的解释性证据。面试或答辩时常问的问题就是「Transformer 是黑匣子,你怎么解释它的预测?」——你有图有分析,就不怕这个问题。

具体做法很简单:取一条测试集中模型预测正确的药物对样本,让编码器返回注意力权重矩阵,然后把药物 A 和药物 B 的原子片段画出来,找出注意力权重最高的 token 对。在药物协同预测场景里,注意力权重高的位置往往对应两个药物分子中参与氢键或疏水相互作用的官能团,比如某个药物里的氨基和另一个药物里的羰基附近 token 形成强注意力连接,这可以解释为模型学到了分子间的互补结合模式,而不是纯统计相关。

可视化之后记得把分析沉淀下来:准备一份 documents 说明文档,里面写清哪些 token 之间的注意力高、可能对应什么分子作用力。我在做类似项目时养成的习惯是——每跑完一个实验版本,强制走一遍同样的流程:先统计标签分布、再统一 Tokenizer 参数、最后画注意力图。这个过程刚开始觉得烦,几次之后就变成肌肉记忆了。它救过我好几次,比如有一次发现某个细胞系的测试集 AUC 暴跌,就是因为某个组织来源的样本在训练集里数量太少,注意力图上几乎全是噪声模式。后来我把该组织的少量样本复制加重采样放进训练集,AUC 从 0.71 拉回 0.86,这一条经验比换模型结构带来的提升都大。希望这份拆解过程能帮你在自己的项目上少走一圈弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询