做化学AI方向或药物发现相关开发的同学,大概率都遇到过同一个尴尬:模型在公开 benchmark 上跑得不错,一换到自己业务里的真实反应数据,准确率就开始“自由落体”。反应性预测(Reactivity Prediction)作为化学信息学的核心任务,看起来只是“预测反应能不能发生、产物是什么”,但真正落地时,数据稀缺、标签噪声、反应类型分布不均等一系列问题,都会把模型的泛化能力一点点拽下来。
本文要解读的,是一篇把**对比学习(Contrastive Learning)和变换感知(Transformation-Aware)**机制引入反应基础模型的研究——RxnCLF。它目标很明确:通过一种更符合化学反应本质的预训练策略,让模型学到更鲁棒的反应表示,从而提升下游反应性预测的效果。
如果你之前对“反应基础模型”“对比学习在化学上的应用”只有模糊概念,这篇文章会从背景、方法拆解到实践思路,完整梳理清楚。适合正在做化学信息学、AI 制药、分子表征相关工作的算法工程师,也适合想了解“基础模型如何落地到科学计算领域”的研究者。
1. 背景与核心概念
1.1 反应性预测到底在解决什么问题
先从最直白的问题说起:给定一组反应物(Reactants)和试剂(Reagents),化学反应能不能发生?如果发生,主要产物是什么?反应性预测任务就是让模型回答这两个问题。
它在实际业务中的价值非常直接:
- 逆合成分析:给定目标产物,反推可能的反应路径,这要求模型理解“正向反应是否合理”。
- 反应条件推荐:预测特定反应能否在给定条件下进行,辅助化学家筛掉无效实验。
- 虚拟筛选:在大规模化合物库中,快速评估构建新化合物的可行性,减少湿实验试错成本。
从建模视角看,反应性预测通常有两种实现方式:
- 分类式:将反应性预测建模为二分类问题,判断给定反应式是否成立。
- 生成式:直接以产物 SMILES 为监督信号,让模型学习反应物到产物的映射关系。
RxnCLF 更偏向于前者——“CLF”大概率对应 Classification,也就是通过对比学习预训练一个强大的反应编码器,再去完成反应性分类任务。
1.2 深度反应预测面临哪些瓶颈
传统分子指纹加机器学习模型的方法(如 Morgan 指纹 + RF/SVM)已经用了很多年,优点是特征可解释、计算快,缺点也很明显:指纹是固定规则,无法表达分子在反应过程中的动态变化。
深度学习方法(如基于 Transformer 的分子反应模型)能自动学习反应表示,但有几个长期痛点:
数据问题。带标注的高质量反应数据非常昂贵。公开数据集(如 USPTO)规模有限,且存在大量重复、错误标注。预训练语言模型可以用海量无标注文本,但化学反应数据的“无标注”形态更难定义。
标签偏置。USPTO 等数据集中,常见反应类型(如酰胺化、Buchwald-Hartwig 偶联)样本量极大,而小众反应类型样本极少。模型天然倾向于学习大类的统计规律,导致在真实业务里的长尾反应上表现不佳。
反应表示的表达力不足。常见做法是直接拼接“反应物 + 试剂 + 产物”的 SMILES 序列,或者使用 Difference Fingerprint 这类手工特征。前者让模型很难关注到反应前后真正的变化点位,后者又丢失了分子局部语义。换句话说,模型学到的表示不一定抓住了“这个反应的本质变化是什么”。
RxnCLF 的出发点正是针对这些问题:能不能设计一种自监督预训练方式,让模型在没有任何人工反应类型标注的情况下,自己学会区分“这个反应发生了什么变化”?
1.3 基础模型与预训练思路为什么值得关注
大语言模型(LLM)领域的成功证明了“预训练 + 微调”范式具备很强泛化能力。同样的逻辑也适用于化学领域:先在大规模分子/反应数据上自监督预训练,再在特定下游任务上微调。
反应基础模型(Reaction Foundation Model)的通用做法是,使用大量无标注反应 SMILES 预训练一个编码器-解码器模型,让模型学会“读”反应序列。下游任务接入分类头或回归头即可迁移。
RxnCLF 在这个框架下多做了两件事:
- 对比学习目标,让模型把“原始反应”和“这个反应对应的反应类型信息增强后”的表示拉近。
- 变换感知(Transformation-Aware)机制,显式让模型关注反应前后分子结构的变化,而不仅仅是整体序列的语义相似性。
这两个设计的核心逻辑其实不难理解:如果我们希望模型真正理解一个反应,就不能只让它“看过”反应式,还要让它知道“反应前后哪些片段变了、哪些片段没变”。
2. RxnCLF 核心思想与技术拆解
2.1 从反应式到变换感知
先思考一个问题:当人类化学家看一个反应式时,大脑里发生了什么?
通常我们会做三件事:
- 识别反应中心(Reaction Center):哪些键断裂、哪些键生成。
- 识别不变骨架:哪些官能团和骨架在反应前后保持不变,这决定了反应的类型归属。
- 泛化反应模板:把反应中心 + 不变骨架组合起来,形成一个可复用的反应规则。
传统的模板匹配方法(如 RDChiral)本质上就是这个思路,但模板的覆盖范围受限于人工定义,长尾反应很难覆盖。
深度学习方法早期不显式建模这种“变化感知”,而是寄希望于模型从海量数据中隐式学会。RxnCLF 的“变换感知”则是把这种先验知识显式注入训练过程。
具体来说,RxnCLF 会构建一个变换感知的对比学习任务:
- 对同一个反应,生成“变换语义一致”的不同视角(View)。
- 这些视角共享相同的反应中心/变换类型,但可能拥有不同的分子骨架或取代基。
- 模型需要学会:无论分子整体多不同,只要反应的“变换语义”一致,它们的表示就应该在隐空间中对齐。
这类似于图像领域对比学习中的“同一样本的不同增强视角”,只不过这里的“增强”不再是随机裁剪、旋转,而是在化学反应语义层面进行的变换保持增强。
2.2 对比学习在化学反应表示中的作用
对比学习(Contrastive Learning)的核心思想是:在表示空间中,让正样本对(语义相似)的距离变小,让负样本对(语义不相似)的距离变大。
经典的 InfoNCE 损失可以简化为:
L = -log( exp(sim(z_i, z_j) / tau) / sum(exp(sim(z_i, z_k) / tau)) )其中:
z_i是锚点样本的表示向量;z_j是正样本的表示向量;z_k是所有负样本的表示向量;tau是温度系数,控制分布的锐利程度;sim()通常是余弦相似度。
在 RxnCLF 中,正样本对的设计非常关键:“原始反应”与“经过变换感知增强的反应”被视为正样本对。模型被强制学会过滤掉表层差异(如取代基不同、骨架不同),只保留真正的反应语义。
这种做法的好处是什么?
- 对反应类型变化更敏感:不同反应类型的表征在隐空间中自然分离。
- 对分子表层变化更鲁棒:同样的反应类型,换一个骨架或官能团位置,表示不会漂移太远。
- 长尾反应也能受益:即使某一反应类型样本量很少,只要它与其它反应共享某些变换片段,对比学习依然能学到可迁移的特征。
2.3 模型整体架构概览
从论文题目和行业通用做法推断,RxnCLF 的整体架构可以拆成四个层次:
输入层: 反应物 SMILES / 产物 SMILES / 试剂 SMILES 编码层: 分子片段编码器(Transformer / GNN) -> 反应序列编码器 -> 得到反应的整体表示 h 对比学习预训练层: 原始反应 -> h1 变换感知增强反应 -> h2 监督信号:最大化 h1 与 h2 的相似度 下游任务层: 反应性分类头(MLP) -> 二分类输出(反应是否成立 / 产物是否合理)值得说明的是,论文中不同实验设置可能使用不同的编码器骨干。对于化学信息学中的应用,常见的骨干网络包括:
- 基于 SMILES 的 Transformer/BERT 类模型。
- 基于 2D 分子图的 GNN(GIN、GAT、MPNN 等)。
- 基于分子指纹与 MLP 的简单基线。
RxnCLF 的价值更多在训练目标和数据增强策略层面,而不是发明一种全新的骨干网络。这个特性也降低了复现和使用门槛:只要替换或改造预训练目标,就可以把 RxnCLF 的对比学习策略套到现有的反应编码器上。
3. 关键模块与实现要点
3.1 数据增强策略:如何构造“变换感知”的正样本对
在化学领域的对比学习中,一个常见问题是:到底什么样的增强能保证语义不变性?
如果单纯对 SMILES 做随机打乱或随机掩码,很容易破坏分子语义,构造出“假正样本”。RxnCLF 的思路要更精细:
- 提取反应中心:通过 RDKit 等工具,原子映射(Atom Mapping)匹配反应前后差异,找到断键和成键位置。
- 局部替换:保持反应中心附近片段不变,替换反应中心外围的取代基或骨架片段。
- 保持变换类型:确保替换后仍然是同一个反应类型(如酰胺化仍然是酰胺化)。
这样构造出的正样本对,“反应的本质变化”保持一致,分子的外围结构可以有较大差异。
这里给出一个简化示例,说明什么是“变换感知增强”:
原始反应(SMILES 简化示意):
CCO.CC(=O)O>>CCOC(C)=O这是乙醇与乙酸生成乙酸乙酯的酯化反应。反应中心是羧酸与醇的脱水缩合区域。
经过“变换感知增强”后,可以把乙醇替换为丙醇,得到:
CCCO.CC(=O)O>>CCCOC(C)=O这个反应依然是酯化反应,反应类型一致,因此应作为正样本对。
而如果换成:
CCCO.CC(=O)O>>CCCOC(C)=O再比如把羧酸替换为胺,生成酰胺:
CCN.CC(=O)O>>CCNC(C)=O这个反应类型已经变化(酯化 -> 酰胺化),因此应该作为负样本或单独类别。
这个例子说明,RxnCLF 的数据增强不是随机扰动,而是精确的“变换语义保持”操作。要实现这一目标,不能只靠正则表达式或随机图扰动,需要借助 RDKit 的原子映射和反应中心提取能力。
3.2 编码器设计:从分子序列到反应表示
假设我们采用基于 SMILES 的 Transformer 编码器,输入可以这样组织:
[CLS] 反应物 SMILES [SEP] 试剂 SMILES [SEP] 产物 SMILES [SEP]也可以使用更结构化的输入,将反应物、试剂、产物分别编码后再融合:
# 伪代码:反应编码器示意 import torch import torch.nn as nn class ReactionEncoder(nn.Module): def __init__(self, d_model=256, nhead=8, num_layers=4): super().__init__() self.token_emb = nn.Embedding(vocab_size, d_model) self.pos_emb = nn.Embedding(max_len, d_model) encoder_layer = nn.TransformerEncoderLayer( d_model=d_model, nhead=nhead, batch_first=True ) self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) self.cls_token = nn.Parameter(torch.randn(1, 1, d_model)) def forward(self, input_ids, attention_mask): # input_ids: [batch, seq_len] batch_size = input_ids.size(0) cls_token = self.cls_token.expand(batch_size, -1, -1) x = self.token_emb(input_ids) + self.pos_emb(input_ids) x = torch.cat([cls_token, x], dim=1) # 根据 attention_mask 扩展后传入 Transformer x = self.encoder(x, src_key_padding_mask=attention_mask) return x[:, 0, :] # 取 [CLS] 作为整条反应的表示这段代码只是一个极简示意,真实项目中还需要考虑:
- SMILES 的 tokenizer(可以用 BPE 或字符级 tokenizer)。
- 反应物、试剂、产物的分隔符设计。
- mask 机制对 SMILES 语法合法性的影响。
3.3 对比学习训练目标
RxnCLF 的预训练核心是带变换感知的正样本对对比损失。可以这样描述训练流程:
# 伪代码:对比学习训练流程示意 def contrastive_loss(z1, z2, temperature=0.1): # z1, z2: [batch, d_model] z1 = F.normalize(z1, dim=-1) z2 = F.normalize(z2, dim=-1) logits = z1 @ z2.T / temperature # [batch, batch] labels = torch.arange(z1.size(0)).to(z1.device) loss = F.cross_entropy(logits, labels) return loss这是一个标准的 SimCLR 风格对比损失。每个 batch 中,原始反应与其变换感知增强反应构成正样本对,其它样本对构成负样本对。
此外,论文中可能还引入了额外的辅助损失,例如:
- 变换类型分类损失:预测反应属于哪种变换类型,帮助模型保留变换信息。
- 重构损失:从增强视角解码回原始反应,保证增强前后信息完整。
- 掩码语言建模损失:作为通用的自监督目标,保持基础模型的文本理解能力。
这些辅助目标是否真的存在,取决于论文的具体设置。但从方法论来看,纯对比损失在化学表示学习时,如果没有辅助目标,有时候会导致表示崩溃(Representation Collapse),需要引入正则化或不对称结构(如 SimSiam 的 stop-gradient 操作)来规避。
3.4 下游反应性预测
预训练完成后,RxnCLF 的编码器会被迁移到下游任务。对于反应性分类任务,通常做法是:
# 伪代码:反应性分类头 class ReactivityHead(nn.Module): def __init__(self, d_model=256, num_classes=2): super().__init__() self.classifier = nn.Sequential( nn.Linear(d_model, 128), nn.ReLU(), nn.Dropout(0.1), nn.Linear(128, num_classes) ) def forward(self, reaction_embedding): return self.classifier(reaction_embedding)训练策略有两种选择:
- 冻结编码器,只训练分类头。此时预训练表示直接用,训练快,适合数据量小的场景。
- 端到端微调。编码器和分类头一起训练,效果通常更好,但需要更小心地设置学习率,避免灾难性遗忘。
4. 完整实战思路:构建一个简易版 RxnCLF
说明:以下内容不是论文官方代码,而是基于方法论的一个可行实践框架。如果你打算在自己的数据上复现论文思想,可以参考这个思路落地。
4.1 创建项目结构
rxnclf_demo/ ├── data/ │ ├── raw/ │ │ └── reactions.csv │ └── processed/ │ └── contrastive_pairs.csv ├── src/ │ ├── __init__.py │ ├── augment.py │ ├── dataset.py │ ├── model.py │ ├── train_pretrain.py │ └── train_finetune.py ├── config/ │ └── config.yaml └── requirements.txt4.2 数据准备与反应中心提取
第一件事是准备合适的反应数据。公开数据可以使用 USPTO 数据集,也可以使用自己业务的反应数据库。
建议使用 RDKit 预处理数据,这里给出一个提取反应中心并构造正样本对的示例:
# src/augment.py from rdkit import Chem from rdkit.Chem import rdChemReactions def extract_reaction_center(rxn_smarts): """提取反应中心原子索引""" rxn = rdChemReactions.ReactionFromSmarts(rxn_smarts) # 简化处理:这里需要根据原子映射找出反应前后变化的原子 # 实际项目中可使用 rxn.GetReactantTemplate 与产物模板对比 center_indices = [] # 伪代码,真实实现需遍历 reactant 和 product 的原子映射 return center_indices def transform_aware_augment(rxn_smiles, vocab_replacements): """ 对反应 SMILES 进行变换感知增强。 思路:保留反应中心周围的局部环境,对外围取代基进行替换。 """ mol_rxn = rdChemReactions.ReactionFromSmarts(rxn_smiles) reactants = mol_rxn.GetReactants() products = mol_rxn.GetProducts() # 1. 解析原子映射 # 2. 找到非反应中心的取代基位置 # 3. 从 vocab_replacements 中随机替换合法片段 # 4. 重新组装反应 SMILES augmented_rxn_smiles = rxn_smiles # 占位 return augmented_rxn_smiles注意:上面的代码只是思路演示,真正的原子映射解析与片段替换需要较复杂的 RDKit 操作,不建议直接复制到生产环境。实际实现时要注意原子映射编号的保留,否则替换后可能生成非法 SMILES。
4.3 构造对比学习数据集
对于每个原始反应,生成一个或多个增强反应,形成正样本对。
# src/dataset.py import pandas as pd import torch from torch.utils.data import Dataset class ContrastiveReactionDataset(Dataset): def __init__(self, df, tokenizer, augment_fn, max_len=256): self.df = df self.tokenizer = tokenizer self.augment_fn = augment_fn self.max_len = max_len def __len__(self): return len(self.df) def __getitem__(self, idx): row = self.df.iloc[idx] rxn_smiles = row["rxn_smiles"] # 原始反应 orig_ids = self.tokenizer.encode( rxn_smiles, max_len=self.max_len ) # 增强反应 aug_rxn_smiles = self.augment_fn(rxn_smiles) aug_ids = self.tokenizer.encode( aug_rxn_smiles, max_len=self.max_len ) return { "orig_input_ids": torch.tensor(orig_ids, dtype=torch.long), "aug_input_ids": torch.tensor(aug_ids, dtype=torch.long), }4.4 预训练主流程
这里给出一个简化但可扩展的预训练脚本框架:
# src/train_pretrain.py import torch import torch.nn.functional as F from torch.utils.data import DataLoader from model import ReactionEncoder def train_epoch(model, dataloader, optimizer, device): model.train() total_loss = 0.0 for batch in dataloader: orig_ids = batch["orig_input_ids"].to(device) aug_ids = batch["aug_input_ids"].to(device) z1 = model(orig_ids) z2 = model(aug_ids) z1 = F.normalize(z1, dim=-1) z2 = F.normalize(z2, dim=-1) logits = z1 @ z2.T / 0.1 labels = torch.arange(z1.size(0)).to(device) loss = F.cross_entropy(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(dataloader)4.5 下游微调与验证
预训练完成后,把编码器接入分类头进行反应性预测:
# src/train_finetune.py import torch from model import ReactionEncoder from reactivity_head import ReactivityHead class ReactivityPredictor(torch.nn.Module): def __init__(self, d_model=256, num_classes=2): super().__init__() self.encoder = ReactionEncoder(d_model=d_model) self.head = ReactivityHead(d_model=d_model, num_classes=num_classes) def forward(self, input_ids): emb = self.encoder(input_ids) return self.head(emb)微调时的数据格式一般是一列反应 SMILES 加一列标签,例如:
rxn_smiles,label "CCC(=O)O.CCO>>CCC(=O)OCC",1 "CCC(=O)O.CCO>>CCC(=O)CCO",0第二行是刻意构造的不合理反应,用于训练模型判断负样本。
5. 常见问题与思考
5.1 对比学习在化学反应表示上一定有效吗
不一定。对比学习的有效性强依赖正样本对的构造质量。如果增强操作破坏了反应中心,模型学到的是“相似 SMILES 片段聚在一起”,而不是“相似反应类型聚在一起”。
实际项目中需要做两件事来验证增强质量:
- 随机抽样部分增强前后的反应,人工检查反应类型是否一致。
- 在预训练后可视化表示分布,看相同反应类型是否聚簇。
5.2 变换感知比“随机增强 + 对比学习”好在哪
随机增强(如随机掩码、随机交换 token)很容易生成一个语义不合理或语法非法的 SMILES。即使模型能强行把表示拉近,学到的也是“语法形式上的相似”,而不是“反应类型上的相似”。
变换感知增强从化学反应的中心思想出发,保证了增强前后反应中心一致,让模型关注的是“变化的语义”。这一点在反应性预测上尤为重要——因为反应性本质上是由反应中心和变换趋势决定的,而不是由外围骨架决定的。
| 对比维度 | 随机增强 + 对比学习 | 变换感知 + 对比学习 |
|---|---|---|
| 正样本对语义一致性 | 低,可能破坏分子语义 | 高,保证变换类型一致 |
| 表示可解释性 | 较弱 | 较强,能区分反应类型 |
| 数据需求 | 高,需要大量数据弥补噪声 | 相对友好 |
| 实现复杂度 | 低 | 较高,需要原子映射与反应中心提取 |
5.3 长尾反应类型怎么处理
即使使用对比学习,长尾问题依然存在。建议结合以下策略:
- 使用加权采样,让样本量少的反应类型出现概率更高。
- 在对比学习负样本中,更多采样困难负样本(Hard Negative),即那些看起来相似但变换类型不同的反应。
- 在微调阶段,使用类别平衡损失或 Focal Loss。
5.4 训练不稳定、表示崩塌怎么办
表示崩塌是对比学习的经典问题。常见对策:
- 使用 stop-gradient 或者预测头(如 SimSiam 的做法)。
- 加大 batch size,增加负样本数量。
- 降低温度系数 tau,但不要过低(过低的 tau 会让训练不稳定)。
- 引入额外的重构或分类损失作为正则化。
6. 最佳实践与工程建议
6.1 数据层面:原子映射是前提
对比学习增强的质量完全取决于原子映射的准确性。如果反应数据中的原子映射缺失或错误,变换感知增强就不可能正确。建议在数据预处理阶段:
- 对每一条反应使用 RDKit 的原子映射工具进行标准化。
- 过滤掉无法映射或映射冲突的反应。
- 对反应中心进行合法性校验(断键和成键数量合理)。
6.2 模型层面:预训练和微调的解耦
预训练阶段尽量使用大规模无标注数据,目标函数是对比损失加辅助损失。微调阶段要根据业务目标做任务适配:
- 如果是二分类反应性预测,分类头后面加一层 BN 和 Dropout 避免过拟合。
- 如果是排序任务(如候选产物排序),可以换成 RankNet 或 ListNet 形式的损失。
- 如果是生成式逆合成,预训练的编码器通常不足以支撑生成任务,需要额外的解码器和更大的模型容量。
6.3 训练层面的工程细节
- 混合精度训练:对比学习通常吃显存,使用 AMP 能显著减少显存占用。
- 学习率调度:对比学习常用 Cosine Annealing,配合 Warmup 效果更好。
- 负样本缓存:当 batch size 受限于显存时,可以考虑使用负样本队列(如 MoCo 的做法),增加负样本覆盖范围。
- 保存最优模型:不能只看训练 loss,要以对比学习正负样本分离度或下游任务指标作为保存标准。
6.4 评测层面:不只看准确率
反应性预测的评测指标建议覆盖:
- Accuracy / AUC-ROC:整体分类性能。
- 长尾类别 Recall:真实业务中长尾反应往往更有价值。
- Top-K Hit Rate:如果模型输出候选反应排序,关注 Top-1、Top-5 命中率。
- 化学合理性:使用 RDKit 检查预测产物是否可解析,避免模型生成非法 SMILES。
6.5 部署与安全边界
如果模型要进入实际研发流程,需要注意:
- 模型的预测结果必须由化学家复核,不能直接作为最终决定。
- 对低置信度样本设置“无法判断”的输出选项,而不是强行给出一个答案。
- 涉及具体化合物活性、毒性预测时,模型输出只能作为筛选线索,不能替代实验验证。
- 数据权限和隐私问题:企业内部反应数据通常非常敏感,模型训练和推理必须在受控环境内进行,不能上传到第三方平台。
7. 总结与学习路线
RxnCLF 的核心贡献在于把“变换感知”这个化学反应领域的领域知识,巧妙地嵌入到对比学习框架中。它没有推翻已有的反应基础模型架构,而是提供了一种更符合化学反应本质的自监督训练策略:保持反应中心语义不变,对分子外围进行增强,让模型学会关注“反应的本质变化”。
从工程角度看,这个思路对实际项目有很直接的借鉴意义:
- 如果已有反应编码器但效果不佳,可以尝试改造预训练目标,引入对比学习。
- 如果数据量少、反应类型分散,变换感知增强是提高数据利用率的有效手段。
- 如果下游任务对反应类型敏感,对比学习预训练后的表示可以显著提升分类边界。
下一步可以深入学习的方向包括:
- RDKit 原子映射与反应中心提取。
- Contrastive Learning 经典方法(SimCLR、MoCo、SimSiam、InfoNCE)。
- 化学生成模型(如基于 Transformer 的 SMILES 生成)。
- 反应模板提取与模板匹配方法,它们与变换感知增强在思想上有很多相通之处。
如果你正在做反应性预测相关项目,建议先在小规模数据上验证变换感知增强的有效性,再逐步扩大预训练数据规模。同时,每一条数据都要做严格的化学合法性校验,不要盲目堆数据。
以上是对 RxnCLF 的思路解析与实践参考。如果你有具体业务场景或复现细节想讨论,欢迎在评论区交流。