AiZynthFinder 完整教學 — AI 逆合成規劃工具
1. 專案概述
AiZynthFinder 是 AstraZeneca Molecular AI 團隊開源的逆合成規劃工具(retrosynthetic planning tool),也是該領域目前最成熟的開源方案(845 stars,MIT 授權)。
核心能力
- 從一個**目標分子(target molecule)**出發,自動找出合成路線(synthetic routes)
- 預設使用 Monte Carlo Tree Search (MCTS) 演算法,以神經網路策略遞迴拆解分子
- 搜尋終止條件:所有葉節點分子都能在**商用試劑庫(stock)**中買到
- 支援多種搜尋演算法、多目標最佳化、自訂評分函數
你為什麼需要它
在 lead optimization(先導化合物優化)工作流中,AI 產生的 analog(類似物)候選分子需要回答一個關鍵問題:這個分子做得出來嗎? AiZynthFinder 的角色就是回答這個問題,並找到:
- 合成步驟盡可能少的路線(降低實驗成本與時間)
- 所有起始原料都是可商業購買的 building block(確保可行性)
flowchart LR
A["Lead Optimization
AI 產生 analog"] --> B["AiZynthFinder
逆合成分析"]
B --> C{"所有 building block
可商購?"}
C -->|"Yes"| D["路線評分
選最短路線"]
C -->|"No"| E["排除 / 調整結構"]
D --> F["送交合成化學家
實驗執行"]
2. 安裝與環境設定
2.1 基本安裝
1# Step 1: 建立獨立的 conda 環境
2conda create "python>=3.10,<3.13" -n aizynth-env
3
4# Step 2: 啟用環境
5conda activate aizynth-env
6
7# Step 3: 安裝完整版(建議)
8python -m pip install aizynthfinder[all]
[all] 安裝內含 pymongo、route-distances、scipy、molbloom 等額外功能。若只需最小安裝可用 pip install aizynthfinder。
2.2 下載公開模型與 Stock
1# 自動下載 USPTO 模型 + ZINC stock + 生成 config.yml
2download_public_data ./aizynthfinder_data
這會下載以下檔案:
| 檔案 | 用途 | 來源 |
|---|---|---|
uspto_model.onnx | Expansion policy 模型 | Zenodo |
uspto_templates.csv.gz | 反應模板庫 | Zenodo |
uspto_ringbreaker_model.onnx | RingBreaker 模型 | Zenodo |
uspto_ringbreaker_templates.csv.gz | RingBreaker 模板 | Zenodo |
uspto_filter_model.onnx | Filter policy 模型 | Zenodo |
zinc_stock.hdf5 | ZINC 可購買分子庫 | Figshare |
config.yml | 自動生成的設定檔 | — |
2.3 驗證安裝
1# 確認 CLI 工具可用
2aizynthcli -h
3
4# 快速測試(單一 SMILES)
5aizynthcli --config ./aizynthfinder_data/config.yml --smiles "c1ccccc1"
3. 核心概念
3.1 MCTS 搜尋原理
AiZynthFinder 的預設搜尋演算法是 Monte Carlo Tree Search (MCTS),每一輪包含四個步驟:
flowchart TB
A["1. Selection
從 root 走到最有潛力的 leaf node"] --> B["2. Expansion
用 expansion policy (神經網路)
預測可能的反應拆解"]
B --> C["3. Rollout
展開子節點直到 terminal state"]
C --> D["4. Backpropagation
將 reward 回傳更新所有祖先節點"]
D --> A
搜尋終止條件(任一即停):
| 條件 | 預設值 | 設定參數 |
|---|---|---|
| 達到最大迭代次數 | 100 次 | iteration_limit |
| 超過時間限制 | 120 秒 | time_limit |
| 找到第一條解(可選) | False | return_first |
| 所有節點都無法展開 | — | 自動偵測 |
3.2 狀態(State)與解的定義
每個搜尋樹節點包含一個 MctsState,由一組分子組成:
- 已解決(in stock):該分子在商用試劑庫中可以買到
- 可展開(expandable):該分子不在 stock 中,需要進一步拆解
- is_solved = True:所有分子都在 stock 中 → 這條路線是一個完整的合成方案
- is_terminal = True:已解決 或 達到最大合成步驟數(
max_transforms)
3.3 Expansion Policy
Expansion policy 是一個神經網路,輸入分子指紋(molecular fingerprint),輸出在反應模板庫中各模板的機率分佈。選出機率最高的模板後,嘗試將其套用到分子上生成前驅物。
關鍵參數:
| 參數 | 預設值 | 說明 |
|---|---|---|
cutoff_cumulative | 0.995 | 累積機率門檻,超過的模板不考慮 |
cutoff_number | 50 | 最多考慮的模板數量 |
use_rdchiral | True | 使用 RDChiral 套用模板(立體化學正確) |
4. 設定 Buyable Building Block Stock(商用試劑庫)
這是 lead optimization 工作流中最關鍵的設定。Stock 決定了「什麼分子算是可以買到的起始原料」。
4.1 使用預設 ZINC Stock
download_public_data 下載的 zinc_stock.hdf5 包含 ZINC 資料庫中的可購買分子。設定檔中:
1stock:
2 zinc: zinc_stock.hdf5
4.2 使用自訂 Stock(建議用於實際專案)
在藥物化學實作中,你可能想用公司內部的試劑目錄或特定供應商(如 Enamine、Sigma-Aldrich、eMolecules)的 building block 清單。
方法一:從 SMILES 檔案建立 stock
1# 假設你有一份 building_blocks.smi(每行一個 SMILES)
2smiles2stock --files building_blocks.smi --output my_stock.hdf5
方法二:使用 CSV/HDF5 檔案(含價格資訊)
Stock 檔案格式要求:
- HDF5 檔案須有
tablekey,含inchi_key欄位 - CSV 檔案須有
inchi_key欄位 - 可選
price欄位用於價格相關評分
1stock:
2 enamine_bb:
3 type: inchiset
4 path: /path/to/enamine_building_blocks.hdf5
5 sigma:
6 type: inchiset
7 path: /path/to/sigma_stock.csv
方法三:多 stock 來源同時使用
1stock:
2 zinc: zinc_stock.hdf5
3 enamine: enamine_bb.hdf5
4 internal: internal_inventory.csv
在 Python API 或 CLI 中,可以選擇使用哪些 stock:
1finder.stock.select(["zinc", "enamine"]) # 只用這兩個
2finder.stock.select(finder.stock.items) # 使用全部
4.3 Stock Stop Criteria(進階)
可以設定額外的終止條件,讓搜尋更精確:
1stock:
2 zinc: zinc_stock.hdf5
3 stop_criteria:
4 price: 100 # 只接受價格 <= 100 的 building block
5 weight: 250 # 分子量上限
6 counts: # 原子數量限制
7 C: 10
8 N: 4
4.4 Molbloom Filter(快速 stock 查詢)
對於超大 stock(數千萬分子),可以使用 bloom filter 加速查詢:
1stock:
2 large_catalog: /path/to/catalog.bloom
4.5 自訂 Stock 類別(進階)
可以實作自己的 stock 查詢邏輯,例如結合即時 API 查詢供應商庫存:
1from aizynthfinder.context.stock.queries import StockQueryMixin
2
3class VendorAPIStock(StockQueryMixin):
4 def __contains__(self, mol):
5 # 用 InChI key 查詢供應商 API
6 return self._query_vendor_api(mol.inchi_key)
7
8 def price(self, mol):
9 return self._get_price(mol.inchi_key)
5. 控制最大合成步驟數(Tree Depth)
這是你最關心的參數之一:合成路線不要太長。
5.1 max_transforms 參數
max_transforms 控制搜尋樹的最大深度,即允許的最大反應步驟數。預設為 6。
1search:
2 max_transforms: 6 # 預設值,最多 6 步反應
設成較小的值(如 3-4),搜尋會更快且偏好短路線:
1search:
2 max_transforms: 4 # 強制限制:最多 4 步
3 iteration_limit: 200 # 增加迭代次數補償
4 time_limit: 300 # 增加時間限制
5.2 深度如何影響搜尋
flowchart TB
subgraph "max_transforms = 3"
T1["Target"] --> R1["反應 1"]
R1 --> I1["中間體 A"] & I2["中間體 B"]
I1 --> R2["反應 2"]
R2 --> BB1["Building Block 1
✅ in stock"] & BB2["Building Block 2
✅ in stock"]
I2 --> R3["反應 3"]
R3 --> BB3["Building Block 3
✅ in stock"] & BB4["Building Block 4
✅ in stock"]
end
max_transforms = 3:搜尋空間小、速度快,但可能找不到解(如果目標分子很複雜)max_transforms = 6(預設):適合大多數藥物分子max_transforms = 8-10:適合非常複雜的天然物或大環分子,但搜尋時間大幅增加
5.3 實務建議
| 目標分子複雜度 | 建議 max_transforms | 建議 iteration_limit |
|---|---|---|
| 簡單 analog(MW < 400) | 3-4 | 100-200 |
| 標準藥物分子(MW 400-600) | 5-6 | 200-500 |
| 複雜結構(MW > 600 或多環) | 7-8 | 500-1000 |
6. MCTS 搜尋策略如何找最短路線
6.1 State Score — 預設 Reward 函數
MCTS 搜尋的核心是 reward function(回饋函數),決定哪些路線更好。預設的 State Score 計算公式為:
1State Score = 0.95 * FractionInStock + 0.05 * MaxTransformScore
其中:
- FractionInStock:葉節點中在 stock 的分子比例(0~1)。全部在 stock = 1.0
- MaxTransformScore:使用 squash 函數壓縮的深度分數。步驟越少,分數越高
這意味著 State Score 天生偏好短路線:在同樣都是 solved 的路線中,步驟少的路線會得到更高的 MaxTransformScore。
6.2 使用 return_first 快速找到可行路線
如果你只需要確認「能不能合成」而不需要最佳路線:
1search:
2 return_first: true # 找到第一條解就停止
3 max_transforms: 4
6.3 增加迭代以找更多/更好的路線
1search:
2 iteration_limit: 500 # 更多迭代 → 探索更多路線
3 time_limit: 600 # 10 分鐘時間限制
4 max_transforms: 5
6.4 MCTS 探索-利用平衡(C 參數)
1search:
2 algorithm_config:
3 C: 1.4 # UCB 公式中的探索常數
4 # 值越大 → 探索越多新路線
5 # 值越小 → 集中在已知好的路線
7. Route Scoring(路線評分)機制
搜尋完成後,需要對找到的路線進行排序。AiZynthFinder 提供多種評分函數。
7.1 內建評分函數
| Scorer | 名稱 | 偏好 | 說明 |
|---|---|---|---|
| StateScorer | state score | 高分好 | 預設。0.95 * stock比例 + 0.05 * 深度分數 |
| NumberOfReactionsScorer | number of reactions | 低分好 | 反應步驟數(越少越好) |
| NumberOfPrecursorsScorer | number of pre-cursors | 低分好 | 前驅物數量 |
| NumberOfPrecursorsInStockScorer | number of pre-cursors in stock | 高分好 | 在 stock 中的前驅物數量 |
| AverageTemplateOccurrenceScorer | average template occurrence | 高分好 | 模板的平均出現次數(代表反應可靠性) |
| PriceSumScorer | sum of prices | 低分好 | 所有前驅物價格總和 |
| RouteCostScorer | route cost | 低分好 | 含反應成本與產率的路線成本 |
7.2 設定 Post-processing 評分
1post_processing:
2 min_routes: 5
3 max_routes: 25
4 route_scorer: "state score" # 用哪個 scorer 排序
7.3 提取所有 solved 路線
1post_processing:
2 all_routes: true # 提取所有已解決的路線
3 min_routes: 5 # 若無解,fallback 至少取 5 條
4 max_routes: 50 # 上限
7.4 自訂評分函數
1from aizynthfinder.context.scoring.scorers_base import Scorer
2
3class ShortRoutePreferenceScorer(Scorer):
4 """強烈偏好短路線的自訂評分"""
5
6 def __repr__(self):
7 return "short route preference"
8
9 def _score_node(self, node):
10 n_reactions = len(node.actions_to())
11 in_stock_frac = sum(node.state.in_stock_list) / len(node.state.mols)
12 # 反應數越少 bonus 越大
13 return in_stock_frac * (1.0 / max(n_reactions, 1))
14
15 def _score_reaction_tree(self, tree):
16 n_reactions = len(list(tree.reactions()))
17 leaves = list(tree.leafs())
18 in_stock_frac = sum(1 for m in leaves if m in self._config.stock) / len(leaves)
19 return in_stock_frac * (1.0 / max(n_reactions, 1))
8. 實戰:從 SMILES 跑逆合成分析
8.1 CLI 方式(單一分子)
1# 直接輸入 SMILES
2aizynthcli --config config.yml --smiles "CC(=O)Oc1ccccc1C(=O)O"
輸出會包含:
- 搜尋統計(時間、迭代次數、是否找到解)
- Top-scored 路線的統計資訊
trees.json:所有提取的路線(JSON 格式)
8.2 CLI 方式(批次分子)
1# 建立 SMILES 檔案
2echo "CC(=O)Oc1ccccc1C(=O)O
3c1ccc2c(c1)cc1ccc3cccc4ccc2c1c34
4CC(C)NCC(O)c1ccc(O)c(O)c1" > targets.smi
5
6# 批次處理
7aizynthcli --config config.yml --smiles targets.smi --output results.json.gz
8
9# 使用多進程加速
10aizynthcli --config config.yml --smiles targets.smi --nproc 4
8.3 Python API 方式(推薦整合用)
1from aizynthfinder.aizynthfinder import AiZynthFinder
2
3# 1. 初始化
4finder = AiZynthFinder(configfile="config.yml")
5
6# 2. 選擇 stock 和 policy
7finder.stock.select("zinc")
8finder.expansion_policy.select("uspto")
9finder.filter_policy.select("uspto")
10
11# 3. 設定目標分子
12finder.target_smiles = "CC(=O)Oc1ccccc1C(=O)O" # Aspirin
13
14# 4. 執行搜尋
15finder.tree_search(show_progress=True)
16
17# 5. 提取路線
18finder.build_routes()
19stats = finder.extract_statistics()
20
21# 6. 查看結果
22print(f"找到解: {stats['is_solved']}")
23print(f"路線數: {stats['number_of_routes']}")
24print(f"已解決路線數: {stats['number_of_solved_routes']}")
25print(f"搜尋時間: {stats['search_time']:.1f} 秒")
26
27# 7. 檢查 building block 的 stock 資訊
28stock_info = finder.stock_info()
29for smiles, sources in stock_info.items():
30 print(f" {smiles}: {sources}")
31
32# 8. 輸出路線圖片
33for i, tree in enumerate(finder.routes.reaction_trees):
34 tree.to_image().save(f"route_{i:03d}.png")
8.4 Python API — 自訂設定(不用設定檔)
1from aizynthfinder.aizynthfinder import AiZynthFinder
2
3# 用 dict 設定(適合程式化整合)
4config = {
5 "expansion": {
6 "uspto": [
7 "/path/to/uspto_model.onnx",
8 "/path/to/uspto_templates.csv.gz"
9 ]
10 },
11 "filter": {
12 "uspto": "/path/to/uspto_filter_model.onnx"
13 },
14 "stock": {
15 "zinc": "/path/to/zinc_stock.hdf5",
16 "enamine": "/path/to/enamine_bb.hdf5"
17 },
18 "search": {
19 "max_transforms": 4,
20 "iteration_limit": 200,
21 "time_limit": 300
22 }
23}
24
25finder = AiZynthFinder(configdict=config)
26finder.stock.select(["zinc", "enamine"])
27finder.expansion_policy.select("uspto")
28finder.filter_policy.select("uspto")
9. 與 Lead Optimization 工作流的整合
9.1 整合架構
flowchart TB
subgraph "Lead Optimization Pipeline"
A["AI 生成 analog
(SMILES list)"] --> B["過濾器
(ADMET / 藥物性質)"]
B --> C["AiZynthFinder
批次逆合成"]
C --> D["結果篩選"]
D --> E["排序 & 選擇"]
E --> F["送交合成化學家"]
end
subgraph "AiZynthFinder 篩選標準"
D --> G["is_solved = True"]
D --> H["steps <= 4"]
D --> I["所有 BB 在 stock"]
D --> J["route_cost 最低"]
end
9.2 批次處理 + 結果篩選腳本
1import pandas as pd
2from aizynthfinder.aizynthfinder import AiZynthFinder
3from aizynthfinder.reactiontree import ReactionTree
4
5def evaluate_synthesizability(smiles_list, config_path, max_steps=4):
6 """
7 評估一批候選分子的可合成性
8
9 Parameters
10 ----------
11 smiles_list : list of str
12 候選分子的 SMILES
13 config_path : str
14 AiZynthFinder 設定檔路徑
15 max_steps : int
16 可接受的最大合成步驟數
17
18 Returns
19 -------
20 pd.DataFrame
21 含可合成性評估結果的 DataFrame
22 """
23 finder = AiZynthFinder(configfile=config_path)
24 finder.stock.select(finder.stock.items)
25 finder.expansion_policy.select(finder.expansion_policy.items[0])
26 finder.filter_policy.select_all()
27
28 results = []
29 for smi in smiles_list:
30 finder.target_smiles = smi
31 try:
32 finder.prepare_tree()
33 finder.tree_search()
34 finder.build_routes()
35 stats = finder.extract_statistics()
36 stock_info = finder.stock_info()
37
38 # 找出步驟最少的 solved route
39 best_steps = None
40 for tree in finder.routes.reaction_trees:
41 n_steps = len(list(tree.reactions()))
42 leaves = list(tree.leafs())
43 all_in_stock = all(mol in finder.stock for mol in leaves)
44 if all_in_stock and (best_steps is None or n_steps < best_steps):
45 best_steps = n_steps
46
47 results.append({
48 "smiles": smi,
49 "is_solved": stats.get("is_solved", False),
50 "n_routes": stats.get("number_of_solved_routes", 0),
51 "best_steps": best_steps,
52 "synthesizable": (
53 stats.get("is_solved", False)
54 and best_steps is not None
55 and best_steps <= max_steps
56 ),
57 "top_score": stats.get("top_score", 0),
58 "search_time": stats.get("search_time", 0),
59 })
60 except (ValueError, Exception) as e:
61 results.append({
62 "smiles": smi,
63 "is_solved": False,
64 "n_routes": 0,
65 "best_steps": None,
66 "synthesizable": False,
67 "top_score": 0,
68 "search_time": 0,
69 })
70
71 return pd.DataFrame(results)
9.3 推薦的 config.yml(lead optimization 專用)
1# config_lead_opt.yml — 針對 lead optimization 優化的設定
2expansion:
3 uspto:
4 - /path/to/uspto_model.onnx
5 - /path/to/uspto_templates.csv.gz
6
7filter:
8 uspto: /path/to/uspto_filter_model.onnx
9
10stock:
11 zinc: /path/to/zinc_stock.hdf5
12 # 建議加入你實際使用的 building block 供應商
13 # enamine_bb: /path/to/enamine_bb.hdf5
14
15search:
16 algorithm: mcts
17 max_transforms: 4 # 限制最多 4 步,符合 med-chem 實務
18 iteration_limit: 200 # 增加迭代以確保充分搜尋
19 time_limit: 300 # 5 分鐘/分子
20 return_first: false # 找完整搜尋空間
21 algorithm_config:
22 C: 1.4
23 prune_cycles_in_search: true
24 search_rewards:
25 - state score
26
27post_processing:
28 min_routes: 5
29 max_routes: 25
30 all_routes: true # 提取所有 solved 路線
10. 進階功能
10.1 多 Expansion Policy(RingBreaker)
預設的 USPTO 模型對開環/成環反應的處理有限。RingBreaker 模型專門處理環系拆解:
1expansion:
2 uspto:
3 - uspto_model.onnx
4 - uspto_templates.csv.gz
5 ringbreaker:
6 - uspto_ringbreaker_model.onnx
7 - uspto_ringbreaker_templates.csv.gz
8 multi_expansion:
9 type: aizynthfinder.context.policy.MultiExpansionStrategy
10 expansion_strategies: [uspto, ringbreaker]
11 additive_expansion: true
10.2 Retro* 搜尋演算法
MCTS 是啟發式搜尋,不保證找到最優解。Retro* 是一種最優優先搜尋,適合需要保證找到最短路線的場景:
1search:
2 algorithm: aizynthfinder.search.retrostar.search_tree.SearchTree
3 algorithm_config:
4 molecule_cost:
5 cost: aizynthfinder.search.retrostar.cost.RetroStarCost
6 model_path: retrostar_value_model.pickle
7 fingerprint_length: 2048
8 fingerprint_radius: 2
10.3 多目標 MCTS(Multi-Objective)
同時最佳化多個目標(例如 state score + 特定化學鍵斷裂):
1search:
2 break_bonds: [[1, 2], [3, 4]]
3 algorithm_config:
4 search_rewards: ["state score", "broken bonds"]
10.4 Focussed Bond Synthesis
控制哪些化學鍵必須斷開或保留:
1search:
2 break_bonds: [[1, 2]] # 這些鍵必須被斷開
3 freeze_bonds: [[3, 4]] # 這些鍵不可被斷開
4 break_bonds_operator: or # any 或 all
10.5 Route Clustering
自動將相似的路線分群,方便化學家評估:
1aizynthcli --config config.yml --smiles targets.smi --cluster
10.6 AiZynthExpander — 單步反應拆解
不需要完整的搜尋樹,只看目標分子的第一步拆解:
1from aizynthfinder.aizynthfinder import AiZynthExpander
2
3expander = AiZynthExpander(configfile="config.yml")
4expander.expansion_policy.select("uspto")
5reactions = expander.do_expansion(
6 "CC(=O)Oc1ccccc1C(=O)O",
7 return_n=10 # 回傳最多 10 種不同的拆解方式
8)
9
10for rxn_group in reactions:
11 for rxn in rxn_group:
12 reactants = [mol.smiles for mol in rxn.reactants[0]]
13 print(f" 反應: {rxn.smiles}")
14 print(f" 前驅物: {reactants}")
11. 常見問題與疑難排解
Q1: 搜尋找不到 solved route
可能原因與解法:
- Stock 太小 → 加入更多 building block 來源
- max_transforms 太小 → 增加到 6-8
- 分子太複雜 → 增加
iteration_limit和time_limit - 反應模板覆蓋不足 → 加入 RingBreaker 或其他 expansion policy
Q2: 路線步驟太多
- 降低
max_transforms(例如 3-4) - 擴大 stock 覆蓋範圍(更多分子可以直接買到 → 更早停止搜尋)
- 使用
number of reactionsscorer 排序結果
Q3: 如何確認 building block 真的可以買到?
stock_info() 方法回傳每個 building block 的來源資訊:
1stock_info = finder.stock_info()
2for smiles, sources in stock_info.items():
3 if sources:
4 print(f"✓ {smiles} — 來源: {', '.join(sources)}")
5 else:
6 print(f"✗ {smiles} — 不在 stock 中")
Q4: 如何處理大量候選分子(> 1000 個)?
1# 使用多進程
2aizynthcli --config config.yml --smiles all_candidates.smi --nproc 8 --output batch_results.json.gz
Q5: 記憶體不足
- 降低
cutoff_number(模板數上限) - 降低
max_transforms - 使用 Molbloom filter 取代 InMemory stock
最後提醒: AiZynthFinder 的預測結果是計算建議,最終合成路線仍需由合成化學家審核。該工具的價值在於快速篩掉不可合成的候選分子,並為可行的候選提供起始路線方向。
Comments