032

SELFIES 入门:为什么它比 SMILES 更适合分子生成

SELFIES 保证生成的字符串总是有效分子。这篇讲清它的编码原理、优势场景与实际的适用边界。

SELFIES(SELF-referencIng Embedded Strings)解决了 SMILES 在分子生成中的一个具体问题:随机生成或修改的 SMILES 常常是无效的分子,而 SELFIES 保证 100% 有效

SMILES 在生成中的问题

# 【问题一:语法可能不合法】
#   随机改一个字符:
#     "c1ccccc1" → "c1ccccc2"    环闭合不匹配 → 无效
#     "CC(C)C"   → "CC(C)C)"     括号不匹配 → 无效
#
# 【问题二:语法合法但化学不合法】
#   "C(C)(C)(C)(C)C"   五价碳 → 化学上不可能
#
# 【后果】:
#   生成模型需要「学会」这些规则
#   → 浪费模型容量
#   → 生成的分子中相当比例无效
#   → 【在隐空间中做优化时,很多点解码不出有效分子】(见 149)
#
# 【SELFIES 的解决方案】:
#   设计一套编码规则,使得
#   【任意的符号序列都能解码成有效分子】
#
#   核心机制:
#     1) 每个符号携带「派生规则」
#     2) 解码时维护一个状态(当前原子的剩余价键数)
#     3) 【如果某个符号会导致违规,就按规则替换成合法的】
#     4) 环闭合用【相对索引】而非绝对标号
#        → 不会出现「闭合不匹配」
#
#   → 结果:100% 有效性(这是数学上保证的,不是统计上的)

使用

pip install selfies

import selfies as sf
from rdkit import Chem

# 编码与解码
smiles = "CC(=O)Nc1ccc(O)cc1"
encoded = sf.encoder(smiles)
print(encoded)
# [C][C][=Branch1][C][=O][N][C][=C][C][=C][Branch1]...

decoded = sf.decoder(encoded)
print(decoded)
print(Chem.CanonSmiles(decoded) == Chem.CanonSmiles(smiles))   # True

# ---- 验证「任意序列都有效」----
import random

alphabet = list(sf.get_semantic_robust_alphabet())
random.seed(42)

valid_count = 0
for _ in range(1000):
    length = random.randint(5, 20)
    random_selfies = "".join(random.choices(alphabet, k=length))
    smi = sf.decoder(random_selfies)
    if Chem.MolFromSmiles(smi) is not None:
        valid_count += 1
print(f"随机 SELFIES 的有效率: {valid_count/1000:.1%}")   # 100%

# 【对比】:随机的 SMILES 字符序列几乎全部无效

# ---- 用于生成模型的词表 ----
dataset = ["CCO", "c1ccccc1", "CC(=O)Nc1ccc(O)cc1"]
selfies_list = [sf.encoder(s) for s in dataset]
alphabet = sf.get_alphabet_from_selfies(selfies_list)
alphabet.add("[nop]")     # padding token
alphabet = sorted(alphabet)
symbol_to_idx = {s: i for i, s in enumerate(alphabet)}

# 转成整数序列
max_len = max(sf.len_selfies(s) for s in selfies_list)
for s in selfies_list:
    label, one_hot = sf.selfies_to_encoding(
        s, vocab_stoi=symbol_to_idx, pad_to_len=max_len)
    print(label)

什么时候用 SELFIES

场景 SELFIES 的价值
遗传算法的变异/交叉 高——随意修改都有效
隐空间优化 高——避免解码失败(见 149《MolMIM 论文精读》
从零训练生成模型 中——省去学语法的负担
强化学习生成 中(见 137《REINVENT 论文精读》
性质预测 低——有效性不是问题
数据存储与交换 低——SMILES 更通用可读
子结构搜索 低——用 SMARTS 更好

实际的局限

# 【局限一:有效不等于合理】
#   SELFIES 保证「化学上可能存在」,
#   但【不保证】:
#     - 分子稳定(可能极不稳定)
#     - 可合成
#     - 类药
#     - 有意义
#
#   → 【100% 有效性只解决了最低层次的问题】
#   → 后续的过滤(性质、警示、可合成性)仍然必需
#
# 【局限二:序列变长】
#   SELFIES 通常比对应的 SMILES 长
#   → 序列模型的负担增加
#   → 长程依赖更难学
#
# 【局限三:可读性差】
#   SMILES 有经验的化学家能直接读
#   SELFIES 基本不可读
#   → 【调试与人工检查时不便】
#
# 【局限四:生态支持不如 SMILES】
#   多数工具、数据库用 SMILES
#   → 需要来回转换
#
# 【局限五:局部性差】
#   修改 SELFIES 中的一个符号,
#   【可能导致后续结构大幅改变】
#   (因为解码是有状态的)
#   → 这对「小改动 = 小变化」的优化直觉不利
#   → 在遗传算法中可能产生跳跃过大的变异
#
# 【近年的实践观察】:
#   随着生成模型能力提升,
#   直接用 SMILES 的有效率也能达到 95% 以上
#   → 【SELFIES 的相对优势在缩小】
#   → 但在遗传算法与隐空间优化中仍有明确价值

与其它表示的选择

# 【决策流程】
#
# 我要做什么?
#
# ├─ 性质预测
# │   → 【用 ECFP 指纹或分子图】(见 035、034)
# │     字符串表示不是最优选择
# │
# ├─ 生成模型
# │   ├─ 需要保证有效性(GA、隐空间优化)
# │   │   → 【SELFIES】
# │   └─ 用序列模型自回归生成
# │       → SMILES 也可以(现代模型有效率已很高)
# │         + SMILES 增强(见 145)
# │
# ├─ 数据存储与交换
# │   → 【SMILES】(通用、可读)
# │     + InChIKey 作为唯一标识(见 033)
# │
# ├─ 子结构搜索
# │   → 【SMARTS】(见 031)
# │
# └─ 三维相关任务
#     → 需要生成构象(见 049)
#
# 【一个务实的建议】:
#   不要为了用 SELFIES 而用
#   先确认「无效分子」真的是你的瓶颈
#   → 如果生成的分子有效率已经 95%+,
#     换 SELFIES 的收益很小

关键要点

  • 100% 有效性是数学上保证的,不是统计上的——任意符号序列都能解码成有效分子;
  • 有效不等于合理——不保证稳定、可合成或类药,后续过滤仍必需;
  • 真正有价值的场景是遗传算法的变异与隐空间优化
  • 局部性差:改一个符号可能导致后续结构大幅改变,不利于「小改动=小变化」的优化。

延伸资源