化学AI核心模型Transformer:从注意力机制到分子建模实践
2026/8/30 19:18:03 网站建设 项目流程

化学AI里如果只能选一个底层模型,多数人绕不开Transformer。无论是分子性质预测、分子生成、化学反应预测,还是最近常被讨论的分子大模型,底层基本都在用Transformer或它的变体。这个架构已经不只是NLP领域的标配,而是分子建模、材料设计、蛋白质结构预测等方向的公共底座。很多人知道它重要,但学习时容易卡在中间:看论文觉得抽象,跑代码又只会调用现成接口,最后既不能解释注意力机制,也不知道分子数据该怎么喂给模型。

这篇文章适合两类人。一类是刚进入化学AI方向、想把Transformer系统学一遍的研究生或工程师;另一类是已经能跑通开源项目、但想从“会用”进阶到“能复现、能改造、能讲清楚”的开发者。我会按一条完整路径拆:先讲为什么分子建模依赖Transformer,再讲注意力机制的核心逻辑,然后给出手写最小模型的实操流程,最后落到论文复现时的工程化步骤和常见坑。重点不是堆概念,而是让每一步都能验证、能复现、能排查。

1. 为什么说Transformer是化学AI的底层基石

1.1 化学AI实际在解决什么问题

化学AI的核心问题,可以概括成几类:给定一个分子,预测它的溶解度、毒性、药效等性质;给定一些反应物,预测产物和反应条件;给定目标性质,生成一个可能满足条件的分子;或者给定蛋白质序列,预测它的三维结构。

这些任务有一个共同点:都需要把分子表示成计算机能处理的输入。目前主流分子表示有三种。

第一种是序列表示,最典型的是SMILES。比如乙醇用CCO表示,苯用c1ccccc1表示。这种表示本质上就是字符串,天然适合序列模型处理。Transformer最早就是为了处理序列设计的,所以直接迁移成本很低。

第二种是分子图表示,把原子看作节点,把化学键看作边。相比SMILES,图表示保留的连接关系更直接,不会因为字符串写法不同而出现同一个分子多种SMILES的问题。

第三种是三维坐标表示,把原子在空间中的位置直接作为输入。这类数据适合处理构象、结合能、蛋白质配体相互作用等对空间结构敏感的任务。

Transformer在三种表示上都能用,这本身就决定了它的通用性。更重要的是,分子性质往往不止由局部结构决定,而是受长距离影响。比如某个官能团隔着好几个原子影响另一个位置的电子密度,这种全局依赖关系,正好是Transformer最擅长捕捉的。

1.2 Transformer和CNN、RNN在分子任务上的差异

早期做分子预测,RNN和CNN用得比较多。RNN按顺序逐个处理token,当前时刻的输出依赖上一个时刻,优势是能处理任意长度序列,但长距离依赖存在信息衰减。分子序列中官能团之间的相互影响可能跨越很长距离,RNN容易丢失这部分信息。LSTM和GRU缓解了这个问题,但不是彻底解决。

CNN通过卷积核在局部窗口内提取特征。它能捕捉局部模式,比如某个官能团片段,但卷积核大小固定,要覆盖长距离关联,就得堆很多层或扩大卷积核。分子场景中,不同化学键长度差异很大,一个固定窗口很难同时兼顾近程和远程模式。

Transformer走的是另一条路线:每个token和序列中所有其他token直接做注意力计算。无论两个原子在序列上的距离有多远,注意力机制都能让它们直接交互。这种全局建模能力,让它非常适合处理分子结构和性质之间的复杂映射。

代价也很明显:自注意力的计算复杂度是序列长度的平方。序列越长,计算量和内存占用增长越快。所以实操中必须控制输入长度和批量大小,不能无脑加长序列。

2. 先把注意力机制吃透,再进Transformer架构

2.1 自注意力在分子序列上是怎么工作的

Transformer最核心的部分是自注意力机制,英文叫Self-Attention。理解它,基本就理解了Transformer一半。

自注意力的输入是一组向量,每个token都有一个对应的向量表示。在分子任务里,SMILES字符串中的每个字符,经过嵌入层之后都变成一个向量。之后经过三个线性变换,分别得到Query、Key、Value,通常简写为Q、K、V。

可以类比成查资料的过程。Query是你要查的问题,Key是资料库里的索引,Value是资料正文。注意力分数是Q和K的相似度,相似度越高,说明这个问题越应该参考那段资料。然后把相似度归一化,再和Value加权求和,就得到当前token经过全局交互后的新表示。

具体计算时,Q和K做点积,除以根号下的向量维度,是为了避免分数过大导致softmax过饱和,然后用softmax归一化成权重,最后和V加权求和。

在一句话里,每个token都会作为Query去关注其他token,同时也作为Key被其他token关注。这就是“自”的意思:同一个序列内部自己做交互。

放到分子例子里理解。比如SMILES序列中有一个苯环,某个位置上的取代基对远处另一个位置有电子效应影响。自注意力可以直接建立这两个位置之间的高权重连接,不需要经过中间层层传递。

2.2 多头注意力和位置编码为什么重要

只算一次自注意力还不够。多个token之间的依赖关系可能有很多种,比如有的是空间距离关系,有的是电子效应关系,有的是立体化学关系。如果只用一个注意力函数,很难同时捕捉多种关系。

所以Transformer用多头注意力,把Q、K、V切分成多组,每组独立计算注意力,最后拼接到一起。每个头可以关注不同角度的关系。头数一般取8或12,不是一个必须调很大的参数,取默认值就能在很多任务上有不错表现。

位置编码也是容易忽略但极其重要的部分。自注意力本身没有顺序概念,它只看两个token之间的相关性,不区分谁在前谁在后。但SMILES字符串顺序是稳定的,同一个分子可能有多个合法SMILES,但在同一个数据集中,如果顺序改变,token之间的位置信息就变了。为了让模型知道当前token在序列中的位置,Transformer需要在输入里加入位置编码。

位置编码有两种常见做法:一种是固定的三角函数位置编码,在原始Transformer论文中使用;另一种是可学习的位置编码,让模型自己从数据中学。大多数分子Transformer项目使用可学习位置编码,因为分子序列通常较短,固定公式的优势不明显,可学习版本更容易适配任务。

3. 自己动手写一个最小Transformer:从环境到训练

3.1 环境准备与数据形态

想真正理解Transformer,只看架构图是不够的。我建议用PyTorch手写一个最小可运行的Transformer分类模型,用少量SMILES数据做训练。这个过程不是为了实现一个能部署的完整系统,而是为了把每一层的作用落到代码上。

环境不需要太复杂。Python 3.10以上、PyTorch 2.x、一个基础的数据处理工具包就够了。如果机器有NVIDIA显卡,可以顺手用GPU训练;没有显卡,用CPU跑一个小数据集也能验证流程。

数据可以先不从大型数据集下载。自己准备几十条SMILES字符串,加上两三个标签,比如某个分子是否具有某种性质,先用这个极小型数据集跑通,再做扩展。

注意一点:这里的代码是教学用简化版本,重点在理解结构。生产项目中一般直接用别人优化好的实现,不建议从零造轮子。

3.2 最小Transformer训练流程

一个最小Transformer分类器可以拆成四部分:嵌入层、位置编码、Transformer编码器、分类头。PyTorch提供了nn.TransformerEncoderLayernn.TransformerEncoder,可以直接组合,不用手写完整的注意力实现。但建议至少先自己实现一次单头注意力,再切换到官方封装。

先定义数据加载逻辑。对SMILES字符串做字符级分词,得到token到索引的映射。然后对每条SMILES做定长填充,长度不够补填充符,长度超了截断。这里长度可以先设成64或128,具体要看你数据集里的长度分布。

训练循环本身很常规:前向传播、计算损失、反向传播、更新参数。分类任务用交叉熵损失,优化器用AdamW,学习率可以设1e-4左右。关键点是先跑过拟合:用几十条数据训练,如果Loss能明显下降,说明模型和数据链路是通的,这时再去扩大数据规模。

import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader class SmilesDataset(Dataset): def __init__(self, smiles_list, labels, token2idx, max_len=128): self.smiles_list = smiles_list self.labels = labels self.token2idx = token2idx self.max_len = max_len self.pad_idx = 0 def __len__(self): return len(self.smiles_list) def __getitem__(self, idx): smi = self.smiles_list[idx] ids = [self.token2idx.get(ch, 1) for ch in smi] ids = ids[:self.max_len] ids = ids + [self.pad_idx] * (self.max_len - len(ids)) return torch.tensor(ids, dtype=torch.long), torch.tensor(self.labels[idx], dtype=torch.float)

模型部分可以这样搭:输入先经过嵌入层,加上位置编码,接着过几层TransformerEncoder,取序列第一个位置或全局池化的结果,再过线性层得到分类输出。

class SimpleTransformerClassifier(nn.Module): def __init__(self, vocab_size, d_model=128, nhead=4, num_layers=2, num_classes=1, max_len=128): super().__init__() self.embedding = nn.Embedding(vocab_size, d_model) self.pos_embedding = 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.classifier = nn.Linear(d_model, num_classes) def forward(self, x): positions = torch.arange(x.size(1), device=x.device).unsqueeze(0) x = self.embedding(x) + self.pos_embedding(positions) x = self.encoder(x) x = x.mean(dim=1) return self.classifier(x).squeeze(1)

一个非常值得做的实验是:只用一个注意力头,在代码里手动写出Q、K、V和softmax计算,观察注意力权重矩阵是什么样的。这样可以直观看到苯环上的原子和哪些原子建立了高权重连接。

3.3 训练完成后怎么验证

训练不是跑完就算了,要能判断结果是否正常。第一看Loss曲线是否下降。如果Loss完全没有下降趋势,先不要调模型结构,优先检查数据和标签是否对应。第二看是否过拟合。用几十条数据训练,正常情况应该能很快过拟合到接近100%准确率,如果连这一点都做不到,大概率是代码链路有问题。

第三看注意力可视化。取一条测试SMILES,输出注意力矩阵,观察模型关注的位置是否符合化学直觉。比如某个官能团的注意力权重是否集中于另一端的活性位点。不用强求所有结果都有明确化学意义,但不能完全散乱无规律。

这里要特别注意:小规模验证通过,不等于大规模训练就一定能成功。真正扩数据量之前,还要考虑序列长度分布、类别平衡、随机种子、评估指标等一系列问题。

4. 分子建模的Transformer实践路径:从SMILES到图结构

4.1 SMILES序列建模:最直接的切入点

对刚入门的人来说,SMILES是最友好的入口。原因是生态最成熟:数据容易获取,开源项目多,很多公开基线可以直接对照。比如分子性质分类任务,输入是SMILES字符串,输出是一个或多个标签,模型结构基本就是一个TransformerEncoder加分类头。

SMILES建模有几个固定步骤要处理。

第一步是清洗数据。SMILES字符串里的特殊字符比较多,比如Cl是两个字符但表示一个原子,不能拆成Cl;括号表示分支;数字表示环的闭合。字符级分词有时会把多字符原子拆坏,所以很多项目会先做正则化,把ClBr这类多字符原子处理成单个token。

第二步是统一长度。Transformer输入通常要求固定长度,但SMILES长度差异很大。短的可能只有十几个字符,长的能到几百个。处理方式一般有两种:截断到指定最大长度,或者通过数据统计分析选择覆盖95%样本的长度。

第三步是注意标签可靠性。分子数据集的标签来源经常不一致,有的来自实验测量,有的来自计算预测,有的来自文献收集。标签噪声对模型效果的影响,往往比模型结构选择更大。

判断一个SMILES Transformer项目是否正常,不只看Loss和准确率,还要看数据划分是否合理。如果随机划分,同一种分子的多个相似样本可能同时出现在训练集和测试集,导致验证结果虚高。更稳妥的做法是按分子骨架或相似性聚类划分。

4.2 分子图与3D坐标:什么时候才需要升级结构

SMILES序列不是万能的。它本质上是一种线性化表示,会把分子原本的拓扑结构压扁。同一个分子可能对应多个不同的SMILES字符串,虽然通过标准化函数可以统一,但序列表示仍然不是最自然的化学语言。

当任务对连接关系敏感时,可以考虑图Transformer。输入是图结构,每个原子有初始特征,通过消息传递或者图注意力机制聚合邻居信息。图Transformer可以更好地保留原子的连接关系,例如判断某个原子在分子中的拓扑环境。

当任务对三维结构敏感时,比如预测蛋白质配体结合能、分子构象、手性性质,就需要引入3D坐标信息。设计上常见做法是把原子的3D坐标经过距离编码或高斯核展开后加入注意力计算,让模型知道两个原子在空间上离得远不远。

需要注意的是:不是所有任务都值得用图或3D模型。如果只是做分子性质分类,SMILES加TransformerEncoder通常已经有了不错效果。图模型和3D模型的数据预处理更复杂,计算量更大,如果数据量只有几千条,反而容易过拟合。

5. 论文复现的正确打开方式:先拆任务,再复现

5.1 复现前要确认的四件事

论文复现是很多人进阶时绕不开的环节。但复现不是把开源代码下载下来跑通就结束了。严格来说,跑通官方代码只是第一步,真正的复现是理解设计选择,并且能迁移到自己的任务上。

开工之前,先确认四件事。

第一,输入表征是什么。论文用的是SMILES、分子指纹、分子图还是3D坐标。不同表征对应的预处理流程完全不同,这里最容易踩坑。

第二,模型结构是哪一个分支。有些工作是BERT式的Encoder结构,适合性质预测和表征学习;有些是GPT式的Decoder结构,适合分子生成;有些是Encoder-Decoder结构,适合序列到序列任务,比如反应预测。如果把结构判错,后面的分析全都会偏。

第三,数据划分方式是什么。很多论文会在附件或代码里说明数据按什么规则划分。是随机划分、按骨架划分、按时间划分,还是按相似性划分。这个细节直接决定结果是否可比。

第四,评估指标是什么。分类任务是准确率、AUC还是F1;生成任务是合法率、唯一率、成功率还是目标性质的优化程度。指标不同,模型选择方向也不同。

5.2 常见复现步骤和资源判断

复现时最稳妥的顺序是先创建独立Python环境,再安装代码仓库中指定的依赖版本,不要直接用全局环境,避免版本冲突。

然后先跑小规模实验。如果官方训练需要几百个epoch,不要一开始就完整跑,先取一个小子集,比如1%的数据,跑几个epoch确认数据加载和训练循环正常。这一步能发现很多问题,比如某列缺失、某类SMILES解析失败、标签索引错位。

再确认随机种子。很多论文会给出种子,有的给一个,有的给多个取平均。如果代码没有明确种子设置,最好自己固定,因为不同种子下结果波动很大,尤其数据量小时。

资源不足是常见情况。显存不够时,最直接的办法是减小batch_size,同时考虑减小最大序列长度。如果batch_size已经很小,还可以用梯度累积,模拟更大的batch。混合精度能减少显存占用并加快训练,但要注意某些操作在低精度下可能不稳定,需要观察Loss是否异常。尽量不要一开始就减少模型层数或隐藏维度,因为这会改变模型容量,和论文设定失去可比性。

5.3 不能照搬的边界:数据集划分、指标、种子

复现成功不等于精度完全一致。绝大多数情况下,由于硬件差异、软件版本、数据预处理顺序和随机种子不同,结果不会和论文完全一样。关键要看趋势是否一致:训练集和验证集上的曲线形态、不同方法之间的相对差距、消融实验里的主次顺序。

我有一个经验:复现时先看论文有没有给出具体的逻辑,比如数据清洗规则、SMILES标准化策略、是否去重、是否过滤长度过大样本。这些细节经常不会出现在正文,而在代码仓库或附录。看到代码里对数据做了奇怪操作,不要删掉,先搞清楚为什么。

复现效果差时,不要第一时间怀疑理论,优先排查三个地方:数据文件是否和原版一致、tokenizer是否一致、训练超参数是否被改过。很多“复现失败”都是因为下载的是第三方的处理版本,和原始项目不一致。

6. 踩坑记录:这些坑我几乎每次都遇到

6.1 数据清洗和序列长度

SMILES数据看起来简单,实际上到处是坑。先看字符,Clbr这类表示必须整体处理;再看括号和数字,环闭合数字经常成对出现,解析失败会让模型学到错误模式;还要看是否有空字符串、非法字符、非标准写法。

字符串标准化工具可以将SMILES转为规范形式,但需要注意:标准化的结果不一定保留原论文使用的表示方式。有些论文使用特定的token化规则,有些论文加入原子类型增强特征,这些都要以原仓库为准。

序列长度不要凭感觉决定。先把数据集的SMILES长度分布统计出来,看中位数、95分位数、最大长度。设最大值时,既要覆盖绝大多数样本,又不能太大拖慢训练。常见做法是取95%到99%的覆盖度,超出部分截断或丢弃。

6.2 显存与批量大小怎么平衡

Transformer训练时最常遇到的就是显存不足。很多人第一反应是调小batch_size,这没有错,但要注意:batch_size过小会导致训练不稳定,BatchNorm或者梯度估计受影响。

更好的做法是先限制序列长度,再调batch_size。比如从256降到128,显存可能下降接近一半。如果长度不能降,再考虑梯度累积。梯度累积的意思是多个小batch分别前向和反向,但暂不更新参数,攒到一定步数后统一更新。效果上接近大batch,但训练时间会变长。

混合精度训练也是一种常规手段。PyTorch里可以用torch.autocastGradScaler实现。大多数情况下Loss能正常下降,但出现NaN时,要检查是否是低精度数值溢出。如果发现混合精度下训练不稳定,可以退回全精度。

注意:低显存环境不是不能跑Transformer,而是要把模型尺寸、序列长度、batch_size三者协调好。我一般先固定模型为两个Encoder层,再用小batch和小长度跑通,最后逐步加量。

6.3 训练不收敛时的排查顺序

训练不收敛时,不要急着改模型结构,按顺序排查。

第一步,看Loss是否从初始值开始下降。如果一直不降,检查数据标签是不是错位了,或者标签类别分布极度不均衡。第二步,看梯度和数值。Loss变成NaN时,先查学习率是否过高、输入是否含NaN、位置编码是否是NaN。第三步,用一个极小数据集做单batch过拟合测试。如果模型在几个样本上Loss能降到很低,说明模型表达能力和代码链路没问题,问题可能出在数据或训练策略。如果连单batch都降不下去,基本可以确定是模型代码或输入格式问题。

还要注意类别不均衡。分子数据集里,正负样本比例经常差很多。遇到这种情况,可以调整损失函数权重,或者尝试其他评价指标,而不是只盯着准确率。

7. 学完Transformer之后,下一步该补什么

7.1 从分子Transformer到大模型的距离

跑通一个分子分类Transformer之后,很多人会直接想:是不是可以去做分子大模型了?中间的差距其实很大。

小规模模型和大规模预训练模型之间隔着几层门槛。第一是数据规模。大模型需要百万级甚至亿级分子数据,不是普通单机环境能处理的。第二是tokenizer设计。小模型可以直接用字符级分词,大模型需要更高效的分词方法,比如基于BPE的分词,平衡词表大小和序列长度。第三是训练策略。大模型需要分布式训练、学习率调度、数据流式读取、检查点保存恢复机制,这些都不是一个简单训练循环能搞定的。

但学习路径是通的。先掌握单卡小模型训练,再理解数据组织和模型结构,然后学习大规模训练中的常见工程问题,最后才是部署。本地部署大模型需要的资源和优化手段,和跑一个小模型完全不是一个量级。

从Transformer家族来看,Vision Transformer和Swin Transformer这类视觉侧变体也值得关注。虽然研究对象不是分子,但它们的结构设计思路,比如Patch化、窗口注意力、层级结构,经常被迁移到化学和材料领域的多模态任务中。学有余力时,把这些变体对照着看,很容易加深对基础架构的理解。

7.2 怎么判断自己真的掌握了

能跑通项目代码不算掌握。判断标准可以更严格一些:能不能不看参考资料,画出Transformer流程并解释每个模块作用;能不能把SMILES分类模型改成多标签任务或生成任务;遇到一篇新论文时,能不能在一天内定位它相对标准Transformer做了什么改动;模型训练失败时,能不能根据现象给出至少两个可能的排查方向。

这些能力不是看论文看出来的,是动手改代码、反复实验、记录日志之后累积出来的。我建议学完基础部分后,给自己布置一个完整的小项目:从一个公开的分子数据集中取数据,实现一个从数据清洗、搭建模型、训练验证到可视化注意力权重的流程。这个过程能覆盖的学习点,比看十篇教程都有效。

真正落地化学AI项目时,最该盯住的不是Transformer这个名词有多新,而是输入数据是否干净、标签是否可靠、指标选择是否合理、训练日志是否完整。模型结构可以换,Transformer也可以换成其他架构,但这些工程问题始终存在。

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

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

立即咨询