016

GNINA 教程:深度学习打分如何用于分子对接

GNINA 把深度学习打分带入对接。这篇给出上手流程、参数选择与结果解读的实用指南。

GNINA 在 smina(Vina 的分支)基础上加入了三维卷积神经网络重打分。它的上手门槛比原版 Vina 更低——能直接读 PDB/SDF、能自动定盒子,这两个改进消除了对接流程中最常见的错误来源。

安装与第一次运行

# 最简单:下载预编译的二进制
wget https://github.com/gnina/gnina/releases/latest/download/gnina
chmod +x gnina
./gnina --version

# 或用 Docker
docker pull gnina/gnina

# ---- 第一次运行:用参考配体自动定盒子 ----
./gnina \
  -r receptor.pdb \
  -l ligands.sdf \
  --autobox_ligand crystal_ligand.sdf \
  --autobox_add 4 \
  -o docked.sdf \
  --seed 42 \
  --exhaustiveness 16 \
  --num_modes 5

# 【相对 Vina 的三个便利】:
#   1) 直接读 PDB/SDF —— 【不需要转 PDBQT】
#      → 省掉一个高频出错的环节(见 013)
#   2) --autobox_ligand 自动定盒子
#      → 不用手工填坐标(另一个高频错误)
#   3) 输出直接是 SDF
#      → 可以直接用 RDKit 读
#
# 【必须加 --seed】:搜索有随机性,不固定则结果不可复现

CNN 模式的选择

# --cnn_scoring 有四个选项
#
# none        不用 CNN(等价于 smina)
# rescore     【推荐】用经验打分搜索,CNN 只对最终姿势重打分
# refinement  CNN 参与姿势精修(慢约 10 倍)
# all         全程 CNN(最慢)
#
# 【为什么推荐 rescore】:
#   搜索阶段需要评估大量候选姿势 → 用快的经验打分
#   排序阶段只有几个姿势 → 用准的 CNN
#   → 【性价比最优】

./gnina -r receptor.pdb -l ligands.sdf \
  --autobox_ligand ref.sdf \
  --cnn_scoring rescore \
  --cnn crossdock_default2018 \
  -o out.sdf --seed 42

# 【CNN 模型的选择】:
#   crossdock_default2018       默认,通用
#   dense、general_default2018  其它训练配置
#   → 可以用 --cnn <name> 指定
#   → 【也可以用多个模型集成】:
#     --cnn crossdock_default2018_ensemble
#
# GPU 加速:
#   GNINA 自动使用 GPU(如果可用)
#   → 【CNN 打分在 CPU 上会很慢】

三个分数的正确使用

分数 含义 用法
minimizedAffinity 经验打分(kcal/mol) 越负越好
CNNscore 姿势可信度(0~1) 不是亲和力!
CNNaffinity CNN 预测的亲和力(pK 尺度) 越大越好
# 【最常见的误用】:把 CNNscore 当作活性排序的依据
#   它回答的是「这个姿势像不像真实的结合姿势」,
#   不是「这个分子结合得多强」
#
# 【推荐的组合用法】:

from rdkit import Chem
import pandas as pd

rows = []
for mol in Chem.SDMolSupplier("docked.sdf"):
    if mol is None:
        continue
    rows.append({
        "name": mol.GetProp("_Name"),
        "affinity": float(mol.GetProp("minimizedAffinity")),
        "cnn_score": float(mol.GetProp("CNNscore")),
        "cnn_affinity": float(mol.GetProp("CNNaffinity")),
        "smiles": Chem.MolToSmiles(mol),
    })
df = pd.DataFrame(rows)

# 每个分子只保留最好的姿势
df = df.sort_values("cnn_score", ascending=False)
df = df.drop_duplicates(subset=["name"], keep="first")

# 【第一步:用 CNNscore 过滤姿势不可信的】
df = df[df["cnn_score"] > 0.5]

# 【第二步:在剩余的里按亲和力排序】
df = df.sort_values("cnn_affinity", ascending=False)

# 【第三步:用配体高效性纠正大小偏倚】(见 095)
from rdkit.Chem import Descriptors
df["heavy_atoms"] = df["smiles"].apply(
    lambda s: Descriptors.HeavyAtomCount(Chem.MolFromSmiles(s)))
df["LE"] = -df["affinity"] / df["heavy_atoms"]

print(df.head(20))

使用前的必做步骤:重对接验证

# 【这一步不做,所有结果都不可信】(见 096)
#
# 做法:把共晶配体取出,重新对接回去

./gnina -r receptor.pdb -l crystal_ligand.sdf \
  --autobox_ligand crystal_ligand.sdf --autobox_add 4 \
  --cnn_scoring rescore --seed 42 \
  -o redock.sdf

# 计算 RMSD
from rdkit import Chem
from rdkit.Chem import rdMolAlign

ref = Chem.RemoveHs(Chem.SDMolSupplier("crystal_ligand.sdf")[0])
for i, mol in enumerate(Chem.SDMolSupplier("redock.sdf")):
    if mol is None:
        continue
    rmsd = rdMolAlign.CalcRMS(Chem.RemoveHs(mol), ref)
    print(f"姿势 {i+1}: RMSD = {rmsd:.2f} Å, "
          f"CNNscore = {mol.GetProp('CNNscore')}")

# 【判据】:
#   top-1 RMSD ≤ 2.0 Å  → 通过
#   2~3 Å              → 边缘,谨慎使用
#   > 3 Å              → 【失败,先排查再继续】
#
# 排查方向:
#   - 结构准备(质子化态、缺失原子,见 105)
#   - 盒子是否覆盖了整个口袋
#   - 是否删掉了必要的辅因子/金属/关键水
#   - 配体的立体化学与互变异构体
#
# 【建议】:对该靶点的 3~5 个共晶结构都做,
#   得到该体系上的成功率

常用参数速查

参数 作用 建议
--exhaustiveness 搜索强度 筛选 8~16;精确 32~64
--num_modes 输出姿势数 5~9
--seed 随机种子 必须设置
--cnn_scoring CNN 模式 rescore
--autobox_ligand 参考配体定盒子 强烈推荐
--autobox_add 盒子外扩(Å) 4~8
--minimize 只做局部优化 修正外部姿势
--score_only 只打分不搜索 评估外部姿势
--cpu CPU 线程数 按机器配置

结果的后处理

关键要点

  • 直接读 PDB/SDF + 自动定盒子消除了对接中两个最常见的错误来源;
  • --cnn_scoring rescore 是性价比最优的模式
  • CNNscore 是姿势可信度,不是亲和力——先用它过滤,再按 CNNaffinity 排序;
  • 重对接验证(RMSD ≤ 2 Å)是使用前的必做步骤,不通过则结果不可信。

延伸资源