大语言模型这三年的进展,几乎是肉眼可见地改变了我们处理化学问题的方式。前几年做分子生成,大家默认的路径还是图神经网络、变分自编码器、扩散模型那一套,用离散图或者连续潜变量去描述分子。最近我重新刷 Nature Machine Intelligence 上那篇讲“引导大语言模型预测分子可合成性优化的编辑序列”的工作时,突然觉得思路清朗了不少——它没有重复“从零生成一个分子”的老路,而是把问题变成了一段一段有序的“编辑序列”,让 LLM 去预测:给定一个当前还比较难合成的分子,你该按什么顺序做几步修改,才能让它变得好合成,同时尽量保持原本想要的性质。与其说这是一个生成模型,不如说它更像一个“分子改造顾问”,直接告诉合成化学家从哪下手。
这篇论文很适合三类人看:一类是做 AI4Science 的研究者,想找个把 LLM 落到分子优化场景的比较有意思的切入点;一类是药物化学、计算化学背景的从业者,手里经常拿着活性还不错的分子,但下一步就是不知道能不能做出来,或者哪些修饰在合成上更划算;还有一类是做提示工程、模型微调的人,想看看 LLM 在化学这种强符号领域到底怎么才能“说人话”。我自己看完之后的直接感受是:这个方向没有想象中那么玄,核心还是数据的组织方式、原子映射的稳定性,以及如何让模型真正输出机器可执行的序列,而不是一本正经地胡扯。
1. 项目整体设计与思路拆解
1.1 为什么“可合成性优化”要比“直接生成可合成分子”更难
过去我常用各种可合成性过滤器:生成一个分子,算一下 Synthetic Accessibility 得分,低于某个阈值就淘汰。问题在于,这类分数本质上是一个静态指标,看到分数高只能说明“这个结构大概比较好蹭”,完全没有告诉你“如果你手里已经有 XYZ 分子,下一步应该怎么变”。分数是对最终产物的总结,不是一个过程性的指导。
论文把任务往前走了一步:给定一个起点分子,模型生成一个有序的编辑序列,经过这些编辑之后,产物的可合成性得分显著下降(或者更严格地说,逆合成可行性提高),同时分子的核心骨架、目标口袋里的结合特性尽量不丢。这个思路类似于“从难合成的候选物,到容易合成的等价物”之间架了一座桥,而不是把分子拍到高维空间里再拿去撞墙。
这样做还有个实际原因:从头生成很容易得到一堆“纸面看起来很漂亮、做起来完全不是那么回事”的结构,因为合成路线充满了顺序问题——某些官能团必须在特定步骤之前装上去,某些保护基如果提前去掉后面就废了。所以真正决定一个分子能不能做出来的,不是单一的分子图,而是这个分子图背后的一长串操作顺序。把可合成性优化转成编辑序列的预测,等于把“结果问题”转成“动作问题”,信息量更大,也更贴近化学家的工作习惯。
- 静态可合成性打分只能给你一个数,编辑序列给你的是六七步操作。
- 操作顺序隐含了合成可行性,同一个分子换不同顺序,成功率完全不同。
- 编辑序列天然支持人机协同——化学家可以只采纳第一步,再按自己的知识往下走。
1.2 为什么这次选择大语言模型,而不是图神经网络
如果用图神经网络做分子改造,通常需要设计一个消息传递网络,把原子和键编码成节点和边,再通过某种解码器输出修改后的图。听起来没问题,但图模型有一个天然短板:它输出的是“最终改完的图”,而不是“编辑操作本身”。少原子的替换、断键加环这类操作,虽然也可以表示为图编辑谓词,但要端到端学一个有序的图编辑策略,解码空间会被撑得很大。
大语言模型则不同。它天然以序列为单位,而分子可以用 SMILES、SELFIES 或原子映射编号后的线性表示来编码。编辑序列本身就很好线性化:“第一步,把原子 7 和原子 12 之间的键断开;第二步,在原子 9 上加一个氟原子;第三步,把五元环扩成六元环。”本质上这就是一段化学语言语法。LLM 在大量文本上做预训练时,已经学到了序列之间的统计依赖关系,这种依赖关系恰好可以用来表示“先断键、后替换”的关系。
还有一个更实际的理由:如果连论文都跑到 LLM 这个赛道,大概率是因为微调门的门槛对于大部分实验室来说更低。有很多开源权重可以直接用,你也可以非常轻易地把分子编辑序列的示例塞进 prompt,让模型先试跑一轮 few-shot,再决定要不要做全量微调。图神经网络往往得从零开始训,而且每次想改一个输入特征都要重新设计网络结构,迭代成本高不少。
图神经网络与 LLM 在编辑序列式分子优化上的对比
| 维度 | 图神经网络 | 大语言模型 |
|---|---|---|
| 输入表示 | 原子图/键级矩阵,需要额外区分原子映射 | SMILES、SELFIES、原子编号序列等一维文本 |
| 输出方式 | 一般输出修改后的完整图 | 输出离散操作 token 序列,天然有序 |
| 顺序建模 | 需要额外引入解码顺序或自回归 | 自回归机制天然建模编辑顺序 |
| 先验知识 | 主要在化学图上学习 | 在语言语料+化学文本上有大量先验 |
| 修改代价 | 改一个任务,网络结构通常要跟着动 | 换 prompt 或微调指令就能适配新任务 |
| 可解释性 | 难解释“为什么这样改” | 每一步编辑都是显式文本,化学家能看懂 |
当然,用 LLM 不是没有代价。最明显的是 token 化问题:一个 SMILES 串被切成语义词元之后,原子索引、环数字这些数字 token 容易错乱。所以后面所有工程上的严谨性,都在围绕这个问题兜底。
1.3 数据配方:怎么把编辑序列变成模型的监督信号
要训练模型去预测编辑序列,首先得有一堆“编辑前的分子 + 编辑序列 + 编辑后的分子”三元组。论文里的做法,我复现时是分成了三层来构造数据。
第一层,拿公开小分子库,比如 ZINC、ChEMBL 里的中性小分子作为“目标性质良好但可合成性需要提升”的起点。第二层,用一批有机反应模板,比如酰化、还原胺化、铃木偶联、保护基引入等,去反向生成“难合成的候选物”——大致思路是先随机抽取一个可合成的参考分子,按若干步反应模板随机地加官能团或改骨架,把它“弄乱”,得到一个难合成的中间体。第三步,把真实反应图反过来翻译成编辑序列:从难合成中间体出发,每应用一个逆合成步骤,就对应一条编辑记录,最终恢复成一个好合成的分子。整条链就是监督信号。
这里有个容易被忽略的关键点:编辑序列不是随便写的“修改清单”,它必须对应真实的化学可操作性。比如 “把芳环上的溴变成硼酸”这种操作,在现实中是要经过金属催化的偶联或者官能团互换的,不能只把 SMILES 改一改就当可合成路径。所以数据构造阶段最好用带原子映射的反应模板,让模型学到的每一步都能映射到现实中可执行的合成变换。
另外,数据里还要考虑样本平衡。如果数据集里全是三步以内的短编辑链,模型会偏向偷懒,看到一个难合成分子就只做一个浅层修饰,完全不解决根本问题。最好按编辑步数分布采样,让三到六步的中长编辑序列也占到一个合理比例,模型才能真正学会多步规划。
2. 核心细节解析与实操要点
2.1 编辑序列的表示法:原子映射是命门
一提到“编辑序列”,新手最容易犯的错误就是直接用原 SMILES 里的原子顺序号当操作目标。比如模型输出“把 C5 的氟原子换成甲氧基”,如果这条序列不是在统一的原子映射语境下生成的,那 C5 这个编号完全可能随着一个断键操作发生变化。你刚把第 3 步执行完,第 4 步里的“C8”已经变成另一个原子了,整个序列彻底失效。
所以说,让 LLM 学一个稳定的原子映射上下文非常重要。简单来说,你要在模型的每轮输入里都给出“当前分子的带编号 SMILES”,并且确保这个编号在对分子做原子删除、插入时能尽量保持稳定。具体实现里,我比较推荐用 RDKit 的原子映射机制,先把每个反应物原子标上规范映射号,再做反应转化。模型看到的是一个带编号的线性式,类似:
[C:10]1([H])N([H])Cc(c1)-c1ncc([F:13])cc1当模型输出编辑操作时,引用的是这些稳定编号,同时每执行完一步,就把当前新分子重新生成新的编号方案,再拼成下一步的上下文喂回给模型。这样虽然多几次 RDKit 调用,但能避免绝大多数“索引越界”问题。这一步做得稳,后面一切推理才可信。
2.2 可合成性指标怎么选,以及为什么要多指标共同约束
论文标题里说的是“可合成性优化”,但可合成性本身不是一个单一客观量。常见的 SACores 或 RAscore 都是从结构复杂度出发的启发式分数;而严格意义上,合成可行性是“是否存在一条可行的合成路线”。更贴近实际的是用逆合成模板库做一个可合成性判定:给定一个分子,看它能否在一到三步内匹配到已知反应模板。这种判定的结果更可靠,但开销也更大。
我在复现时,采用了“模板库可合成性 + SAScore + RAscore”三者结合的指标。训练时主要用模板库结果作为强约束,因为它是布尔判定,直接反映了“编辑序列是否真的把分子推向可合成”。SAScore 和 RAscore 作为软指标,用来平滑优化,防止模型为了匹配模板而过度改变分子的核心结构。
指标对比速查
| 指标 | 类型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| SAScore | 启发式分数 | 速度快,零成本 | 对复杂环系和立体化学判断粗糙 | 大规模初筛 |
| RAscore | 随机森林预测 | 比 SAScore 更接近人工判断 | 依赖训练集分布,可能偏移 | 中规模筛选 |
| 逆合成模板库判定 | 基于规则/检索 | 最贴近真实可合成性 | 覆盖范围受模板库限制 | 编辑序列数据构造与最终评估 |
| 专家打分 | 人工 | 最可靠 | 昂贵,无法规模化 | 下游实验前最终确认 |
一个非常容易踩的坑是:只优化一个指标,比如用 SAScore,可能导致模型发现“把所有环都拆掉”就能无限降低难度,但那已经不是同一个分子了。因此设计损失函数时,我会把编辑序列的前若干步设成“骨架不变”的操作,只有后几步才允许较大改动。论文里更优雅一点的做法,是在 prompt 里显式给出一个“最大编辑预算”和“核心骨架 SMILES”,要求模型在核心骨架不变的前提下完成优化,超过预算的输出一律判非法。
2.3 提示词设计与输出解析:让 LLM 老老实实吐序列
如果直接用“帮我优化这个分子的可合成性”这种开放式的 prompt,模型大概率会给你一段散文,而不是机器可执行的编辑序列。这个方向的实际项目里,最稳的方案是给模型规定输出格式,并在输出层做一次语法校验。你可以把 prompt 设计成:
你是一个药物化学编辑器。输入是一个待优化分子 SMILES 和它的可合成性分数。 请只输出一个 JSON 对象,包含一个列表,每个元素为: {"op": "add|del|sub|ring_modify", "target_atom": 7, "value": "F", "reason": "..."} 条件: - 最多 4 步编辑 - 保持核心骨架 SMILES 中的环系统不变 - 每一步之后更新原子映射实际推理时,模型可能偶尔输出“我觉得应该先改这里的氟……”,这种就靠解析器直接拦掉。更稳妥的方式是设置一个简单的状态机:先要求模型强制输出一个带固定键名的 JSON,用 json.loads 解出来;解不出来就重试一次,重试时把上一条非法输出贴回去当作负样例。
除了限制输出格式,还要限制模型“不要解释太多”。化学语境里,让 LLM 解释原因很诱人,但 token 一多,指令跟随能力就会下降。所以 prompt 里要写清楚:所有推理过程到思维链为止,但最终输出必须仅保留可执行的部分。如果要评测,我更倾向于把思维链藏在辅助字段里,不参与后续的分子编辑执行。
3. 实操过程与核心环节实现
3.1 环境与工具选型
这个方向并不需要特别夸张的算力。一张 24 GB 显存的消费级卡,比如 RTX 3090/4090,就能跑 7B 量级的开源模型微调。更省的做法是直接拿一个 3B~7B 的模型做 LoRA 微调,OneCard 就能完成整个训练周期。不过要注意,LLM 推理时嵌套 RDKit 的原子映射和毒理性检查,这部分函数调用才往往是瓶颈,跟 GPU 无关。
具体软件栈,我用的是这样一套:
# 核心环境 python 3.11 torch 2.x transformers 4.4x peft # 做 LoRA 微调 rdkit # 分子读写、反应模板、原子映射 rdchiral # 基于模板的逆合成扩展 json5 # 容忍大语言模型常见的小 JSON 格式问题除了这些,建议数据结构层用 HDF5 或者 parquet 存编辑序列样本,因为几百万条数据如果全放内存,跑实验时会反复换页。我自己实际用到 50 万条左右的编辑链,parquet 文件加起来不到 2 GB,读取速度比 CSV 快得多。
3.2 从公开数据库构造编辑序列数据集
构造数据集是整条管线里最花时间的一步,没有捷径。我把工作拆成了四个阶段:
- 筛选起始分子集合。从 ChEMBL 里随机抽 30 万个分子,过滤掉分子量 > 600、含有金属原子或者明确包含不可合成基团的样品,避免数据集里出现太多“宇宙分子”。
- 编辑链反向生成。先用反应模板库对起始分子做逆合成扩展,得到前体和产物。再从前体出发,把整条逆合成路线翻成编辑序列:每用一步模板,就记录对应的操作类型和原子映射。
- 数据清洗与平衡。剔除任何中间分子在 RDKit 里无法解析的样本;按编辑步数和模板类别做分层采样,防止某些反应类型占据绝对主导。
- 标准化 token 化。把每个分子转成带原子映射的 SMILES,然后把编辑操作映射到一组专门的 token,比如
<ADD_ATOM>、<DEL_BOND>、<REPLACE_GROUP>、<RING_EXPAND>、<RING_CONTRACT>。
实际中还要做一步“副作用验证”:每生成一条编辑序列,就实际把它执行一遍,确认从起点分子开始,按序执行后真的能到达目标分子。这步不做的话,你会在训练中引入大量“模型永远也学不会”的噪声标签。我当时第一次构造数据就偷懒跳过了验证,结果训练集里大量序列执行到一半就断掉,损失一直下不来,后面花了整整两天排查才发现是数据问题。
3.3 微调与推理策略:温度、束搜索与自一致性
模型选择上我试过两类:一类是 ChemT5 这类本来就抠到化学语义的 encoder-decoder 模型,另一类是通用代码模型,比如 CodeLlama 和 Qwen2.5-Coder 这类。实践下来,通用的代码模型在“输出 JSON 编辑序列”这种任务上反而更稳定,可能是因为它们已经见多了结构化的 key-value 格式。如果你想快速起步,直接拿一个 7B 的 Qwen2.5-Coder 做 LoRA 微调,效果已经会非常可喜。
训练阶段最重要的策略是“把编辑序列当作代码生成任务来做格式化”。训练样本的输入部分不仅包含当前分子 SMILES,还要附上“当前核心骨架约束”和“已执行过的编辑步骤”,输出部分则是严格的 JSON 列表。掩盖函数要确保损失只计算输出部分,否则模型在复述输入上占用过多容量。
推理阶段,我推荐微调后用三个设置:
- temperature 0.6~0.8:给一定随机性,但别太高。高于 1.0 时,输出经常为了凑新序列而乱拆键。
- beam search 5~10:在低温度下,束搜索质量比 top-p 采样要好。
- self-consistency:采 5 个候选编辑序列,逐个用 RDKit 执行并检验最终分子是否达到了可合成性阈值,选通过验证的那条。如果都有问题,选与当前分子相似度最高的那条回退。
这一套下来,成功率比单次 beam search 高 10 到 20 个百分点,尤其那些需要四步以上编辑的分子,几乎离不开 self-consistency。代价就是推理时多几倍的解析耗时,但考虑到一步验证开书最贵的是 RDKit 而不是模型推理,这个取舍很划算。
4. 常见问题与排查技巧实录
4.1 非法 SMILES、索引越界等生成崩溃问题
这类问题几乎每个人都会遇到。最典型的现象是模型输出的编辑序列引用了不存在的原子编号,或者执行完一步后产物的 SMILES 解析失败。排查的第一步,先把“模型输出”和“数据后处理”分开:用一个小样本检查模型输出本身是否合法,再检查 RDKit 执行端是否有 bug。我有一半的“模型很蠢”其实都是后处理代码写错了:比如原子映射编号没有在每步之后重置,或者执行删除操作时没更新键序。
真正要根治,还得靠推理时的状态校验。我采用“每步都调用一次 RDKit,生成合法的新分子再进入下一步”的做法。同时,在给模型拼接下一步输入时,重新生成带原子映射的 SMILES。这一步虽然拖慢了推理速度,但可靠性大幅提升。如果你需要批量跑大量分子,可以给 RDKit 的解析和映射函数写一个缓存,同一个中间结构不要反复计算。
4.2 骨架漂移:指标优化了,分子却变成另外的分子
你可能会看到模型输出了很长一串编辑序列,算下来 SAScore 确实漂亮,但最终分子跟原分子结构几乎毫无关系。这种问题在只用单一可合成性指标时特别常见,因为模型很会钻空子:把芳环全打断、把杂原子全删掉,当然是“好合成”。
解决骨架漂移,我这里有三个土办法:
- 编辑预算硬限制:在 prompt 里写明最多几笔,并在概率解码时对输出 token 数做上限截断。
- 核心骨架替换检测:预先用 RDKit 提取分子的 Murcko 骨架,编辑结束后重新提取,如果骨架不一致,直接判负。
- 相似度惩罚:在推理选序列时,把最终分子与起始分子的 ECFP4 相似度作为一个加权条件,相似度低于 0.3 的结果直接丢弃。
论文里其实也把“保持核心骨架”作为一个可配置的提示条件,并不是硬性限制每个人都要严格守。但在我的场景里,骨架漂移是下游最不能接受的问题,毕竟药化项目里骨架改变意味着一切都得重来。
4.3 生成多样性差与局部收敛
如果你的模型总是给不同起点分子输出几乎一样的编辑序列,比如不管什么分子都“把某个位置的甲基换成氟”,那大概率不是模型笨,而是数据里这类操作占了绝对多数。解决办法是统计训练数据里编辑操作类型的分布,如果不平衡,就用采样器把低频操作加权放大。这个操作跟处理类别不平衡的方式如出一辙。
还有一个容易被忽视的因素:如果推理时温度过低,且束搜索宽度很窄,模型无论怎么跑都会收敛到同一条高分序列。你可以先设定温度 0.7 采样 20 个候选,再看多样性是否上来。如果还不行,就要回到数据层面,看看是不是高频模板压制了其他模板的表达。
4.4 推理速度与批量验证的工程优化
这个方向容易被论文刷屏式宣传误导,以为模型是瓶颈。实际上在每一步都要做 RDKit 验证的流程里,LLM 推理只占不到一半时间,另一半全消耗在分子解析、映射和算法评价上。我实测的一次跑批,8000 个分子、每个 4 步编辑,总耗时约 21 分钟,其中 GPU 推理只占 9 分钟,RDKit 相关操作占了 12 分钟。
优化方向主要是批量化和缓存。比如,所有分子转换都尽量用 RDKit 批量函数,别一个一个走 Python 循环;遇到相似度计算,用 ECFP4 指纹的预计算缓存;遇到 SAScore,可以把同一个中间结构的评分结果存到内存里。另外,RDKit 的原子映射重建很耗内存,建议把映射结果持久化,第二次跑就不需要再算一次。把工程这层优化后,全流程能提速大约一倍。
5. 后续扩展与个人实践建议
5.1 把编辑序列接到逆合成工具链上
论文这个方向最自然的扩展,就是不让 LLM 停在“编辑建议”这一步,而是把模型输出的每一步编辑直接作为逆向合成工具链的输入。比如模型先建议“在 N7 上加一个叔丁氧羰基保护基”,下游就通过模板库搜索具体反应条件,或者交给 retrosynthesis model 去补全详细路线。这样做的好处很明显:LLM 负责高层策略和优先级,规则系统负责低层精确反应,两层结合,出错率会显著下降。
我实际做落地方案时,会在编辑序列的每个操作 token 后面加一个“template_id”字段,记录这条操作来自哪个反应模板。这个 id 可以作为下游验证的钥匙,也能在过滤时排除那些没有可靠反应条件的编辑。没有模板支撑的编辑,只能在最后一步被采纳,前面的尽量不碰。
5.2 用强化学习在线调整编辑路径
历史数据训练出的编辑策略,到了分布外的新分子上不一定还灵。更进阶的做法是引入强化学习:把“可合成性提升幅度”和“骨架相似度保持”整合成一个 reward,用策略梯度对生成编辑序列的 LLM 做在线调优。我记得前一阵也有人用类似思路做分子对接优化,效果还不错。
但强化学习在这个任务上有两个风险,一个是 reward hacking,模型总会发现你指标里的漏洞;另一个是探索开销太大,毕竟每一条编辑序列都要跑一遍完整的化学验证。稳妥的做法是先用离线数据微调出一个基本可用的模型,再用 RL 做小批量小步长的适应,别一上来就大规模探索。
5.3 避坑清单:数据泄漏、词表设计与版本兼容
最后给四个我踩过的比较深的数据和工程坑。
一是数据泄漏。如果你从同一个数据库里既抽了起点分子,又用于可合成性评估,那么模型很可能会“记住”某个分子的标准答案,导致评估虚高。最好把评估集刻意与训练集按 Bemis-Murcko 骨架去重,确保常见骨架至少在两两之间差异大于一定阈值。
二是自定义 token 必须留白。当你在 LLM 词表里新增<ADD_ATOM>这类 token 时,务必检查嵌入层规模和 tokenizer 的保留位,不然扩容后位置编码错位很坑。我自己就遇到过 LoRA 微调后新 token 的 embedding 没初始化,训练出一片奇怪的数字。
三是RDKit 版本必须锁死。同一个分子在不同 RDKit 版本里的规范 SMILES 表示可能不同,直接影响原子映射编号的一致性。项目初期就固定版本号,并在 README 里写明,否则隔一个月再看数据,生成结果根本对不上。
四是输出校验比模型重要。在真实项目中,模型只需要有三四成概率生成合法序列,配合状态机和多次采样,最终可用率就能拉得很高。反过来,模型再强,如果输出端没有严格校验,整个系统的成功率也会被无效调用拖垮。
我个人做这套流程最大的体会是:不要把 LLM 当成一个独立的“化学家”,把它当成一个擅长提出建议但需要严密监督的实习生。你说清楚格式、给足上下文、为它准备好每一步要验证的工具,它就能在编辑序列预测这种任务上给出相当惊艳的结果。但如果放任自由发挥,它也很容易一本正经地输出一堆根本执行不了的分子。后续如果再有人拉我一起做分子优化项目,我大概率会沿用这套“LLM 出策略、规则引擎做校验、模板库保底”的三角结构,它虽然朴素,但在真实合成场景里是少数能让化学家愿意点下“采纳”按钮的方案。