第3章:分布(Distribution)模型评估方法
评估的整体逻辑
分布模型预测的是药物从血液往组织里跑的程度,说白了就是看药物在体内的“地盘”有多大。我个人习惯把分布模型分成两类:一类预测稳态分布容积(Vss),另一类预测组织-血浆分配系数(Kp)。前者告诉你整体扩散范围,后者帮你细化到每个器官。
评估分布模型,我通常看这三块:
- 外部验证:拿没见过的化合物来测,这是硬标准。
- 内部验证:用训练集本身评估模型稳定性。
- 适用范围:搞清楚你的模型对哪些化合物靠谱。
下面这张图是我做项目时总结的评估框架,你想想看,是不是每步都对应一个实际问题?
外部验证指标
外部验证是我最看重的部分。做模型不是为了在论文里装样子,而是要拿来预测新化合物的。
实际应用中,常见的回归指标有这些:
| 指标 | 公式 / 说明 | 我关心的阈值 |
|---|---|---|
| RMSE | \(\sqrt{\frac{\sum_{i=1}^{n}(\hat{y}_i - y_i)^2}{n}}\) | <0.5 log 单位(Vss常用) |
| MAE | \(\frac{\sum_{i=1}^{n}|\hat{y}_i - y_i|}{n}\) | <0.3 log 单位 |
| R² | 决定系数 | >0.6(分布模型很难>0.8) |
| Q²_F | 外部预测决定系数 | >0.5 说明有预测能力 |
| Geometric Mean Fold Error | 预测/实测比值的几何均值 | 1.5-2.0以内可以接受 |
举个例子:我做过一个Vss预测模型,测试集有45个化合物。RMSE在0.42 log单位,R²是0.58。当时有同事嫌R²低,但你看分布模型的实验测定误差本身就很大(同一化合物不同实验室能差2倍),所以0.58实际是个不错的数。后来这个模型帮我们筛选了300多个先导化合物中哪些容易在组织中滞留。
我的使用习惯:我不会单独看RMSE或R²,而是把散点图一块儿看。有时候R²挺高,但斜率和截距跑偏了,那模型也是有问题的。我一般会跑一次这个检查:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error, r2_score
y_true = [1.2, 2.5, 3.1, ...] # 实际logVss
y_pred = [1.0, 2.7, 2.9, ...] # 预测logVss
rmse = np.sqrt(mean_squared_error(y_true, y_pred))
r2 = r2_score(y_true, y_pred)
mae = np.mean(np.abs(np.array(y_true) - np.array(y_pred)))
print(f"RMSE: {rmse:.2f}")
print(f"R²: {r2:.2f}")
print(f"MAE: {mae:.2f}")
# 再计算几何平均折留误差
fold_error = np.mean(np.abs(10 ** (y_true - y_pred) - 1))
print(f"Geometric Mean Fold Error: {fold_error:.2f}")
内部验证指标
内部验证说白了就是“自己考自己”,看看模型在训练集上是不是过拟合了。很多新手模型外部验证一塌糊涂,内部验证却漂亮得很,这就是问题。
留一法交叉验证(Q²-LOO)
这个做法我挺喜欢的,虽然计算量大。每次拿一个样本出来当测试,其余训练,最后算预测值和真实值的Q²。我在项目中遇到过好几次:模型在训练集上R²>0.9,但Q²-LOO只有0.3。这就说明模型在背答案,不是真学到了东西。
避坑指南:我曾经犯过一个错。用留一法评估一个分布模型,Q²看起来不错(0.72),可一遇到外部数据就掉到0.2。后来发现——训练集里有很多相似结构的化合物,留一法测不出“族外”预测能力。所以现在我一定会做“族留出交叉验证”:把同一骨架的化合物整个移除,这才算数。
Y随机化测试
这个测试特简单:把训练集里的实测值打乱,然后用原来的特征重新建模。如果打乱后还能得到高R²或高Q²,那你的模型基本就是瞎猫碰死老鼠了。
我在报告里一般这样展示:
Randomization results:
Original model: R² = 0.74, Q²_LOO = 0.53
- Random run 1: R² = 0.12, Q²_LOO = -0.18
- Random run 2: R² = 0.08, Q²_LOO = -0.21
- Random run 3: R² = 0.15, Q²_LOO = -0.14
- Random run 4: R² = 0.05, Q²_LOO = -0.30
- Random run 5: R² = 0.10, Q²_LOO = -0.19
cR²Y = 0.85 (cut-off > 0.5 通过)
你看,打乱之后R²和Q²全掉到0附近甚至负值,说明模型没有误用随机相关性。
适用范围(Applicability Domain)
这个我觉得是分布模型里最容易忽略的一块。模型不是万能的,它只在训练集覆盖的化学空间里才有效。你拿一个与训练集结构完全不同的新化合物去预测,结果基本就是掷骰子。
适用范围的核心方法是Williams图
- 横轴:杠杆值(h)——表示化合物在化学空间里的“奇怪程度”。
- 纵轴:标准化残差——预测偏差有多大。
- 阈值线:h* = 3(k+1)/n,其中k是变量个数,n是训练集样本数。
实际解读规则:
- 落在 h < h* 且 |残差| < 3 的化合物 → 安全区域,预测可信。
- 落在 h > h* 且残差小的化合物 → 结构离群但预测还好,要谨慎。
- 落在 h > h* 且残差大的化合物 → **不可信,不能用**。
生成Williams图的代码可以这样写:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.covariance import MinCovDet
# 假设X_train是训练集的分子描述符矩阵
mcd = MinCovDet().fit(X_train)
# 计算马氏距离,再转成杠杆值
mahalanobis = mcd.mahalanobis(X_train)
n_train, k = X_train.shape
h = mahalanobis / (n_train - 1)
h_star = 3 * (k + 1) / n_train # 阈值
# 假设标准化残差已经计算好存在std_residuals中
plt.figure(figsize=(6,5))
plt.scatter(h, std_residuals, c='#3498db', edgecolors='#2980b9')
plt.axhline(y=3, color='#e74c3c', linestyle='--', label='±3 σ')
plt.axhline(y=-3, color='#e74c3c', linestyle='--')
plt.axvline(x=h_star, color='#f39c12', linestyle='--', label=f'h* = {h_star:.3f}')
plt.xlabel('Leverage (h)')
plt.ylabel('Standardized Residuals')
plt.title('Williams Plot - Vss Model AD')
plt.legend()
plt.tight_layout()
plt.show()
分布模型评估的可接受标准
你想想看,不同场景下对模型的要求是不一样的。我给一个参考基准表:
| 用途 | RMSE (log单位) | R² | 适用范围覆盖率 |
|---|---|---|---|
| 早期先导筛选(高通量) | < 0.7 | > 0.4 | > 70% 待测化合物落在AD内 |
| 先导优化(中等通量) | < 0.5 | > 0.5 | > 80% |
| 候选化合物申报支撑 | < 0.3 | > 0.7 | > 90% + 全部进行外部实验验证 |
⚠ 注意:R²在分布模型里很少超过0.8。如果你看到一个分布模型的R² > 0.9,赶紧检查:
- 是不是训练集有重复或高度相似化合物?
- 是不是用了太多描述符(过拟合)?
- 是不是测试集太小或者分布太窄?
我曾经见过一个团队号称Vss预测R²达到0.95,后来发现是只用了15个化合物做测试,而且都是脂肪胺类。这种模型放到真实项目里根本没法用。
总结一下我的评估策略
嗯,写到这里,我习惯的做法是这样的:
- 先跑外部验证,看RMSE和Q²_F是否达标,同时画散点图检查系统误差。
- 再做内部验证,用留一法或5折交叉验证,看与外部指标是否一致。
- Y随机化必须有,如果打乱后R²还>0.2,这个模型基本可以扔了。
- 画Williams图,明确哪些新化合物可以放心用,哪些不行。
- 最终决策:外部+R²和Q²不低于0.5,AD覆盖率不低于70%,才敢拿出去给项目组用。
一点个人经验:我最开始做分布模型时总觉得外部验证和内部验证分开处理就行。后来发现最大的坑不是模型的精度不够,而是适用范围没画清楚。项目组拿着模型乱预测,出了问题全怪模型。所以我现在写任何分布模型评估报告,第一页就贴Williams图,告诉所有人:红线外面的化合物,我不会给预测值。