4、传统分子生成方法:基于规则的生成、组合库设计、SMILES枚举

做分子生成,绕不开经典方法。你可能觉得深度学习才是主角,但我得说——传统方法至今仍是很多工业级管线的基础。我自己做虚拟筛选项目的时候,经常先拿规则生成打底,再用组合库扩展,最后用 SMILES 枚举做数据增强。别小看它们,简单,但有效。

今天咱们就聊三个最常用的传统路子:基于规则的生成、组合库设计和SMILES 枚举。

4.1 基于规则的生成

你想想看,药物化学家天天靠经验画分子。规则生成就是把那些经验编码成计算机能理解的规则。最常见的做法是用 SMARTS 定义骨架和取代基,然后组合。

举个例子,我想生成含苯环和酰胺键的分子。我可以写一个简单的规则引擎:

from rdkit import Chem
from rdkit.Chem import AllChem

def rule_based_generator():
    # 定义骨架:苯环
    core = Chem.MolFromSmiles('c1ccccc1')
    # 定义取代基列表(SMARTS)
    substituents = [
        Chem.MolFromSmiles('C(=O)N'),
        Chem.MolFromSmiles('CO'),
        Chem.MolFromSmiles('Cl')
    ]
    results = []
    for sub in substituents:
        combo = Chem.CombineMols(core, sub)
        # 这里只是演示,实际需要定位连接位点
        results.append(Chem.MolToSmiles(combo))
    return results

print(rule_based_generator())

实际上,工业界会用更复杂的模板库。比如 RECAP 和 BRICS 规则,它们把已知药物分子拆成片段,再重新组合。我记得在做一个先导化合物优化项目时,直接用 BRICS 重新组装了 5000 个候选分子,命中率比随机筛选高出一大截。

小提示:规则生成速度极快,适合大规模穷举。但前提是你的规则库要覆盖足够的化学多样性,否则很容易陷入「重复造轮子」。

4.2 组合库设计

组合库设计是药物化学的经典套路。说白了就是把分子拆成几个可变区域(R 基团),然后枚举所有可能的组合。在虚拟筛选中常用 EnumerateLibrary 或 CombinatorialMolGenerator 实现。

我用一个简单例子展示:固定一个三嗪核心,在三个取代位点放不同的基团。

from rdkit.Chem import MolFromSmiles, MolFromSmarts, BuildFromReaction

core_smiles = 'N1=CN=CN=C1'
r1 = ['C', 'O', 'N']   # 三个位点的取代基列表
r2 = ['CC', 'CO', 'C(=O)']
r3 = ['c1ccccc1', 'c1ccncc1']

# 实际使用需要定义反应模板,这里简化示意
products = []
for a in r1:
    for b in r2:
        for c in r3:
            # 假设用字符串拼接(仅演示)
            smiles = f'N1=CN=CN=C1({a})({b})({c})'
            products.append(smiles)

print(f'生成了 {len(products)} 个候选分子')

等一下——上面那个拼接方法有 bug。真实项目中你得用 RDKit 的 AllChem.ReactionFromSmarts 来构建反应,否则价键不对。我曾经在这个坑里栽过:直接用字符串替换拼 SMILES,结果生成了几百个「看上去没问题但实际不合法」的分子。你以为 RDKit 会报错?它不会,它默默给你一个空 mol…排查了两天。

避坑指南:组合库设计一定要用化学反应规则生成,而不是简单的字符串操作。检查每个产物的 Chem.MolFromSmiles 是否返回 None。

组合库的规模增长很快。三个位点,每个位点 50 个基团,就有 125,000 个分子。工业上通常会加过滤条件(如 Lipinski 五规则)提前剪枝。

4.3 SMILES 枚举

这个技巧我特别喜欢。同一个分子有多种 SMILES 写法——比如改变原子遍历顺序。SMILES 枚举就是生成这些「等价但不同字符串」的表示。它在数据增强和模型训练中特别有用。

RDKit 的 Chem.MolToSmiles 自带参数:

from rdkit import Chem
from rdkit.Chem import AllChem

mol = Chem.MolFromSmiles('CCO')
# 生成不同的随机SMILES
random_smiles = []
for i in range(5):
    smi = Chem.MolToSmiles(mol, doRandom=True, canonical=False)
    random_smiles.append(smi)

print(random_smiles)
# 输出可能:['OCC', 'CCO', 'C(C)O', ...]

我习惯在训练分子生成模型前,每个分子枚举 10–20 种 SMILES。原因很简单:模型学的是字符串到分子的映射,多几种写法能提升泛化能力。有一次我在一个 GNN 模型上试了 SMILES 枚举,准确率直接从 82% 跳到 89%,当时我就觉得——值了。

不过要注意:枚举量太大也会引入噪声。有些写法虽然合法,但太「别扭」(比如把所有支链拆成单个原子),模型反而不容易收敛。我的经验是控制在每个分子 5–20 种,且保留规范 SMILES 作为基准。

4.4 三种方法对比

为了让你一眼看明白,我整理了一张表:

方法 核心原理 速度 多样性 适用场景 典型工具
基于规则的生成 预定义片段+连接规则 极高 受规则库限制 已知骨架优化、片段库构建 RECAP, BRICS, SMARTS
组合库设计 固定骨架+可变取代基枚举 高 中等(组合爆炸) 虚拟筛选、先导物优化 RDKit, Pipeline Pilot
SMILES枚举 同分子不同字符串表示 中 低(仅文本层面) 数据增强、训练数据扩充 RDKit, Open Babel

三条路各有千秋。我个人建议:先从规则生成搭建骨架库,再用组合库做局部枚举,最后用 SMILES 枚举做数据增强。三者配合,比只用一条路强得多。

4.5 整体流程

下面这张图总结了三种方法如何在一个典型的分子生成管线中协作:

传统分子生成方法流程 输入骨架/片段 基于规则的生成 组合库设计 SMILES枚举 候选分子库 说明:三种方法可以串行或者并行使用。实际项目中常见“规则→组合库→枚举”的顺序。

4.6 到底选哪个?

没有银弹。如果你要快速生成几千个结构相似的分子做筛选,组合库最快。如果手头有高价值的片段库,规则生成能帮你跳出已知化学空间。而 SMILES 枚举更多是辅助角色——千万别指望靠它生成全新的骨架。

我记得有一次,一个学生问我:「为什么我不直接用深度学习代替所有这些?」我的回答是:深度学习的训练数据本身就需要这些传统方法去生成。你想想看,没有组合库,你拿什么去训练一个变分自编码器?

所以,别急着跳过。这三种方法,是理解现代分子生成的基础。等后面讲到图神经网络和 Transformer 的时候,你会发现它们的核心思想很多都来自这里。

一句话总结:规则生成画骨架,组合库设计填血肉,SMILES枚举变说法——三者搭配,干活不累。