067

SA Score:合成可及性为什么要进入分子生成模型

SA Score 衡量合成可及性,是分子生成的必备护栏。这篇讲清它的原理、系统性偏差与替代方案。

SA Score(Synthetic Accessibility Score,Ertl & Schuffenhauer 2009)是最常用的合成难度快速估计。它在分子生成中是必备的护栏——没有它,生成模型会产出大量看起来合理但无法合成的分子。

原理

SA Score 由两部分构成:

  • 片段贡献分:统计 PubChem 中约百万个分子的片段(以 Morgan 指纹的形式)出现频率。常见片段 → 容易合成;罕见片段 → 难。分子中所有片段的频率加权得到基础分;
  • 复杂度惩罚:手性中心数、螺环、桥环、大环、分子尺寸等带来额外惩罚;
  • 最终归一化到 1(易)~ 10(难)

计算

from rdkit import Chem, RDConfig
import sys, os
sys.path.append(os.path.join(RDConfig.RDContribDir, "SA_Score"))
import sascorer

for smi, name in [
    ("CC(=O)Oc1ccccc1C(=O)O", "阿司匹林"),
    ("CN1C=NC2=C1C(=O)N(C)C(=O)N2C", "咖啡因"),
    ("CC12CCC3C(CCc4cc(O)ccc34)C1CCC2O", "雌二醇"),
]:
    mol = Chem.MolFromSmiles(smi)
    print(f"{name:8s} SA = {sascorer.calculateScore(mol):.2f}")

# 典型范围:
#   1~3   简单,常规反应可得
#   3~5   中等
#   5~7   较难
#   7~10  很难或需特殊方法

系统性偏差:必须知道

  • 它衡量的是「片段常见程度」而非「合成可行性」这是最根本的局限。一个用常规反应就能做出来、但骨架少见的分子,SA Score 会偏高——这系统性地惩罚了新颖性,而新颖性恰恰是分子生成的价值所在。
  • 不考虑起始原料可得性:结构简单但需要罕见砌块的分子,SA 分低但实际难做;
  • 对手性的惩罚可能过重:现代不对称合成能高效构建多个手性中心;
  • 不考虑官能团兼容性:分子中多个反应性基团的相互干扰、保护基策略完全不在考虑范围;
  • 对大环惩罚重:而大环化学近年进展很快。

在生成模型中的正确配置

# REINVENT4 中的配置(见 347)
[[stage.scoring.component]]
[stage.scoring.component.SAScore]
[[stage.scoring.component.SAScore.endpoint]]
name = "SA"
weight = 1.0
transform.type = "reverse_sigmoid"   # 分数低(易合成)时接近 1
transform.low = 2.0
transform.high = 6.0
transform.k = 0.4

# 关键设计:
# 1) 用平滑 transform 而非硬阈值
#    直接卡 SA < 5 会造成优化悬崖,
#    模型会在阈值边缘徘徊而非真正往简单方向走
#
# 2) 阈值不要卡太严
#    high = 6.0 而非 4.0,给新颖结构留空间
#
# 3) 权重适中
#    权重过高会让模型只产出平凡结构

替代与补充指标

指标 原理 优势
SA Score 片段频率 + 复杂度 极快,可用于生成每一步
SCScore 从反应数据学「产物比反应物复杂」 基于真实反应逻辑
RAScore 用逆合成求解结果训练的分类器 直接预测「能否找到路线」
实际跑逆合成 AiZynthFinder 等(见 385《AiZynthFinder》 最可靠,但慢
# RAScore:更贴近实际关心的问题
pip install rascore

from RAScore import RAScore_XGB
scorer = RAScore_XGB.RAScorerXGB()
print(f"RAScore: {scorer.predict('CC(=O)Oc1ccccc1C(=O)O'):.3f}")
# 0~1,越大越可能找到合成路线

# 分级使用策略:
#   生成阶段(每步都算)→ SA Score(毫秒级)
#   生成后初筛(几千个)→ RAScore
#   候选精选(几百个)  → 实际跑逆合成
#   最终确认(几十个)  → 合成化学家评审

用自家数据校准阈值

import numpy as np

# 通用阈值不一定适合你的团队
# 拿公司历史上真正合成过的分子算分布
historical_sa = [sascorer.calculateScore(Chem.MolFromSmiles(s))
                 for s in synthesized_compounds]

print(f"历史合成分子的 SA 分布:")
print(f"  中位数  {np.median(historical_sa):.2f}")
print(f"  90 分位 {np.percentile(historical_sa, 90):.2f}")

# 用 90 分位作为 transform.high,比照搬通用的 6.0 更贴合实际

# 更好的做法:
#   同时收集「设计了但最终没做出来」的分子,
#   看它们的 SA 分布 —— 这是真正的负样本
#   → 能定出更有判别力的阈值

为什么生成模型必须有这个护栏

  • 生成模型会优化你写下的分数:如果只优化活性预测分,模型会产出高分但化学上荒谬的分子;
  • SA Score 是最便宜的护栏:毫秒级计算,可以在生成的每一步都算;
  • 但它不够:还需要结构警报、PAINS 过滤、相似性约束一起构成完整的护栏(见 347《用 REINVENT4 生成新分子》);
  • 最终仍需人工审查:用 mols2grid(见 217《Mols2grid》)让药化专家过一遍 top 分子,几乎总能发现自动指标漏掉的问题。

关键要点

  • SA Score 衡量片段常见程度,不是真正的合成路线分析;
  • 它会系统性惩罚新颖但易合成的结构,阈值不要卡太严;
  • 用平滑 transform 而非硬阈值,避免优化悬崖;
  • 分级使用:生成时用 SA Score,候选精选时实际跑逆合成。

延伸资源