Transformer在化学AI中的原理与实战:从分子建模到性质预测

发布时间:2026/8/30 19:17:54
Transformer在化学AI中的原理与实战:从分子建模到性质预测 化学AI领域这两年热度涨得飞快尤其是分子建模、性质预测、逆合成分析这些方向几乎都能看到 Transformer 的身影。很多做化学信息学或材料模拟的同学刚开始接触时都会卡在同一道坎上论文里的公式能看懂大半但一落到代码就不知道从哪下手想复现一篇分子性质预测的模型又搞不清 Transformer 的 Embedding 该怎么设计、分子序列该用什么 Tokenizer。本文整理了一套从原理到论文复现的完整学习路径把 Transformer 的架构细节、分子建模中的常见做法、代码实现步骤以及高频踩坑点一次讲透。适合刚入门化学AI的学生也适合需要在项目中引入分子大模型的工程师。很多初学者会把 Transformer 理解成“又一个深度学习网络”但实际上它和传统的 CNN、RNN 有本质差异。CNN 靠卷积核捕捉局部特征RNN 按时间步递归处理序列而 Transformer 的核心是自注意力机制它能在一次前向计算中让序列中任意两个位置的 Token 直接交互从而更好地捕捉长距离依赖。对分子建模来说这个特性特别有价值分子中的远程官能团相互作用、共轭效应、空间位阻等信息靠一维卷积或循环网络很难充分建模而 Transformer 的注意力头可以同时关注不同原子之间的关系。但 Transformer 不是银弹。它训练成本高、数据需求大、对 Tokenization 和位置编码敏感只有理解清楚这些底层机制才能在实际项目中少走弯路。接下来我们按“原理 → 代码 → 分子建模实战 → 论文复现 → 优化与踩坑”的顺序把整条链路展开。一、Transformer 为什么能成为化学AI大模型的基石1.1 从分子建模的视角看 Transformer 的价值传统分子建模通常依赖两类方法基于物理的方法比如分子动力学模拟和密度泛函理论计算精度较高但计算成本非常大难以直接用于高通量筛选。基于描述符的机器学习方法把分子编码成手工设计的特征比如摩根指纹、分子描述符再输入到随机森林或 SVM 中。这类方法速度快但特征表达能力有限很难捕捉原子的三维空间关系和长程相互作用。Transformer 提供的是第三条路线直接从分子的序列表示或图结构中学习表征。它不需要人工设计大量化学特征而是通过自注意力机制自动发现原子之间的依赖关系。对于分子性质预测、化学反应产率预测、分子生成等任务Transformer 都能作为统一的骨干网络使用。从模型演进的角度看Transformer 也是当前主流分子大模型的底层架构。像基于 SMILES 序列的分子语言模型、基于分子图的 Graph Transformer、以及融合 3D 坐标的分子表征模型本质上都是“分子表示 Transformer 编码器/解码器”的组合。掌握了 Transformer 的原理和代码再去看这些模型就会轻松很多。1.2 Transformer 架构的宏观拆解标准 Transformer 由两部分组成Encoder负责把输入序列编码成一组向量表示适合分类、回归、表征学习等任务。Decoder负责根据 Encoder 的输出和已生成的内容逐步生成目标序列适合分子生成、反应预测中的产物生成等任务。在化学AI 中两种结构都有广泛应用。比如分子性质预测通常只用 EncoderSMILES 到 SMILES 的逆合成预测则使用 Encoder-Decoder 结构而基于 SMILES 的分子生成模型可以只使用 Decoder类似 GPT 的风格。Transformer 的单层内部包含以下关键子模块多头自注意力Multi-Head Self-Attention前馈神经网络Feed-Forward NetworkFFN残差连接Residual Connection层归一化Layer Normalization位置编码Positional Encoding下面我们从最重要的自注意力机制开始讲起。二、核心机制拆解自注意力、多头注意力与位置编码2.1 自注意力的计算过程自注意力的目标是让序列中的每个 Token 都能根据其他 Token 的信息更新自己的表示。以分子序列为例如果输入是 SMILES 字符串“CC(O)O”那么每个字符都可以看作一个 Token自注意力会让“C”和“O”之间建立联系从而感知到羧基的存在。具体的计算过程可以拆成几小步。第一步把每个 Token 的输入向量分别乘以三个权重矩阵得到 Query、Key、Value。import torch import torch.nn as nn import math def qkv_projection(x, w_q, w_k, w_v): # x: [batch_size, seq_len, d_model] Q x w_q K x w_k V x w_v return Q, K, V这里的 Query 表示“我想查找什么信息”Key 表示“我能提供什么信息”Value 表示“我实际携带的信息”。第二步计算注意力分数。用 Query 和所有 Key 做点积再除以根号下向量维度防止数值过大导致 Softmax 梯度消失。def attention_scores(Q, K): d_k Q.size(-1) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k) return scores第三步对注意力分数做 Softmax得到权重分布再用权重对 Value 加权求和。def scaled_dot_product_attention(Q, K, V, maskNone): d_k Q.size(-1) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k) if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) weights torch.softmax(scores, dim-1) output torch.matmul(weights, V) return output这个过程中的关键点在于每个 Token 的输出不再只依赖自己而是综合了全序列的信息。这种全局交互能力正是 Transformer 在分子建模中的优势来源。2.2 多头注意力到底在做什么单头注意力虽然能建模全局依赖但表达能力有限。多头注意力的思路是用多组 QKV 权重并行计算注意力每个头可以关注不同维度的信息。以分子性质预测为例一个头可能关注原子的局部连接关系比如碳原子是否直接与氧相连。另一个头可能关注长程的电荷相互作用比如分子两端的极性基团。还有一个头可能关注官能团的整体模式比如苯环、羧基、氨基的存在。多头注意力的代码如下。class MultiHeadAttention(nn.Module): def __init__(self, d_model, n_head, dropout0.1): super().__init__() assert d_model % n_head 0 self.d_model d_model self.n_head n_head self.d_k d_model // n_head self.w_q nn.Linear(d_model, d_model) self.w_k nn.Linear(d_model, d_model) self.w_v nn.Linear(d_model, d_model) self.out_proj nn.Linear(d_model, d_model) self.dropout nn.Dropout(dropout) def forward(self, x, maskNone): batch_size x.size(0) Q self.w_q(x) K self.w_k(x) V self.w_v(x) Q Q.view(batch_size, -1, self.n_head, self.d_k).transpose(1, 2) K K.view(batch_size, -1, self.n_head, self.d_k).transpose(1, 2) V V.view(batch_size, -1, self.n_head, self.d_k).transpose(1, 2) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) attn torch.softmax(scores, dim-1) attn self.dropout(attn) context torch.matmul(attn, V) context context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) output self.out_proj(context) return output代码里比较关键的步骤是维度重排原始输入形状是[batch_size, seq_len, d_model]。通过view和transpose拆成[batch_size, n_head, seq_len, d_k]。每个头独立计算注意力最后拼接并经过输出投影。这种并行计算在实际项目里非常高效而且头的数量通常设置为 8 或 16。对分子模型来说头数太多会导致参数膨胀太少又难以捕捉多样化的化学特征需要根据数据集规模做取舍。2.3 位置编码给分子序列加上“顺序感”Transformer 没有 CNN 的滑动窗口也没有 RNN 的时间步概念因此如果不加位置编码模型会把输入的 SMILES 序列当作词袋来处理原子顺序信息完全丢失。位置编码有两种常见方式固定位置编码使用正弦余弦函数生成。可学习位置编码把位置索引映射为可训练的向量。对于分子建模尤其是 SMILES 序列任务可学习位置编码通常效果更好因为 SMILES 的语法结构和自然语言不同比如“CCO”表示乙醇“OCC”虽然原子组成相同但表示不同的分子连接方式不过实际 SMILES 规范中这两种写法可能对应不同合法字符串顺序是绝对敏感的因此位置编码必须能区分不同位置的原子。固定位置编码的代码如下。class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len512): super().__init__() pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) div_term torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) pe pe.unsqueeze(0) self.register_buffer(pe, pe) def forward(self, x): return x self.pe[:, :x.size(1)]使用方式是在输入向量进入 Encoder 前把 Token Embedding 和位置编码相加。在实际化学AI项目中可以额外考虑加入一些原子级的位置先验比如原子在分子图中的邻接序号。不过这个扩展属于高级优化后文会再展开。三、从论文到代码手写一个简化版 Transformer Encoder读论文时总觉得 Transformer 结构清晰、逻辑简单但真正动手写代码就会发现很多细节会出错比如维度匹配、残差连接顺序、Mask 的设置等。这一节提供一个可直接运行的最小 Encoder适合用来理解整体流程也适合作为分子建模的骨干网络。3.1 完整 Encoder 代码这里我们会把上一节的多头注意力和位置编码组合起来实现一个完整的 Transformer Encoder 层。import torch import torch.nn as nn import math class FeedForward(nn.Module): def __init__(self, d_model, d_ff, dropout0.1): super().__init__() self.linear1 nn.Linear(d_model, d_ff) self.linear2 nn.Linear(d_ff, d_model) self.dropout nn.Dropout(dropout) def forward(self, x): return self.linear2(self.dropout(torch.relu(self.linear1(x)))) class EncoderLayer(nn.Module): def __init__(self, d_model, n_head, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, n_head, dropout) self.ffn FeedForward(d_model, d_ff, dropout) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) def forward(self, x, maskNone): attn_out self.self_attn(x, mask) x self.norm1(x self.dropout(attn_out)) ffn_out self.ffn(x) x self.norm2(x self.dropout(ffn_out)) return x class TransformerEncoder(nn.Module): def __init__(self, vocab_size, d_model, n_head, d_ff, n_layer, max_len, dropout0.1): super().__init__() self.token_embedding nn.Embedding(vocab_size, d_model) self.position_encoding PositionalEncoding(d_model, max_len) self.layers nn.ModuleList([ EncoderLayer(d_model, n_head, d_ff, dropout) for _ in range(n_layer) ]) self.dropout nn.Dropout(dropout) def forward(self, tokens, maskNone): x self.token_embedding(tokens) x self.position_encoding(x) x self.dropout(x) for layer in self.layers: x layer(x, mask) return x这段代码中需要注意两点残差连接和 LayerNorm 的顺序。当前代码采用的是 Post-Norm也就是先相加再归一化。另一种 Pre-Norm 是在进入子层前先归一化。对于深层模型Pre-Norm 通常更稳定训练时不容易梯度爆炸。输出向量可以直接取[CLS]Token 对应的向量也可以对所有 Token 做池化具体取决于下游任务。对于分子性质预测常见做法是对所有原子向量做平均池化也可以学习一个图的全局表示。3.2 在分子性质预测任务中使用 Encoder假设我们要预测分子的水溶性LogS或者毒性输入是 SMILES 字符串的 Token 序列。核心代码可以按下面这种结构组织。class MoleculePropertyPredictor(nn.Module): def __init__(self, vocab_size, d_model128, n_head4, d_ff512, n_layer4, max_len128, num_classes1): super().__init__() self.encoder TransformerEncoder(vocab_size, d_model, n_head, d_ff, n_layer, max_len) self.pooler nn.AdaptiveAvgPool1d(1) self.regressor nn.Sequential( nn.Linear(d_model, 128), nn.ReLU(), nn.Dropout(0.1), nn.Linear(128, num_classes) ) def forward(self, tokens, maskNone): encoded self.encoder(tokens, mask) encoded encoded.transpose(1, 2) pooled self.pooler(encoded).squeeze(-1) output self.regressor(pooled) return output这里用AdaptiveAvgPool1d把序列维度压缩成 1得到一个固定长度的分子表示。如果你希望保留更完整的化学信息也可以用 attention pooling 或者直接取最后一个 Token 的表示。四、化学AI核心任务如何把分子变成 Transformer 能读懂的输入Transformer 本身不关心输入是什么类型的数据它只处理向量序列。因此化学AI 的第一个关键问题就是如何把分子表示成 Token 序列或图结构并映射成向量。4.1 基于 SMILES 序列的分子表示SMILES 是最常见的分子线性表示法用 ASCII 字符串表示分子的连接关系。比如乙醇可以写成CCO乙酸可以写成CC(O)O。SMILES 字符串可以直接类比为自然语言中的“句子”每个字符或每个基团片段作为 Token。Tokenization 方式有两种字符级 Tokenizer把每个字符作为一个 Token。优点是词表小、简单但模型需要自己学习原子符号和括号的组合规则训练效率偏低。子词级 Tokenizer类似 NLP 中的 BPE把常见片段如CO、[NH3]、c1ccccc1等合并成一个 Token。这样能减少序列长度也能让模型更快学到化学片段知识。下面是一个用字符级 Tokenizer 处理 SMILES 的简单示例。from rdkit import Chem from rdkit.Chem import AllChem smiles CC(O)O # 用 RDKit 校验 SMILES 并转为 canonical SMILES mol Chem.MolFromSmiles(smiles) canonical_smiles Chem.MolToSmiles(mol) print(Canonical SMILES:, canonical_smiles) # 字符级 tokenization tokens list(canonical_smiles) print(Tokens:, tokens)输出结果大致如下Canonical SMILES: CC(O)O Tokens: [C, C, (, , O, ), O]需要注意SMILES 中的括号、等号、数字都有特殊含义比如数字表示环的连接点。字符级 Tokenizer 虽然实现简单但在数据预处理时必须保留这些符号否则分子信息会丢失。4.2 基于分子图的表示SMILES 是线性序列但分子本质上是图结构。原子是节点化学键是边。Graph Transformer 直接把分子图输入 Transformer利用注意力机制在原子节点之间传递信息。Graph Transformer 和标准 Transformer 的主要区别在于输入不再是 Token 序列而是原子节点特征矩阵和邻接矩阵。注意力计算时需要通过邻接矩阵构造 Attention Mask只允许有连接关系的原子对之间直接交互或者在注意力分数中加入化学键信息。位置编码可以替换为图结构编码比如拉普拉斯位置编码或原子距离编码。如果引入 RDKit 计算原子特征可以这样组织特征工程from rdkit import Chem mol Chem.MolFromSmiles(CCO) for atom in mol.GetAtoms(): print(atom.GetIdx(), atom.GetSymbol(), atom.GetDegree(), atom.GetFormalCharge())输出如下0 C 1 0 1 C 2 0 2 O 1 0这些原子级特征可以进一步加工成向量作为 Graph Transformer 的初始节点表示。对 3D 分子建模还可以把空间距离矩阵加入注意力偏置项。4.3 分子表征与数据增强在化学AI 数据集规模普遍较小的情况下数据增强非常关键。常见手段包括SMILES 枚举同一个分子尝试生成多种合法 SMILES 表达增大数据多样性。原子扰动对 3D 坐标加入少量高斯噪声增强模型鲁棒性。子结构掩码随机遮蔽部分原子或化学键让模型学习补全类似 NLP 的 Masked Language Model。其中 SMILES 枚举在分子生成和性质预测中都很常用。RDKit 里可以通过随机遍历原子顺序生成不同形式的 SMILES。from rdkit.Chem import rdMolDescriptors mol Chem.MolFromSmiles(CCO) rand_smiles Chem.MolToRandomSmiles(mol) print(rand_smiles)这种数据增强能在不改变分子结构的情况下增加训练样本的多样性有助于缓解过拟合。五、完整实战用 Transformer 预测分子 LogP下面我们跑一个可以落地的完整案例用 Transformer Encoder 预测分子的油水分配系数 LogP。LogP 是一个经典分子性质RDKit 可以直接计算我们可以先用它生成训练标签然后训练模型从 SMILES 中预测 LogP。5.1 生成数据集这里我们用 RDKit 从已知分子集合中生成训练数据。为了演示方便直接从一组简单分子开始。import pandas as pd from rdkit import Chem from rdkit.Chem import Crippen smiles_list [ CCO, CC(O)O, c1ccccc1, CCN, C(C)(C)O, CC(C)C, COC, C1CCCCC1, C(CCl)N, C(F)(F)F ] records [] for s in smiles_list: mol Chem.MolFromSmiles(s) if mol is None: continue logp Crippen.MolLogP(mol) records.append({smiles: Chem.MolToSmiles(mol), logp: logp}) df pd.DataFrame(records) print(df)输出结果示例smiles logp 0 CCO 0.34650 1 CC(O)O -0.17900 2 c1ccccc1 1.88660 3 CCN 0.28400 4 CC(C)(C)O 1.36700 ...5.2 构建 Tokenizer 和 DataLoader为了更接近真实项目这里把 Tokenizer、Dataset 和 DataLoader 统一封装。词表通过训练集动态构建。import torch from torch.utils.data import Dataset, DataLoader class SMILESTokenizer: def __init__(self, smiles_list): self.vocab set() for s in smiles_list: self.vocab.update(list(s)) self.vocab sorted(self.vocab) self.stoi {ch: i 2 for i, ch in enumerate(self.vocab)} self.stoi[pad] 0 self.stoi[unk] 1 self.itos {i: ch for ch, i in self.stoi.items()} def encode(self, s, max_len): ids [self.stoi.get(ch, self.stoi[unk]) for ch in list(s)] if len(ids) max_len: ids ids [self.stoi[pad]] * (max_len - len(ids)) else: ids ids[:max_len] return torch.tensor(ids, dtypetorch.long) class MoleculeDataset(Dataset): def __init__(self, df, tokenizer, max_len64): self.df df self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] tokens self.tokenizer.encode(row[smiles], self.max_len) logp torch.tensor(row[logp], dtypetorch.float) return tokens, logp tokenizer SMILESTokenizer(df[smiles].tolist()) dataset MoleculeDataset(df, tokenizer) loader DataLoader(dataset, batch_size4, shuffleTrue)这里使用pad和unk是标准做法。在做性质预测时Padding 位置的注意力需要被 Mask 掉否则模型会学习到“空位”的虚假信息。5.3 训练模型训练流程和普通 PyTorch 回归任务没有本质区别。使用均方误差损失AdamW 优化器。device torch.device(cuda if torch.cuda.is_available() else cpu) model MoleculePropertyPredictor( vocab_sizelen(tokenizer.vocab) 2, d_model64, n_head4, d_ff256, n_layer2, max_len64, num_classes1 ).to(device) optimizer torch.optim.AdamW(model.parameters(), lr1e-3) criterion nn.MSELoss() model.train() for epoch in range(50): total_loss 0.0 for tokens, logp in loader: tokens, logp tokens.to(device), logp.to(device).unsqueeze(-1) mask (tokens ! 0).unsqueeze(1).unsqueeze(2).to(device) pred model(tokens, mask) loss criterion(pred, logp) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 10 0: print(fEpoch {epoch 1}, Loss: {total_loss / len(loader):.4f})这个示例中的数据集非常小主要用来验证模型能跑通不追求 LogP 预测精度。实际项目中需要用数千到数百万级别的分子数据并增加验证集、 Early Stopping 和更精细的评估指标。5.4 模型评估回归任务的常见评估指标包括 RMSE、MAE、R²。下面给出一个 MAE 的计算示例。from sklearn.metrics import mean_absolute_error, r2_score model.eval() preds, trues [], [] with torch.no_grad(): for tokens, logp in loader: tokens tokens.to(device) mask (tokens ! 0).unsqueeze(1).unsqueeze(2).to(device) pred model(tokens, mask).cpu().numpy().flatten() preds.extend(pred) trues.extend(logp.numpy()) print(MAE:, mean_absolute_error(trues, preds)) print(R2:, r2_score(trues, preds))六、进阶方向从 Transformer 到化学分子大模型6.1 分子预训练模型和 NLP 中 BERT、GPT 的成功类似化学AI 也广泛采用“预训练 微调”的范式。常见的分子预训练任务包括Masked Language Modeling随机遮蔽 SMILES 中的部分 Token让模型预测被遮蔽的原子或基团。分子属性预测在预训练阶段同时加入多个属性任务让模型学习更丰富的化学语义。对比学习对同一分子的不同增强视图拉近它们在表示空间中的距离比如不同的 SMILES 枚举或不同的 3D 构象。预训练的优势在于下游任务的数据集可能只有几百条分子但通过大规模无标注分子预训练模型已经掌握了基础化学知识微调时更容易收敛泛化性也更好。6.2 分子生成与逆合成设计如果任务是分子生成Transformer 需要变成 Decoder-only 或 Encoder-Decoder 结构。每一步根据已有 Token 预测下一个 Token类似 GPT 生成文本。例如给定一个目标分子的 SMILES逆合成模型需要预测出合理的反应前体。常见做法是训练一个序列到序列模型输入是产物 SMILES输出是反应物 SMILES。评估指标通常包括分子有效性、覆盖率、重复率和化学合理性。这类模型对 Tokenizer 的依赖更强如果使用 BPE 分词需要保证片段能正确还原成合法 SMILES。建议在生成后统一用 RDKit 校验。6.3 3D 分子建模中的 Transformer当数据包含 3D 构象时Transformer 还可以引入空间位置编码。常见做法是在注意力分数中注入原子对之间的距离偏置score_ij Q_i · K_j / sqrt(d_k) distance_bias_ij其中distance_bias_ij由原子 i 和原子 j 的 3D 距离计算得到。距离越近的原子偏置越大注意力越高。这种机制在蛋白质结构预测、分子动力学模拟等任务中非常常见。七、论文复现经验如何高效读懂并实现 Transformer 类化学模型7.1 先复现基线再做改进经常有同学一上来就选一篇很复杂的最新模型结果代码写了两周还没跑通最后只能放弃。更好的策略是先复现经典模型再逐步加模块。建议顺序如下先跑通一个简单的 Transformer 分子性质预测模型。用同一份数据对比 MLP 分子指纹、GCN、Transformer 三种模型的效果。再引入图结构编码或预训练模型逐步逼近目标论文。通过这种“基线到进阶”的方式你能更清楚每个模块对结果的真实贡献。7.2 论文复现时优先确认的关键模块读分子建模相关论文时重点关注以下几个方面分子表示论文用的是 SMILES、分子图还是 3D 坐标这直接决定数据预处理方案。Tokenizer字符级还是 BPE词表大小是多少有无人为加入的特殊 Token注意力偏置是否加入分子键类型、原子距离等先验信息训练策略是否采用预训练、学习率调度、标签平滑评估协议数据集划分是随机划分还是按骨架划分这会影响结果的可比性。很多论文复现失败不是因为模型代码难写而是没有对齐数据预处理和训练细节。7.3 合理利用开源实现学习 Transformer 时正确的做法是先手写一遍核心模块再去参考成熟开源项目的工程优化。建议的参考顺序是自己实现 Multi-Head Attention、Encoder Layer。上手 PyTorch 自带的nn.TransformerEncoder、nn.MultiheadAttention。阅读化学方向开源项目的源码学习它们如何处理分子数据、如何设置注意力 Mask、如何做数据增强。当你能清楚地解释为什么开源代码里要加某个 Mask 或某个 Normalization 时说明你已经真正掌握了。八、常见问题与排查思路Transformer 在化学分子数据上训练时经常会遇到下面几种问题。这里整理成一张排查表。问题现象常见原因解决思路训练 Loss 不下降学习率过大或过小尝试 3e-4 到 1e-3 范围必要时使用 warmup模型过拟合严重分子数据集太小增加 SMILES 枚举、引入预训练或正则化生成的 SMILES 无效Tokenizer 切分破坏了化学语法使用 RDKit 校验调整 BPE 片段注意力分数异常位置编码缺失或 Padding Mask 未设置检查输入是否加入位置编码Mask 是否正确训练时显存不足序列过长或 Batch Size 过大降低 Batch Size缩短 SMILES 长度使用梯度累积分子性质预测偏差大标签计算方式与论文不一致确认 LogP、溶解度等属性的定义和单位复现效果和论文差异大数据集划分不同按论文使用 scaffold split 或相同随机种子8.1 关于 Padding Mask 的提醒在自注意力计算中Padding 位置不能参与注意力计算否则模型会把“空的 Token”当作有效信息。正确的做法是在注意力分数矩阵中把 Padding 位置对应的值设为负无穷Softmax 后权重趋近于零。def make_padding_mask(tokens, pad_idx0): # tokens: [batch_size, seq_len] mask (tokens ! pad_idx).unsqueeze(1).unsqueeze(2) return mask对应到前文代码中masked_fill(mask 0, float(-inf))就是完成这一步。8.2 关于 SMILES 序列长度的处理SMILES 序列长度分布非常不均匀。简单分子只有几个字符复杂分子可能超过 200 个字符。如果统一截断到固定长度会丢失长分子信息。建议先统计数据集的长度分布再设置合理的max_len或者使用动态 Padding 和 Bucket Iterator 来减少无效计算。九、最佳实践与工程建议9.1 数据层面所有 SMILES 在进入模型前统一做 Canonical 处理避免同一分子有多个不同写法。使用 RDKit 过滤无效分子避免数据集中混入解析失败的样本。数据集划分时优先考虑 Scaffold Split按分子骨架划分避免同骨架分子泄漏到训练集和测试集。记录数据预处理全流程包括 RDKit 版本、清洗规则、Tokenization 方式方便复现。9.2 模型层面在模型规模较小时d_model可以从 64 或 128 开始不一定要直接上大模型配置。学习率调度非常重要。Transformer 通常使用 Warmup 线性衰减或余弦退火。使用混合精度训练可以显著降低显存占用同时对分子性质预测任务影响很小。9.3 工程与部署层面训练脚本要支持固定随机种子方便结果复现。模型保存时同时保存 Tokenizer 词表、配置参数和预处理规则。如果要把分子大模型部署到线上服务建议使用 TensorRT、ONNX Runtime 或 vLLM 等推理优化框架具体取决于模型类型。对大模型推理需要关注请求延迟和吞吐量必要时做批处理、缓存和结果校验。9.4 安全与合规分子生成模型可能生成具有毒性或潜在危险的化合物相关实验必须在合法、合规、符合学术伦理的前提下进行。涉及药物、毒理、生物活性数据的模型不能随意公开敏感数据要遵守数据使用协议。任何模型训练和部署都应在授权环境中进行避免数据泄露。十、总结与下一步学习建议现在回头来看我们已经走完了 Transformer 从原理到代码再到分子建模实战的完整链路。重点内容包括自注意力机制如何让分子序列中的原子实现全局交互。多头注意力如何从不同维度捕捉化学特征。位置编码为什么不可省略。手写了一个可运行的 Transformer Encoder。用 RDKit 处理 SMILES完成分子 LogP 预测实战。梳理了从 Transformer 到化学分子大模型的进阶路径。整理了论文复现时的重点模块和排错经验。接下来可以按下面的顺序继续深入在更大规模数据集上训练分子性质预测模型比如 PubChem 或 ZINC 的子集。尝试把 Encoder 换成 Encoder-Decoder做 SMILES 到 SMILES 的反应预测。对比 Graph Transformer 和序列 Transformer 的差异理解各自适用场景。阅读并复现一到两篇经典的分子预训练论文比如基于 SMILES 和基于分子图的预训练模型。如果有 3D 分子数据加入原子距离偏置调研空间位置编码的研究进展。Transformer 本身并不是最终答案但它确实是理解化学AI大模型的起点。当初我在这条路上也花了不少时间反复在维度不一致、Mask 设置错误、Tokenization 不规范这些问题上打转。如果你现在刚起步不要着急把本文的代码一行一行跑通然后再尝试修改为你的分子数据集。学Transformer不是背结构图而是要让每个模块都能在你自己的项目里跑起来、改得动、解释得清。希望这份学习路径能帮你省下一些摸索时间。如果你在自己复现或落地过程中遇到问题也欢迎在评论区带着报错信息讨论一起把遇到的问题沉淀成可以复用的经验。