DrugCLIP:基于对比学习的蛋白质-分子跨模态检索与虚拟筛选新范式 1. 从“看图说话”到“看靶配药”DrugCLIP的跨界启示最近在跟一个做药物发现的朋友聊天他正为一个新靶点筛选先导化合物发愁。传统的虚拟筛选要么是基于分子对接的计算模拟耗时耗力要么是基于已知活性数据的机器学习模型严重依赖标注数据对于全新的、数据稀少的靶点往往束手无策。他半开玩笑地说“要是能像教AI看图说话一样让模型‘看’一眼蛋白质结构就能‘说’出哪些分子可能有效那就好了。”这句话点醒了我。这不就是“CLIP”的思路吗在计算机视觉领域OpenAI的CLIP模型通过对比学习将图像和文本映射到同一个语义空间实现了“图文互搜”的惊人能力。一张猫的图片即使训练数据里没有“猫”这个标签模型也能通过语义关联找到描述它的文本。那么在药物发现这个领域我们能不能也构建一个“蛋白质-分子”的CLIP呢让模型学会理解蛋白质靶点的“功能语言”和药物分子的“结构语言”在同一个空间里衡量它们的“匹配度”从而绕过繁琐的对接计算和稀缺的活性数据直接进行高效的虚拟筛选。这就是DrugCLIP的核心思想。它不是一个具体的、已发布的工具名称而是一个极具潜力的研究方向和技术范式。简单来说DrugCLIP旨在通过对比学习Contrastive Learning技术学习蛋白质和分子的通用、可对齐的表示Representation使得在表示空间中有相互作用的蛋白质-分子对彼此靠近而无相互作用的对彼此远离。一旦这个模型训练成功给定一个新的蛋白质靶点哪怕从未见过我们只需要计算其表示然后与海量化合物库中所有分子的表示进行快速的距离计算或相似度匹配就能高效地筛选出潜在的活性分子。这就像是为药物发现打造了一个“语义搜索引擎”。2. 为什么是“对比学习”虚拟筛选的范式革新要理解DrugCLIP的价值得先看看传统虚拟筛选的“痛点”。目前主流方法大致分两类2.1 基于分子对接的模拟方法这种方法像“锁钥模型”的计算机版本。我们需要蛋白质靶点的三维结构“锁”以及小分子化合物的三维结构“钥匙”。通过复杂的物理力场计算模拟小分子在蛋白质活性口袋中的各种结合姿态并打分评价结合强度如结合自由能。代表性工具有AutoDock Vina, Glide等。优点物理意义明确对于结合模式预测有一定解释性。缺点计算成本极高对接一个分子到一个靶点可能需要几分钟到几小时面对百万级、千万级的化合物库筛选周期以月甚至年计。精度依赖参数打分函数的准确性是瓶颈假阳性率高。依赖精确结构需要蛋白质的精确三维结构对于许多难以结晶的膜蛋白或无序蛋白区域此方法失效。2.2 基于机器学习的定量构效关系模型这类方法将药物发现视为一个监督学习问题。我们需要一个标注好的数据集{蛋白质分子活性值如IC50}。模型从这些数据中学习从“蛋白质-分子对”到“活性”的映射函数。深度学习模型如图神经网络GNN用于分子卷积神经网络CNN或循环神经网络RNN用于蛋白质序列在此领域应用广泛。优点一旦模型训练好预测速度极快毫秒级。缺点严重依赖标注数据高质量、大规模的蛋白质-分子相互作用数据极其稀缺且获取成本高昂。对于全新靶点即“冷启动”问题模型无能为力。可迁移性差在一个靶点家族上训练好的模型在另一个结构迥异的靶点上可能表现很差。黑箱模型预测结果缺乏像对接那样的直观物理解释。2.3 对比学习一条“少依赖标注”的新路对比学习的核心思想不是预测一个具体的标签如活性值而是学习一种“关系判断”拉近正样本对推开负样本对。在DrugCLIP的语境下正样本对已知有相互作用的蛋白质分子对。负样本对随机组合的、大概率没有相互作用的蛋白质分子对或已知无相互作用的对。模型的目标是学习两个编码器一个编码蛋白质一个编码分子使得正样本对在表示空间中的距离如余弦相似度尽可能大负样本对的相似度尽可能小。这带来几个根本性优势数据效率更高我们不需要精确的IC50值只需要二元标签有相互作用/无相互作用。这类数据相对更容易获取可以从公开的数据库如ChEMBL, BindingDB中通过设定活性阈值来构建。解决冷启动问题模型学习的是蛋白质和分子各自通用的“语义表示”。对于一个全新的蛋白质即使它从未在训练集中出现过只要编码器能从其序列或结构中提取出有意义的特征例如某个特定的酶催化口袋特征就能在表示空间中找到与它“语义”相近的分子。这突破了传统QSAR模型对同靶点数据的依赖。实现跨模态检索训练完成后蛋白质编码器和分子编码器可以将各自模态的数据映射到同一个空间。这意味着我们可以进行双向检索靶点→配体给定一个蛋白质在分子库中检索与其表示最相似的分子虚拟筛选。配体→靶点给定一个分子或一个副作用反向推测其可能作用的蛋白质靶点靶点垂钓或副作用机制解释。这正契合了我朋友的需求面对一个数据稀少的新靶点利用对比学习模型从海量无标注或弱标注的蛋白质和分子数据中学到的通用知识进行快速、高效的初筛。3. DrugCLIP的核心架构双塔模型与信息编码一个典型的DrugCLIP模型架构是一个“双塔式”的神经网络如下图所示概念示意[蛋白质输入] -- [蛋白质编码器] -- [蛋白质表示向量] | |--[对比损失函数]计算相似度拉近正对推远负对 | [分子输入] -- [分子编码器] -- [分子表示向量]下面我们拆解每个关键部分。3.1 蛋白质的“语言”如何编码蛋白质是一种由20种氨基酸按特定顺序排列而成的生物大分子。如何将这种一维序列或三维结构转化为计算机能理解的数字向量即表示学习是第一步。主流方法基于序列的预训练模型目前最主流、最有效的方式是使用在超大规模蛋白质序列数据库如UniRef上预训练好的语言模型。这些模型将蛋白质序列视为一种“生物语言”。ESM系列由Meta AI开发如ESM-2拥有高达150亿参数能生成每个氨基酸位置以及整个蛋白质的上下文感知的表示。这个表示蕴含了进化、结构和功能信息。ProtTrans系列基于Transformer架构如BERT, T5在蛋白质序列上训练同样能产生高质量的蛋白质表示。输入与处理对于DrugCLIP我们通常取这些预训练模型输出的[CLS]token的表示或对全体氨基酸表示进行池化如平均池化得到一个固定维度的向量如1280维作为整个蛋白质的“语义摘要”。进阶方法结合结构信息如果蛋白质的三维结构已知通过实验或AlphaFold2预测可以引入结构特征。图表示将蛋白质视为图节点是氨基酸残基边是空间距离或化学键。使用图神经网络GNN来学习结构感知的表示。表面口袋特征专门提取药物结合口袋的几何形状、静电势、疏水性等物理化学特征与序列表示融合。实操注意直接使用结构信息会增加计算复杂度和数据要求。在实际的DrugCLIP实现中往往优先采用基于序列的预训练模型因为其数据可得性极高所有蛋白质都有序列且预训练表示已经隐式包含了丰富的结构和功能信息效果通常已经非常强大。将结构信息作为补充特征是性能进一步提升的方向。3.2 分子的“语言”如何编码小分子药物通常用SMILES字符串或分子图来表示。基于SMILES的编码SMILES是一种用ASCII字符串描述分子结构的线性表示。我们可以使用专门在化学分子SMILES上预训练的语言模型如ChemBERTa, MolFormer来将SMILES字符串编码为向量。这种方式与蛋白质序列编码非常对称。基于分子图的编码这是目前更主流、更强大的方法。将分子视为图原子是节点化学键是边。节点特征原子类型、杂化状态、形式电荷、度等。边特征键类型、共轭、是否在环中等。模型使用图神经网络GNN如图卷积网络GCN、图注意力网络GAT或消息传递神经网络MPNN来迭代地聚合邻居信息最终通过图池化得到整个分子的表示向量。优势GNN能天然地捕捉分子的拓扑结构和官能团信息对药物分子的表征能力通常优于基于SMILES的模型。3.3 对比学习的“裁判”损失函数这是模型训练的灵魂它指导着双塔编码器如何调整参数。最常用的是InfoNCE损失或称NT-Xent损失其思想来源于SimCLR和CLIP。对于一个批次Batch内的N个蛋白质分子对我们计算所有蛋白质和所有分子表示之间的余弦相似度得到一个N×N的相似度矩阵。对角线上的元素是正样本对的相似度其他是非对角线上的负样本对相似度。对于第i个正样本对其损失函数为L_i -log(exp(sim(z_protein_i, z_mol_i) / τ) / Σ_{j1}^{N} exp(sim(z_protein_i, z_mol_j) / τ))其中sim是余弦相似度τ是一个温度超参数控制分布的尖锐程度。这个损失函数的直观解释是让第i个蛋白质与第i个分子的相似度远高于它与本批次内所有其他分子的相似度。同时我们也会计算从分子到蛋白质方向的对称损失两者相加得到总的对比损失。3.4 训练流程与数据构建数据准备从ChEMBL、BindingDB等数据库中收集蛋白质-分子相互作用数据。设定一个活性阈值如IC50 10 μM将数据转化为二元标签1表示有相互作用。对于每个有相互作用的对通过随机替换蛋白质或分子构造负样本对。确保正负样本比例平衡。模型初始化蛋白质编码器加载ESM-2等预训练权重通常冻结一部分底层微调顶层。分子编码器使用预训练的GNN或在化学数据集上从头训练。前向传播一个批次的数据分别通过蛋白质塔和分子塔得到两组表示向量。计算损失计算所有向量对的相似度矩阵进而计算InfoNCE损失。反向传播与优化通过梯度下降更新两个编码器的参数。评估通常在一个留出的测试集上评估模型检索的准确性如RecallK在前K个检索结果中命中真实活性分子的比例。注意温度参数τ是一个关键超参数。τ值较小会放大相似度差异使模型更关注困难的负样本τ值较大则会使分布更平滑。通常需要通过验证集进行调优。4. 从理论到实践构建一个简易DrugCLIP原型理解了原理我们动手搭建一个简化版的DrugCLIP以验证其可行性。这里我们使用PyTorch和PyTorch Geometric用于GNN框架并假设使用蛋白质序列和分子图作为输入。4.1 环境准备与数据加载# 创建环境并安装依赖 conda create -n drugclip python3.9 conda activate drugclip pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 根据CUDA版本调整 pip install torch-geometric pip install biopython transformers pandas scikit-learn pip install fair-esm # 用于ESM-2模型我们使用一个公开的小规模数据集进行演示例如从BindingDB下载部分人源激酶靶点及其活性分子的数据。import pandas as pd import torch from torch_geometric.data import Data, Batch from transformers import AutoTokenizer, AutoModel import esm # 假设我们有一个CSV文件列包括target_id, target_sequence, smiles, label df pd.read_csv(kinase_binding_data.csv) # 简单划分训练集和测试集 from sklearn.model_selection import train_test_split train_df, test_df train_test_split(df, test_size0.2, random_state42)4.2 定义蛋白质编码器使用ESM-2class ProteinEncoder(torch.nn.Module): def __init__(self, model_nameesm2_t33_650M_UR50D, embed_dim1280, proj_dim256): super().__init__() # 加载ESM-2模型和分词器 self.esm_model, self.alphabet esm.pretrained.load_model_and_alphabet_hub(model_name) self.batch_converter self.alphabet.get_batch_converter() # 冻结ESM的大部分层只微调最后几层 for param in self.esm_model.parameters(): param.requires_grad False # 解冻最后几层 for param in self.esm_model.layers[-2:].parameters(): param.requires_grad True # 投影层将ESM输出维度映射到与分子表示相同的空间 self.projection torch.nn.Sequential( torch.nn.Linear(embed_dim, 512), torch.nn.ReLU(), torch.nn.Dropout(0.1), torch.nn.Linear(512, proj_dim) ) def forward(self, protein_seqs): # protein_seqs: list of protein sequence strings batch_labels, batch_strs, batch_tokens self.batch_converter(protein_seqs) batch_tokens batch_tokens.to(next(self.parameters()).device) with torch.no_grad(): # 前向传播时冻结层部分不计算梯度 results self.esm_model(batch_tokens, repr_layers[33]) # 取第33层的表示 token_representations results[representations][33] # 取每个序列的[CLS] token即开头token的表示作为整个蛋白质的表示 protein_embeddings token_representations[:, 0, :] # 通过投影层 projected_embeddings self.projection(protein_embeddings) # L2归一化便于计算余弦相似度 projected_embeddings torch.nn.functional.normalize(projected_embeddings, dim-1) return projected_embeddings4.3 定义分子编码器使用GNNfrom torch_geometric.nn import GCNConv, global_mean_pool from torch_geometric.data import Data from rdkit import Chem from rdkit.Chem import AllChem class MolEncoder(torch.nn.Module): def __init__(self, node_in_dim78, edge_in_dim4, hidden_dim256, proj_dim256): super().__init__() # 简单的GCN编码器 self.conv1 GCNConv(node_in_dim, hidden_dim) self.conv2 GCNConv(hidden_dim, hidden_dim) self.conv3 GCNConv(hidden_dim, hidden_dim) self.projection torch.nn.Sequential( torch.nn.Linear(hidden_dim, proj_dim), torch.nn.ReLU(), torch.nn.Dropout(0.1), ) def forward(self, data): # data: PyG Batch object containing x, edge_index, edge_attr, batch x, edge_index, batch data.x, data.edge_index, data.batch x self.conv1(x, edge_index).relu() x self.conv2(x, edge_index).relu() x self.conv3(x, edge_index) # 图池化得到整个图的表示 graph_emb global_mean_pool(x, batch) # 投影层 projected_emb self.projection(graph_emb) # L2归一化 projected_emb torch.nn.functional.normalize(projected_emb, dim-1) return projected_emb4.4 定义对比损失函数def info_nce_loss(protein_emb, mol_emb, temperature0.07): 计算对称的InfoNCE损失 protein_emb: [batch_size, proj_dim], L2 normalized mol_emb: [batch_size, proj_dim], L2 normalized batch_size protein_emb.size(0) # 计算相似度矩阵因为已经归一化点积即余弦相似度 sim_matrix torch.matmul(protein_emb, mol_emb.T) / temperature # [batch_size, batch_size] # 标签对角线位置是正样本 labels torch.arange(batch_size).to(protein_emb.device) # 蛋白质到分子的损失 loss_p2m torch.nn.functional.cross_entropy(sim_matrix, labels) # 分子到蛋白质的损失 loss_m2p torch.nn.functional.cross_entropy(sim_matrix.T, labels) loss (loss_p2m loss_m2p) / 2 return loss4.5 训练循环# 初始化模型、优化器 device torch.device(cuda if torch.cuda.is_available() else cpu) protein_encoder ProteinEncoder().to(device) mol_encoder MolEncoder().to(device) optimizer torch.optim.AdamW(list(protein_encoder.parameters()) list(mol_encoder.parameters()), lr1e-4) # 假设我们有一个函数将SMILES转换为PyG Data对象 def smiles_to_graph(smiles): mol Chem.MolFromSmiles(smiles) # ... 这里省略具体的特征提取和图构建代码可使用rdkit和torch_geometric工具 # 返回一个PyG Data对象 return data # 简化训练步骤 for epoch in range(num_epochs): protein_encoder.train() mol_encoder.train() for batch in train_dataloader: # 需要自定义DataLoader来生成(protein_seq, smiles, label)批次 protein_seqs batch[protein_seq] smiles_list batch[smiles] # 获取蛋白质和分子表示 protein_emb protein_encoder(protein_seqs) mol_graphs [smiles_to_graph(s) for s in smiles_list] mol_batch Batch.from_data_list(mol_graphs).to(device) mol_emb mol_encoder(mol_batch) # 计算对比损失 loss info_nce_loss(protein_emb, mol_emb) optimizer.zero_grad() loss.backward() optimizer.step() # 在测试集上评估检索性能 # ... 评估代码4.6 虚拟筛选应用模型训练好后虚拟筛选就变得非常简单def virtual_screening(target_protein_seq, compound_smiles_list): 对单个靶点进行虚拟筛选 target_protein_seq: 靶点蛋白质序列字符串 compound_smiles_list: 待筛选化合物的SMILES列表 protein_encoder.eval() mol_encoder.eval() with torch.no_grad(): # 编码靶点 target_emb protein_encoder([target_protein_seq]) # (1, proj_dim) # 批量编码化合物库 compound_embs [] # 这里可以分批次处理大型化合物库 for smiles in compound_smiles_list: graph smiles_to_graph(smiles).to(device) emb mol_encoder(graph) compound_embs.append(emb) compound_embs torch.cat(compound_embs, dim0) # (N, proj_dim) # 计算相似度 similarities torch.matmul(target_emb, compound_embs.T).squeeze(0) # (N,) # 按相似度降序排序返回索引和SMILES sorted_idx torch.argsort(similarities, descendingTrue) ranked_smiles [compound_smiles_list[i] for i in sorted_idx.cpu().numpy()] ranked_scores similarities[sorted_idx].cpu().numpy() return list(zip(ranked_smiles, ranked_scores))这个原型清晰地展示了DrugCLIP的工作流程。在实际研究中还需要考虑更复杂的分子特征、更先进的GNN架构如Attentive FP, D-MPNN、更高效的大规模负采样策略以及更严谨的评估基准。5. 挑战、优化与未来展望尽管DrugCLIP思路诱人但在实际落地中面临诸多挑战这也是当前研究的前沿。5.1 核心挑战负样本的质量问题对比学习极度依赖负样本。随机采样的“负样本对”中很可能包含一些实际上有相互作用但未被数据库收录的“假阴性”。这会给模型带来噪声误导学习。如何构建高质量的负样本集如通过分子对接打分过滤掉可能结合的对或利用蛋白质家族信息是一个关键问题。表示空间的对齐与坍缩模型可能学到一种“偷懒”的解决方案比如将所有蛋白质或所有分子都映射到表示空间中一个很小的区域这样虽然损失函数值低但失去了判别能力。这被称为“表示坍缩”。需要设计更好的损失函数或正则化项来避免。多模态信息的融合蛋白质和分子都有丰富的多模态信息序列、结构、相互作用图谱、物化性质。如何有效地融合这些信息而不是简单地使用序列或图是提升模型性能的关键。例如可以将AlphaFold2预测的结构特征与ESM序列特征结合。评估标准的缺失如何公正地评估一个DrugCLIP模型的性能传统的虚拟筛选评估指标如富集因子、AUC仍然适用但需要构建更具挑战性的测试集例如包含大量未见过的蛋白质家族的“冷启动”测试集。5.2 可能的优化方向硬负样本挖掘在训练过程中动态地寻找那些与正样本相似度高即模型容易混淆的负样本重点学习区分它们。引入三元组损失除了正负样本对引入“锚点-正样本-负样本”三元组直接约束相对距离可能比InfoNCE更稳定。知识蒸馏利用计算成本高昂但更精确的分子对接程序或更复杂的深度学习模型作为“教师”来指导DrugCLIP“学生”模型的学习提升其表示的质量。大规模预训练在超大规模的未标注蛋白质序列和分子结构数据上进行自监督预训练让编码器先学好各自模态的通用表示再进行对比学习微调。这类似于自然语言处理中的“预训练-微调”范式。5.3 从“AI大模型训练”看DrugCLIP的演进最新的网络热词“AI大模型训练”与“人类学习”的对比恰好能映射到DrugCLIP的发展上。早期的虚拟筛选模型就像“题海战术”下的学生需要大量精确标注的习题蛋白质-分子活性数据才能学会解题。而DrugCLIP代表的对比学习范式则更像人类通过“观察和比较”来学习概念。我们不需要知道每张图片的详细描述强标注只需要知道“这张图配这段文字”是对的“那张图配那段文字”是错的弱监督/自监督就能建立起图文之间的语义关联。未来的DrugCLIP很可能走向“基础模型”的道路。就像GPT理解了人类语言CLIP理解了图文关系我们可以设想一个在数十亿蛋白质序列和数亿分子结构上训练出的“生物化学基础模型”。这个模型内化了蛋白质折叠的规律、分子合成的规则以及两者相互作用的基本原理。当面对一个全新的药物发现任务时它不需要针对该靶点进行重新训练只需通过简单的“提示”或“上下文学习”就能给出合理的分子建议。这将把药物发现的起点从“数据密集型”转向“知识密集型”极大地加速源头创新。在我个人的实践中尝试复现这类模型时最大的体会是数据管道构建和负采样策略的重要性往往超过模型结构本身。一个干净、无偏、涵盖足够多样性的数据集是模型成功的基石。另外不要一开始就追求最复杂的融合模型从简单的序列/图对比学习基线出发确保流程跑通、评估可靠再逐步加入结构特征、多任务学习等复杂模块是更稳妥的迭代路径。这个领域正在快速发展保持对最新预训练模型和损失函数设计的关注是跟上节奏的关键。