主动学习的核心思想:不是随机做实验,而是让模型指出「做哪个实验最能减少不确定性」。在实验成本高、周期长的药物发现中,这个思路的价值很大——但它对模型的不确定性估计质量有很高要求。
基本循环
# 1) 用已有数据训练模型
# 2) 用模型评估所有候选分子
# 3) 【用采集函数选出下一批要测试的分子】
# 4) 做实验,得到真实数据
# 5) 把新数据加入训练集,回到 1)
#
# 关键在第 3 步:怎么选
#
# 【核心权衡:探索 vs 利用】
# 利用(exploitation):选模型预测最好的
# → 短期收益高,但可能陷在局部
# 探索(exploration):选模型最不确定的
# → 改善模型,但当前批次的命中率可能低
#
# 在药物发现中,这个权衡有实际含义:
# 利用 = 「这一轮就要拿到活性分子」
# 探索 = 「让模型变好,为后续几轮服务」
# → 【项目早期偏探索,后期偏利用】
常用的采集函数
| 采集函数 | 选择标准 | 倾向 |
|---|---|---|
| 贪心 | 预测值最高 | 纯利用 |
| 不确定性采样 | 预测方差最大 | 纯探索 |
| UCB | μ + κσ | 可调平衡,简单可靠 |
| 期望改进(EI) | 超过当前最好值的期望 | 自适应平衡 |
| 改进概率(PI) | 超过阈值的概率 | 偏利用 |
| Thompson 采样 | 从后验采样后取最优 | 天然平衡,易并行 |
| 多样性加权 | + 与已选分子的差异 | 批量场景必需 |
批量选择:实际场景的关键
# 实验通常是【成批】做的(一次 96 孔板、一批合成)
# 而多数采集函数是为「一次选一个」设计的
#
# 【朴素做法的问题】:
# 直接选采集函数得分最高的 96 个
# → 它们可能高度相似(都在同一个区域)
# → 【实际获得的信息远少于 96 个独立实验】
#
# 正确做法:
import numpy as np
from rdkit import Chem, DataStructs
from rdkit.Chem import rdFingerprintGenerator
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
def batch_select(candidates_smiles, mu, sigma, batch_size=96,
kappa=2.0, diversity_weight=0.5):
"""UCB + 贪心多样性的批量选择"""
fps = []
for s in candidates_smiles:
m = Chem.MolFromSmiles(s)
fps.append(gen.GetFingerprint(m) if m else None)
ucb = mu + kappa * sigma
selected = []
remaining = set(range(len(candidates_smiles)))
while len(selected) < batch_size and remaining:
best_i, best_score = None, -np.inf
for i in remaining:
if fps[i] is None:
continue
# 与已选分子的最大相似度(越小越多样)
if selected:
sims = DataStructs.BulkTanimotoSimilarity(
fps[i], [fps[j] for j in selected])
penalty = max(sims)
else:
penalty = 0.0
score = ucb[i] - diversity_weight * penalty
if score > best_score:
best_score, best_i = score, i
if best_i is None:
break
selected.append(best_i)
remaining.discard(best_i)
return selected
# 其它批量策略:
# - Thompson 采样:每个槽位独立从后验采样
# → 天然产生多样性,且易并行
# - 局部惩罚:选中一个后,降低其邻域的采集函数值
# - 确定性点过程(DPP):显式建模多样性
# - 【聚类后每簇选一个】:简单有效
成败取决于不确定性估计的质量
- 这是主动学习最关键的前提:如果模型的不确定性估计不准,采集函数就是在瞎选(见 166《不确定性估计》);
- 常见问题:神经网络往往过度自信——在自己完全不懂的区域也给出低不确定性;
- 推荐的做法:
- 集成方法(多个种子训练,用预测方差)——简单可靠,实践中效果最稳定;
- 高斯过程——不确定性有理论保证,但难以处理大数据;
- MC Dropout——便宜但校准往往不好;
- 加上适用域判断(与训练集的相似度)作为额外的保险。
- 必须验证校准:在开始主动学习前,先检查「模型说不确定的样本,是不是真的错得更多」——这个检查不做,整个循环可能是无效的。
实际约束
# 主动学习的理论很美,实际约束很多:
#
# 1) 【合成可行性】
# 模型选出的分子可能根本合成不了
# → 【候选池应该是「可获得的分子」】:
# 商业可购买的库、
# 内部化合物库、
# 或有明确合成路线的分子
# → 这个约束常常被忽略,导致选出的分子无法测试
#
# 2) 【实验周期】
# 一轮实验可能要 2~8 周
# → 主动学习的轮数有限(一个项目可能只有 3~10 轮)
# → 【每轮的批量要足够大】
#
# 3) 【实验成本与批量的经济性】
# 有时一次做 96 个与做 20 个成本差不多
# → 批量大小由实验经济性决定,而非算法
#
# 4) 【化学家的意见】
# 模型选的分子,化学家可能认为没有价值
# → 【应该把模型建议与专家判断结合】
# → 实践中常见的做法:
# 模型选 200 个 → 化学家从中挑 96 个
#
# 5) 【多目标】
# 实际不只优化活性,还有选择性、ADMET
# → 需要多目标的采集函数
# → 或用帕累托前沿的思路
#
# 6) 【冷启动】
# 第一轮没有数据,模型无法工作
# → 用多样性采样(覆盖化学空间)
# → 或用文献数据做初始模型
评估主动学习的效果
# 【必须与随机基线比较】
#
# 回溯模拟(retrospective simulation):
# 用已完成项目的全部数据,
# 模拟「如果当初用主动学习会怎样」
#
# 做法:
# 1) 随机取 5% 作为初始训练集
# 2) 用主动学习策略选下一批
# 3) 「揭晓」这批的真实标签(数据已有)
# 4) 重复
# 5) 与「随机选择」对比
#
# 指标:
# - 达到某个性能所需的样本数
# - 前 N 轮累计发现的活性分子数
# - 模型性能随轮次的提升曲线
#
# 【重复多次取平均】—— 单次模拟的方差很大
def simulate_active_learning(X, y, strategy, n_init=50, n_batch=32,
n_rounds=10, n_repeats=10):
import numpy as np
results = []
for rep in range(n_repeats):
rng = np.random.default_rng(rep)
train_idx = list(rng.choice(len(X), n_init, replace=False))
pool = [i for i in range(len(X)) if i not in set(train_idx)]
found = []
for r in range(n_rounds):
model = fit(X[train_idx], y[train_idx])
mu, sigma = model.predict_with_uncertainty(X[pool])
picks = strategy(mu, sigma, n_batch)
chosen = [pool[i] for i in picks]
found.append(sum(y[i] >= ACTIVE_THRESHOLD for i in chosen))
train_idx += chosen
pool = [i for i in pool if i not in set(chosen)]
results.append(np.cumsum(found))
return np.array(results).mean(axis=0)
# 【与 random strategy 对比,差距才是主动学习的价值】
关键要点
- 不确定性估计的质量决定成败——开始前必须验证「模型说不确定的,是否真的错得更多」;
- 批量选择必须加多样性约束,否则选出的 96 个高度相似,信息量远小于 96 个独立实验;
- 候选池必须是「可获得的分子」——选出合成不了的分子等于白做;
- 用回溯模拟评估,必须与随机选择基线比较并重复多次取平均。
延伸资源
- 不确定性估计:166《不确定性估计》;模型外推性:167《模型外推性》;
- 活性预测:155《AI 活性预测模型》;DMTA 循环:405《Lead Optimization》。