计算预测与实验结果之间总有差距。问题不是「AI 准不准」,而是「在什么情况下、对什么问题、可以信到什么程度」——这个判断能力比模型本身更重要。
决定可信度的四个因素
| 因素 | 可信度高 | 可信度低 |
|---|---|---|
| 是否在适用域内 | 与训练集化学空间相近 | 新骨架、新化学型 |
| 预测任务的性质 | 体外单一机制终点 | 体内综合参数 |
| 数据量与质量 | 数千条标准化数据 | 几百条、来源混杂 |
| 用途 | 排序、优先级 | 绝对数值、决策依据 |
按任务类型的可信度
| 预测任务 | 典型可信度 | 合理用途 |
|---|---|---|
| 分子量、TPSA 等计算描述符 | 确定(是计算值不是预测) | 直接使用 |
| logP | 中(误差常 > 1 log) | 排序参考 |
| 溶解度 | 中(RMSE 约 0.7~1.0 log) | 粗筛 |
| hERG、CYP 抑制、Ames | 较高(体外单机制) | 风险预警 |
| 微粒体清除率 | 中 | 排序 |
| 体内 PK 参数 | 低 | 仅作量级参考 |
| 活性(同系列内) | 中高 | 排序、指导设计 |
| 活性(跨骨架) | 低 | 不可靠 |
| 对接姿势 | 中(有共晶验证时较高) | 假设生成 |
| 对接分数排序 | 低 | 粗筛,不作定量 |
| FEP 相对结合自由能 | 较高(1~1.5 kcal/mol) | 类似物排序 |
| 临床疗效 | 极低 | 不应作为依据 |
一条通用规律:预测链条越长、涉及的生物学层次越高,可信度越低。物理化学性质 > 体外单机制 > 体内综合参数 > 临床结果。
适用域判断:最实用的单一工具
import numpy as np
from rdkit import Chem, DataStructs
from rdkit.Chem import rdFingerprintGenerator
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
def applicability_domain(query_smiles, train_smiles, k=5):
"""用与训练集的相似性判断是否在适用域内"""
q = Chem.MolFromSmiles(query_smiles)
if q is None:
return None
q_fp = gen.GetFingerprint(q)
train_fps = []
for s in train_smiles:
m = Chem.MolFromSmiles(s)
if m:
train_fps.append(gen.GetFingerprint(m))
sims = DataStructs.BulkTanimotoSimilarity(q_fp, train_fps)
topk = sorted(sims, reverse=True)[:k]
avg = float(np.mean(topk))
if avg >= 0.6:
level = "高(与训练集相近)"
elif avg >= 0.4:
level = "中(需谨慎)"
else:
level = "低(分布外,预测不可信)"
return {"max_similarity": float(max(sims)),
"avg_top_k": avg, "confidence": level}
# 实践建议:
# 把这个检查内建到所有预测流程中,
# 预测结果永远与适用域标签一起呈现
不确定性估计
# 方法一:集成方差(最常用)
def ensemble_uncertainty(models, X):
preds = np.stack([m.predict(X) for m in models])
return preds.mean(axis=0), preds.std(axis=0)
# 方法二:分位数回归 —— 直接预测区间
# 方法三:共形预测(conformal prediction)
# 给出有覆盖率保证的预测区间
# 优点:理论保证,不依赖模型假设
#
# from nonconformist.cp import IcpRegressor
# 关键:不确定性估计本身也需要验证!
# 检查「不确定性」与「实际误差」是否相关
def validate_uncertainty(pred, true, uncertainty):
errors = np.abs(pred - true)
corr = np.corrcoef(uncertainty, errors)[0, 1]
print(f"不确定性与误差的相关系数: {corr:.3f}")
# > 0.3 → 不确定性有意义,可用于过滤
# ~ 0 → 不确定性无意义,不要用它做决策
# 分箱检查校准
bins = np.quantile(uncertainty, [0, 0.25, 0.5, 0.75, 1.0])
for i in range(len(bins) - 1):
mask = (uncertainty >= bins[i]) & (uncertainty < bins[i+1])
if mask.sum() > 0:
print(f"不确定性 {bins[i]:.2f}~{bins[i+1]:.2f}: "
f"平均误差 {errors[mask].mean():.3f}")
# 好的不确定性估计:误差应随不确定性单调上升
持续校准:模型可信度的唯一真实来源
# 每轮实验后,把预测与实测对比
def prospective_validation(predictions_df, results_df, assay):
"""前瞻性验证 —— 这才是真正的检验"""
merged = predictions_df.merge(results_df, on="compound_id")
err = merged["predicted"] - merged["measured"]
report = {
"n": len(merged),
"rmse": float(np.sqrt((err ** 2).mean())),
"mae": float(err.abs().mean()),
"bias": float(err.mean()), # 系统性高估/低估
"spearman": float(merged["predicted"].corr(
merged["measured"], method="spearman")),
}
# 判读
if report["spearman"] > 0.5:
report["verdict"] = "可用于排序"
elif report["spearman"] > 0.3:
report["verdict"] = "弱参考"
else:
report["verdict"] = "不可用,应停止依赖"
if abs(report["bias"]) > 0.3:
report["note"] = "存在系统性偏差,需要校准"
return report
# 关键:
# 交叉验证的指标是「历史数据上的表现」
# 前瞻性验证才是「真实的预测能力」
# 两者常有明显差距
务实的使用原则
- 用于排序而非定量:绝大多数模型的排序能力好于绝对精度;
- 用于过滤而非选择:模型擅长排除明显不行的,不擅长挑出最好的;
- 预测触发实验而非替代实验:模型提示高风险 → 优先安排实测确认,而不是直接淘汰;
- 永远与适用域一起呈现:孤立的预测值容易被过度信任;
- 区分预测值与实测值:决策表中必须清楚标注;
- 多方法交叉验证:不同原理的方法给出一致结论,可信度大增(如对接 + FEP + 相互作用指纹);
- 建立反馈闭环:持续用新实验数据校准模型,这是模型可信度的唯一真实来源。
怎么向团队传达预测结果
# 不好的传达:
# 「模型预测这个化合物的 hERG IC50 是 8.2 μM」
# → 听起来像精确的实验值
# 好的传达:
# 「模型预测 hERG IC50 约 5~15 μM(中等不确定性)。
# 该化合物与训练集的相似性中等,预测可信度一般。
# 该模型在我们过去 40 个化合物上的前瞻验证 Spearman 为 0.55,
# 适合用于排序但不适合作为绝对判断。
# 建议:优先安排实测确认。」
#
# 差别在于:
# 给出区间而非点值
# 说明适用域
# 给出模型的历史表现
# 明确建议下一步动作
关键要点
- 预测链条越长、生物学层次越高,可信度越低;
- 适用域检查是最实用的单一工具,应内建到所有预测流程;
- 不确定性估计本身也要验证——与实际误差不相关的不确定性没有意义;
- 前瞻性校准是模型可信度的唯一真实来源,交叉验证指标会高估。
延伸资源
- 概率思维:416《决策中的概率思维》;MPO:415《化合物优先级排序》;
- 可信 AI:420《可信 AI Trustworthy AI》;评测陷阱:169《Benchmark 陷阱》。