ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

RxnCLF反应基础模型解析:对比学习与Transformation-Aware实现

RxnCLF反应基础模型解析:对比学习与Transformation-Aware实现 反应活性预测Reactivity Prediction是计算化学与机器学习交叉领域中最典型的任务之一输入一组反应物、试剂和反应条件模型需要判断反应是否可能发生、主产物是什么以及目标产物的收率大概是多少。RxnCLF 这个名称来自 Contrastive Transformation-Aware Reaction Foundation Model核心想法是用对比学习让模型重点关注“从反应物到产物发生了什么变化”而不是只记住分子长什么样。相比传统分子表征方法这种建模方式把反应当作一个整体事件来学习对活性位点识别、产物预测和反应条件推荐都更有帮助。这篇文章从问题定义出发拆解 RxnCLF 这类反应基础模型背后的核心机制为什么要用对比学习、什么是 transformation-aware、正样本和负样本怎么构造、数据增强怎么做然后给出一套可运行的简化 PyTorch 代码用来训练一个极小的对比反应表示模型并讨论训练过程中的常见坑和可复用的检查清单。示例代码用于说明思路真实项目需要根据数据集规模、显存和下游任务重新调整。1. 先理解反应活性预测难在哪反应基础模型在解决什么问题1.1 反应活性预测的任务粒度反应活性预测并不是单一任务而是一组任务的总称。常见输出包括二分类该反应条件组合下反应是否发生。多分类主要产物是哪一个或哪个反应位点最可能被修饰。回归目标产物的收率是多少。排序给定多个候选反应路径哪一个更可行。这些任务有一个共同难点输入不是单个分子而是一个“反应事件”。事件里的关键信息不在某个原子上而在反应物到产物的变化过程中。一个羟基变成醚键一个溴被氨基取代一个双键被还原这些变化模式才决定了反应能不能发生、条件怎么选。传统的分子性质预测模型天然不擅长这类问题因为模型只见过孤立分子的结构描述。反应问题需要模型把反应物集合、产物和条件放在同一个上下文里学习。1.2 从分子表示到反应表示分子表示最常见的有 SMILES 字符串、Morgan 指纹和分子图。分子表示关心的是“这个分子长什么样”。反应表示关心的是“反应前后什么变了、什么没变”。反应 SMILES 使用分隔反应物和产物例如CCOCC(O)O表示乙醇被氧化为乙酸CCO是反应物CC(O)O是产物。更完整的反应 SMILES 可以用一下格式CCO.O[O]CC(O)O.O其中.分隔多个组分。建模时通常还要区分反应物reactants、试剂reagents和产物products因为试剂参与反应但不一定进入产物分子。反应表示要解决的核心问题是把反应物和产物两部分的编码融合成一个反应级表示并且这个融合过程不能只是简单相加或拼接否则模型会退化成“记住分子出现次数”的统计模型。这也是 RxnCLF 强调 transformation-aware 的原因。1.3 为什么选择对比学习而不是直接监督学习如果有大量带标签的反应数据直接用监督学习训练一个分类器或回归器即可。但真实情况是有明确反应活性标签的数据少标签标注成本高。无标签的反应数据多大量文献和专利反应可以用规则自动抽取。下游任务种类多收率预测、位点预测、产物预测、条件预测都需要不同形式的标签。对比学习适合这种“先在大规模无标签数据上预训练再在少量标签上微调”的范式。它不要求样本有精确的化学标签只要求模型知道同一个反应的不同视角应该被编码得相近不同反应的表示应该被拉开距离。这种思路和自然语言处理里的预训练模型一致先用无监督目标学会通用的表示再用有监督目标适配具体任务。RxnCLF 用对比学习替代传统掩码重建目标目的是让表示空间更直接地反映“反应变换”的相似性和差异性。2. RxnCLF 的核心机制对比变换感知2.1 Transformation-aware 要学的是什么Transformation-aware可直译为“变换感知”通俗理解是模型必须看到反应物到产物之间发生了哪类化学变换而不是只看反应物或产物单独的特征。一个官能团反应可以抽象成变换模板例如变换类型示意反应关注点氧化醇到醛、醛到酸含氧官能团变化还原酮到醇、硝基到胺含氧官能团减少取代卤代烃到胺、卤代烃到醚离去基团和亲核试剂偶联芳基卤与硼酸偶联芳环上的碳碳键形成加成烯烃加氢、双键加溴不饱和键被饱和化如果模型只编码反应物就无法区分不同的产物走向如果只编码产物就无法判断该产物是从哪种反应物转化来的。Transformation-aware 要求编码器把“反应物表示”和“产物表示”组织成一组可比较的变换表示使相同变换类型的反应在表示空间中聚集不同变换类型的反应彼此远离。2.2 对比学习的正负样本设计对比学习的前提是构造正样本对和负样本对。在分子和反应领域正负样本并不像图像那样天然存在需要结合化学背景设计。常见的反应级正样本对构造方式有对同一个反应 SMILES 做随机增强得到两个视图视为正对。同一个反应使用不同分子指纹半径或不同原子构象得到两个视图视为正对。同一个反应拆出“完整反应视图”和“只含关键变化片段视图”视为正对。负样本对通常是不同反应之间任意组合。同一反应物换成不同产物模拟“相似反应物但反应结果不同”的难负样本。同一产物换成不同反应物模拟“相似产物但来源不同”的难负样本。实际训练中最常用的负样本是 in-batch negative即当前 batch 内其他样本作为负样本。这样不需要额外采样计算效率高。问题是当 batch 内样本来自同一化学类别时负样本区分度不够模型容易学到粗糙的类别边界。更细致一些负样本可以分为简单负样本和难负样本。简单负样本来自完全无关的反应模型很快就能区分。难负样本来自只差一个官能团或只差一个原子位置的反应它们才是真正影响表示质量的关键。2.3 对比损失的基本形式反应级对比学习通常使用 InfoNCE 形式的损失也常称为 NT-XentNormalized Temperature-scaled Cross Entropy。对于一个 batch 里的某个样本正样本对其他所有样本来说都是负样本模型要学到的就是让正样本对的相似度尽可能高负样本对的相似度尽可能低。损失函数可以写成L -log( exp(sim(z_i, z_i) / tau) / sum_j exp(sim(z_i, z_j) / tau) )其中sim通常使用余弦相似度tau是温度系数。tau越小损失越关注最难区分的负样本tau越大负样本之间的惩罚越平滑训练更稳定但区分度可能下降。InfoNCE 的核心思想是把正对识别问题当做一个N选 1 的分类问题。模型在 batch 内找到正样本对应的那个位置并最大化该位置的分类概率。3. 数据准备反应数据的表示、划分和增强3.1 反应数据怎么写成模型能学的输入实验数据一般以原文记录存在常见来源是专利反应数据库或文献抽取数据集。公开研究中常用的反应库包括美国专利商标局USPTO抽取的反应记录以及商业数据库中的反应查询结果。数据清洗是第一步。这一步决定后续所有环节是否可靠必须标准化反应 SMILES去除未参与反应的盐和溶剂。必须明确拆分 reactants、reagents 和 products。必须对原子映射atom mapping做校验确保产物中的原子能对应到反应物原子。需要检查价键合法性过滤无法解析的分子。清洗后反应样本可以统一为reactantsreagentsproducts然后使用 RDKit 等化学工具包转换成模型输入。最简单的方式是用 Morgan 指纹虽然信息量有限但对验证思路足够。3.2 训练集、验证集和测试集怎么切分反应数据划分不能简单地随机洗牌。随机划分容易造成数据泄漏因为同一分子骨架可能同时出现在训练集和测试集中模型记住了骨架而不是学到了变换规律。更合理的划分方式有划分方式做法优点风险随机划分按行随机分配简单分布一致数据泄漏风险高反应模板划分按反应类型模板分组评估跨模板泛化模板定义困难分子骨架划分保证测试集骨架不在训练集更接近真实新分子数据利用率下降反应中心划分按反应中心图分组评估新的化学空间需要原子映射信息推荐至少使用分子骨架或反应模板划分来评估泛化能力。如果数据显示反应类型不平衡还要按类型做分层采样。3.3 数据增强随机扰动和变换视角对比学习依赖增强augmentation来构造正样本对。反应数据的增强和图像不同不能随意破坏分子结构必须保证增强后的样本仍然是化学上可合理解释的。常用增强策略包括原子掩码随机将部分原子替换为掩码标记迫使模型从剩余结构推断全局变换。键删除随机删除一个或多个化学键模拟不完整信息。片段丢弃随机丢弃一个非反应中心的支链片段。标准化视角同一反应分别以“反应物视角”和“产物视角”计算指纹再把两个视角映射到同一空间。图扰动对分子图做子图采样产生两个不同的子图视图。需要注意增强不能破坏反应中心。如果掩码恰好掩盖了发生反应的那个原子正样本对就失去了对比学习意义模型反而会学到错误的不变性。因此增强策略应该优先保护反应中心原子。4. 最小可运行示例PyTorch 实现简化版对比反应表示学习下面用一个最小示例说明整个训练流程。这个示例只用于验证思路不追求化学精度也不代表 RxnCLF 的真实架构。真实项目中应该用图神经网络或 Transformer 编码器替代这里的全连接网络。4.1 环境准备假设使用 Python 3.9 以上版本需要安装以下依赖pip install torch rdkit numpy验证安装python -c import torch; import rdkit; print(torch.__version__); print(rdkit.__version__)如果本机没有 CUDA 环境训练会回退到 CPUbatch size 需要调小。4.2 示例数据先准备一小批反应 SMILES 作为示例。真实实验中最少需要几万条反应数据这里只演示代码链路。SAMPLE_RXNS [ COCOc1ccccc1, CC(O)OCC(O)Oc1ccccc1, c1ccccc1Brc1ccccc1N, c1ccccc1Brc1ccccc1C, c1ccccc1Ic1ccccc1C#N, CC(O)c1ccccc1CC(O)c1ccccc1, OCc1ccccc1OCc1ccccc1, CC(C)OCC(C)O, CC(C)(C)OC(O)OCC(C)(C)O, COc1ccc(CO)cc1COc1ccc(CO)cc1, ]这些字符串中有些只包含反应物和产物没有试剂部分。可以把它们统一处理成reactantsreagentsproducts形式没有试剂时试剂部分留空。4.3 特征生成用 RDKit 将反应物和产物分别转成 Morgan 指纹再拼接在一起作为模型输入。import numpy as np from rdkit import Chem from rdkit.Chem import AllChem def mol_to_fp(smiles: str, n_bits: int 1024, radius: int 2): mol Chem.MolFromSmiles(smiles) if mol is None: return None fp AllChem.GetMorganFingerprintAsBitVect(mol, radius, nBitsn_bits) return np.array(fp, dtypenp.float32) def split_reaction(rxn: str): parts rxn.split() if len(parts) 2: reactants, products parts[0], parts[1] reagents elif len(parts) 3: reactants, reagents, products parts else: raise ValueError(funsupported reaction format: {rxn}) components [] for part in (reactants, reagents, products): components.append(part) return components上面的split_reaction虽然简单但已经能区分两段和三段格式。真实处理时还需要处理空字符串和.分隔的多个分子。构建 Datasetimport torch from torch.utils.data import Dataset class ReactionContrastiveDataset(Dataset): def __init__(self, rxn_list, n_bits1024, radius2): self.items [] for rxn in rxn_list: components split_reaction(rxn) fps [] ok True for smi in components: fp mol_to_fp(smi, n_bitsn_bits, radiusradius) if fp is None: ok False break fps.append(fp) if ok: # 简化反应物和产物拼接试剂可以后续单独编码 rct fps[0] pdt fps[2] if len(fps) 3 else fps[-1] self.items.append((rct, pdt)) def __len__(self): return len(self.items) def __getitem__(self, idx): rct, pdt self.items[idx] # view1反应物与产物直接拼接 x1 np.concatenate([rct, pdt]) # view2对反应物做一种简化增强得到第二个视图 rct_aug self._mask_bits(rct) x2 np.concatenate([rct_aug, pdt]) return torch.tensor(x1, dtypetorch.float32), torch.tensor(x2, dtypetorch.float32) staticmethod def _mask_bits(arr: np.ndarray, ratio: float 0.1): arr arr.copy() mask np.random.rand(arr.shape[0]) ratio arr[mask] 0 return arr这个增强写得很朴素只是随机把反应物指纹里的一部分位置置零。真正工程上不要这样做因为会破坏关键官能团位点。这里的目的是让对比学习有一个“不同视角”的输入来源。4.4 编码器和投影头编码器负责把输入向量映射到表示空间投影头负责把表示映射到对比损失计算的低维空间。import torch.nn as nn import torch.nn.functional as F class ReactionEncoder(nn.Module): def __init__(self, input_dim2048, hidden_dim256, proj_dim128): super().__init__() self.encoder nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), ) self.projection nn.Sequential( nn.Linear(hidden_dim, proj_dim), nn.BatchNorm1d(proj_dim), ) def forward(self, x): h self.encoder(x) z self.projection(h) return F.normalize(z, dim1)4.5 对比损失模块实现 NT-Xent使用 batch 内负样本。class NTXentLoss(nn.Module): def __init__(self, temperature0.07): super().__init__() self.temperature temperature def forward(self, z1, z2): batch_size z1.size(0) labels torch.arange(batch_size, devicez1.device) logits torch.matmul(z1, z2.T) / self.temperature loss F.cross_entropy(logits, labels) return loss这个实现假设z1[i]和z2[i]是同一个反应的两个视图的正对。torch.matmul(z1, z2.T)得到N x N相似度矩阵对角线元素是正对相似度其他位置是负对相似度。4.6 训练循环import torch.optim as optim from torch.utils.data import DataLoader def train_one_epoch(model, loss_fn, loader, optimizer, device): model.train() total_loss 0.0 for x1, x2 in loader: x1 x1.to(device) x2 x2.to(device) z1 model(x1) z2 model(x2) loss loss_fn(z1, z2) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader) if __name__ __main__: device torch.device(cuda if torch.cuda.is_available() else cpu) dataset ReactionContrastiveDataset(SAMPLE_RXNS) loader DataLoader(dataset, batch_size8, shuffleTrue) model ReactionEncoder(input_dim2048).to(device) loss_fn NTXentLoss(temperature0.07) optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(20): avg_loss train_one_epoch(model, loss_fn, loader, optimizer, device) print(fepoch {epoch 1}: loss {avg_loss:.4f})4.7 运行与预期输出正常运行时loss 会随着 epoch 逐步下降但不一定降到非常小的值因为示例数据只有 10 条batch size 8每个 batch 只包含 8 个负样本。epoch 1: loss 2.1234 epoch 2: loss 1.8765 epoch 3: loss 1.6541 ... epoch 20: loss 0.8921这个结果只证明代码链路可以跑通不代表模型学会了任何化学知识。训练一个有意义的反应基础模型至少需要上万条清洗后的反应数据并使用图神经网络或 Transformer 作为编码器。5. 关键参数与小规模训练策略5.1 温度系数 temperature温度系数tau是 InfoNCE 中最敏感的参数之一。它控制模型对负样本的“严厉程度”。参数值范围行为表现适用场景0.05 左右对难负样本惩罚大训练易震荡数据量大、负样本质量高0.07 到 0.1平衡收敛速度和区分度默认起点多数任务可用0.2 以上梯度平缓收敛慢区分度低噪声大、批次小的情况初调时建议从 0.07 开始。如果 loss 不降可以把tau调到 0.1 减小惩罚如果 loss 下降太快但下游任务指标不涨可能是模型过度拟合了 batch 内负样本的区分可以把tau调回 0.05。5.2 batch size 的影响对比学习中batch size 越大负样本越多表示质量越高。但显存有限需要取舍。学习阶段推荐学习环境batch size 64 到 256 即可。预训练阶段batch size 推荐至少 512最好达到 1024 或更高。如果显存不足可以先减小编码器维度而不是直接减小 batch size。也可以使用梯度累积来模拟更大的 batch。减小 batch size 时尤其是低于 64 时需要同步调高温度系数否则模型只能看到极少负样本对比损失区分能力不足。5.3 嵌入维度与投影头编码器输出的表示维度影响下游任务表达能力投影头维度影响对比学习的稳定性。常用配置参数学习环境预训练环境隐藏维度128 到 256512 到 1024投影维度64 到 128128 到 512编码器层数2 到 3 层 MLP6 到 12 层 Transformer投影头是临时结构预训练结束后通常只保留编码器下游任务使用编码器输出的表示不再使用投影头。5.4 预训练和微调流程反应基础模型的训练分两个阶段第一阶段大规模预训练。在无标签反应数据上优化对比损失让编码器学习通用反应变换表示。第二阶段下游微调。根据目标任务选择输出头反应是否发生在编码器后接二分类头。产物预测在编码器后接候选产物排序头。收率预测在编码器后接回归头。反应位点预测在编码器后接原子级预测头这需要编码器输出原子级表示而不是聚合后的分子级表示。微调时可以使用较低学习率一般比预训练学习率小 5 到 10 倍防止预设好的表示被少量标签破坏。6. 验证与评估表示质量如何衡量6.1 先用下游任务验证对比学习的 loss 下降并不等于模型学会了有用的化学知识。最可靠的验证方式是把预训练编码器接到下游任务上观察任务指标是否提升。最简单的做法是线性探测linear probe用预训练编码器把反应数据转换成表示向量。冻住编码器参数只训练一个线性分类头。在测试集上评估分类或回归指标。如果线性探测效果好说明表示本身已经包含足够信息。如果线性探测效果差但微调后效果好说明表示质量一般信息只存在于深层结构中需要进一步微调。6.2 评估指标选择不同下游任务使用不同指标任务类型推荐指标说明反应是否发生AUC-ROC、F1关注正负样本区分能力主产物预测Top-1 Accuracy、Top-5 Accuracy关注候选排序能力收率预测RMSE、R²关注误差大小和相关性反应位点预测原子级 F1、Top-1 命中率关注标记位点是否正确单独看一个指标不够例如收率预测只看 RMSE会忽略模型对不同反应类型的表现差异。建议按反应类型或分子骨架分层评估。6.3 表示可视化验证除了指标可视化是快速判断表示是否学到变换模式的常用手段。可以使用 t-SNE 或 UMAP 对反应表示降维相同反应类型的样本是否聚在一起。相同官能团变换的样本是否靠近。不同的取代反应、加成反应、消除反应之间是否有明显边界。可视化结果出现以下情况说明预训练有问题所有反应混在一团没有聚类结构。不同反应类型聚成很多碎片但没有化学规律。表示只按反应物分子大小聚类不按变换类型聚类。可视化不是严格验证方法但它能帮助快速定位表示空间是否退化。7. 常见问题与排查链路7.1 对比损失不下降现象训练多个 epoch 后 loss 几乎不变或者在高位震荡。排查顺序检查预处理管线确认反应物和产物指纹非全零。检查正样本对是否正确是否出现了同一个样本的视图完全一样的情况。检查温度系数过小的tau会导致梯度数值过大。检查是否使用了 BatchNorm 且 batch size 过小BatchNorm 在很小 batch 下不稳定。检查学习率建议从 1e-3 开始损失不降时尝试 3e-4 或 1e-4。处理建议先打印一个 batch 的相似度矩阵确认对角线位置的相似度明显高于其他位置如果对角线也不高说明正对构造或编码器有问题。7.2 表示退化和模式崩溃现象loss 很低但下游任务指标也很差或者所有样本的表示几乎相同。可能原因正样本对过于简单模型直接从输入位置找到答案没有学习化学变换。负样本全是简单负样本模型没有足够压力去细化表示。投影头输出维度太小丢失大量信息。增强强度过低两个视图几乎相同模型只用无关的捷径特征。处理建议增加难负样本、加强增强策略、适当增大投影维度并用下游线性探测监控表示质量。7.3 数据泄漏现象测试集指标非常高但新分子上泛化很差。常见泄漏来源随机划分导致同一骨架出现在训练和测试集。同一个反应的不同表述被拆分进不同集合。原子映射或模板信息在推理时不可用但训练时被当作输入特征。指纹计算时使用了全库的规范化信息。建议使用骨架划分或反应模板划分并在测试集上按分子骨架过滤来验证真实泛化能力。7.4 显存不足和训练速度慢现象OOM 或单 epoch 耗时过长。排查和处理现象可能原因处理方式OOMbatch size 过大减小 batch size开启梯度累积OOM嵌入维度太大缩小隐藏维度和投影维度训练慢数据预处理在迭代中重复计算提前离线生成指纹或图对象并缓存训练慢图编码器深层使用更大的 batch size 提高 GPU 利用率CPU 占满RDKit 解析耗时使用数据并行或多进程 dataloader常见经验是把指纹、图结构等预处理结果离线缓存成磁盘文件或内存数组不要在训练循环里反复调用 RDKit。7.5 排查清单遇到问题时按以下顺序检查原始反应 SMILES 能否被 RDKit 正确解析。正样本对是否来自同一个反应。负样本是否包含正样本的副本。输入特征是否标准化。温度系数、batch size 和学习率是否匹配。训练集和测试集是否存在骨架重叠。下游任务指标是否在合理基线之上。8. 最佳实践与可复用清单8.1 复现前检查清单在开始写流程之前先把以下内容固定下来反应数据的来源和清洗规则。是否包含原子映射是否保留试剂。训练集、验证集、测试集的划分方式。分子表示方式指纹、图还是 SMILES 编码器。增强策略清单哪些增强保留哪些保护反应中心。对比损失的具体公式和温度系数。预训练和微调是否共用编码器是否保留投影头。评估指标和基线模型。每条都要写在实验记录里。对比学习实验的随机性较大尤其需要固定随机种子def set_seed(seed: int 42): import random import torch import numpy as np random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)8.2 学习环境与生产环境的差异学习环境可以接受简化设计小数据集、小 batch、单卡训练、用指纹做输入。生产环境需要额外考虑数据管线化用离线任务清洗反应数据而不是在训练时临时解析。特征缓存分子指纹、图对象和原子映射离线保存训练加速明显。分布式训练大规模预训练需要多卡并行同时要考虑梯度同步和 batch 内负样本的全局一致性。模型服务部署后要处理新分子的标准 SMILES 生成失败、异常分子结构和指纹维度不一致等问题。监控记录预训练 loss、下游指标、推理耗时和分子解析失败率。8.3 扩展方向RxnCLF 这类 transformation-aware 对比模型可以扩展到多个方向单步逆合成把产物作为输入检索或生成可能的反应物组合。反应条件预测在反应物和产物已知时预测催化剂、溶剂和温度。选择性预测区分区域选择性、立体选择性等精细属性。多模态反应模型结合实验条件、光谱数据和文本描述统一学习反应上下文。图级与原子级统一编码让同一个模型既输出反应级表示也输出原子级表示支持位点预测。对新手来说最重要的练习不是立刻复现完整基础模型而是先在一个小的验证集上把对比训练的代码链路跑通然后逐步增加数据量、更换编码器、加入难负样本观察每一步对下游指标的影响。这种增量式实验方式比一开始就追求大规模预训练更利于理解模型行为。
返回列表