简介《Machine Learning in Chemistry now and in the future》是一份英文幻灯片式PDF面向化学、药物研发与材料方向的学习者也适合准备组会分享或课程补充的读者用来快速建立机器学习与化学交叉研究的整体框架。文件仅1个PDF压缩包约41.44MB按“引言—案例—化学中的机器学习—结论”组织页面紧凑便于浏览和摘取要点。内容从算法进步、大数据与高性能计算背景讲起结合CASP13蛋白质折叠预测中AlphaFold的表现延伸到AstraZeneca在药物发现及早期临床候选物验证中的多目标设计实践并讨论分子结构预测、材料设计、反应预测和数据驱动实验设计等应用与趋势。对药物化学、材料化学及关注AI for Science的读者较友好可帮助梳理关键术语、典型案例与可能的研究切入点。已有222人学习可作为进入该主题前的概览材料。1. 从一场盲测赛说起机器学习在化学里最先啃下的是“贵的物理”机器学习在化学里最反直觉的一点是它最先攻克的不是最难的物理而是最贵的物理。2018 年一场蛋白质结构预测盲测赛上一支首次参赛的队伍在最难的“无同源模板”组别里拿下 43 项测试中的绝大部分第二名只捡到零星几项两年后赛事重开它又赢了一遍赛事发起人之一公开表示这个问题在某种意义上已经解决。真正震动化学圈的不是“机器赢了”而是赢法它没有去解薛定谔方程而是从已有结构里学出了一个从序列直通三维坐标的映射。这件事之所以值得掰开讲是因为它揭示了一条在化学里反复复现的路径当一个物理量算得起但算得慢、实验能做但做得贵时模型就有了插进去的位置。这条路子为什么成立、边界在哪、什么时候会翻车得从化学空间的尺度和三层时间尺度讲起。2. 10^60 与三层时间尺度模型到底插在化学流程的哪一段一份讲机器学习与化学的讲义里给了个刺眼的估计可合成的小有机分子数量在 10^60 量级。这个数字大到没有直觉换个说法——如果把每个分子写在一张纸上宇宙里的原子数都不够当纸用。所有还没被发现的药物、催化剂、功能材料都散在这片空间里而人类合成过的化合物总数连它的零头都算不上。问题于是从“搜索”变成了“排序”既然穷举不可能能不能用便宜的手段先排个序把昂贵的实验和计算省给最有希望的那一小撮2.1 三层时间尺度实验、量化计算、模型推理要理解模型插在哪一段先得把化学研发里的“算一次要多久”摆成一条时间轴。湿实验一轮下来从设计、合成、纯化到测活几天到几周是常态还不算失败重做的部分量子化学计算比如密度泛函对一个中等大小的分子做几何优化加能量小时级是正常量级做构象搜索或者反应路径时间还要乘上几个数量级而一个训练好的模型在同样的输入上给出一个预测值通常是毫秒到秒。这三层之间的差距不是线性的是四到五个数量级。经典的做法是在第一层和第二层之间反复横跳算一批、挑几个、做实验、再算一批。麻烦在于每一跳都很贵而在 10^60 的空间里跳几百次连局部地形都摸不清。模型的价值就在于它把“算一次”的成本压到了第三层于是同样的预算可以覆盖多得多的候选点。代价也很明确——模型给的是插值出来的估计不是物理真相一旦走出训练分布它会自信地给你一个错得离谱的数。讲义里那组箭头写得很直白从结构到性质可以走实验可以走HΨ EΨ的求解也可以走模型。三条路通向同一个终点区别只在成本、精度和适用边界。2.2 代理模型与主动学习把闭环闭合起来模型替代昂贵计算的这套做法常见的叫法是代理模型surrogate model。它的关键不在于“训一个模型”而在于把闭环闭合起来用少量高保真标签训出第一版用它去给整个候选池排序挑出最值得花昂贵代价的那一批把新标签补回去再训。这就是主动学习或贝叶斯优化的骨架区别只在采集函数怎么写。一个最小可跑的实现大致是这样import numpy as np from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, WhiteKernel def propose_batch(model, candidates, beta1.96, k8): 按采集函数挑下一批候选点。 beta 控制探索强度越大越偏向模型不确定的区域 k 是批大小受限于每轮能承担的昂贵计算次数。 mu, sigma model.predict(candidates, return_stdTrue) acquisition mu - beta * sigma # 目标是最小化能量时均值项取负号 return np.argsort(acquisition)[:k] def active_loop(oracle, pool, n_init20, rounds10, k8): oracle 代表昂贵的量化计算或湿实验每次调用都有真实成本。 rng np.random.default_rng(0) idx rng.choice(len(pool), n_init, replaceFalse) X, y pool[idx], oracle(pool[idx]) # 冷启动先随机采一批建立基线 kernel RBF(length_scale1.0) WhiteKernel(noise_level1e-3) model GaussianProcessRegressor(kernelkernel, normalize_yTrue) for _ in range(rounds): model.fit(X, y) rest np.delete(pool, idx, axis0) # 已标注的点不再重复问 oracle pick propose_batch(model, rest, kk) new_X rest[pick] new_y oracle(new_X) # 每轮只花 k 次昂贵调用 X np.vstack([X, new_X]) y np.append(y, new_y) idx np.concatenate([idx, pick]) model.fit(X, y) return model, X, y这段代码里真正决定成败的不是模型结构而是三个参数背后的取舍。beta是探索与利用的旋钮调小模型会围着已知的好区域反复采样收敛快但容易陷在局部调大它会往高方差区域跑可能浪费预算在明显很差的分子上。k是批大小受实验通量限制——化学实验室一次能并行做几个反应是硬约束k设成 64 而实验室一周只能做 8 个闭环就断在排队上。oracle的成本决定了整个循环的规模量化计算能以小时计批量跑湿实验就得按周算同样的算法框架两边的n_init和rounds完全不是一个量级。还有一个容易被忽略的点propose_batch挑出的是一批点但高斯过程对这批点的预测是彼此独立的。真实场景里一批候选往往高度相似同一骨架的不同取代基批量提交易退化成“只问了一个点”。常见的补救是先按分子指纹做聚类每簇最多取一个或者用贪心的方式逐点加入并对已选点做虚拟观测更新。3. 分子怎么变成张量三条表示路线与它们各自的失效面模型再花哨也得先吃进去一个张量。化学里的分子有三种主流喂法分别对应不同的归纳偏置也分别对应着不同的失效场景选错了表示后面调参调到天亮也救不回来。3.1 字符串路线SMILES 与序列模型的天然接口SMILES 把分子写成一串字符比如苯环写成一个c1ccccc1这就让自然语言处理里的那套东西可以直接搬过来。序列模型的注意力机制天生能吃变长输入化学里也确实出现了把分子当句子读的做法反应预测、逆合成设计这些“输入输出都是字符串”的任务收益最明显。但字符串有个坑同一个分子可以有多种合法写法从哪个原子开始编号、按什么顺序遍历这是典型的表示不唯一问题。模型如果只在一种写法上训练换一种写法它可能就认不出是同一个东西。工程上的做法是训练时做随机化增强把同一个分子的多种枚举写法都喂进去让模型学会对书写顺序不变。import re # 原子级切分双字母元素必须整体匹配否则 Si 会被切成 S 和 i TOKEN re.compile(r(\[[^\]]\]|Br|Cl|Si|Se|[BCNOPSFIbcnops]|\d|\(|\)||#|-|\.)) def tokenize(smiles: str) - list: toks TOKEN.findall(smiles) assert .join(toks) smiles, f切分丢失字符: {smiles} return toks那个assert不是装饰是必须留的。正则漏掉某个符号时切分结果会静默地少一截模型照样训练、loss 照样下降只是它学的是一个被截断的分子。常见的实现思路是要求每个 tokenizer 都自带一个“拼回去等于原串”的自检批量预处理时跑一遍比事后查指标异常靠谱得多。3.2 图路线消息传递与分子的天然同构分子本来就是图原子是节点键是边。所以图神经网络和化学的匹配度是最高的图卷积那一类方法最初就是在分子性质预测上验证的。它的核心是消息传递——每个原子收集邻居的特征聚合更新自己叠几层之后每个节点的表示就编码了其若干跳邻域的结构信息。import numpy as np def mol_to_graph(mol, atom_dim64): 把 RDKit 分子对象转成邻接矩阵 节点特征。 注意元素到索引的映射必须用固定字典不能用取模否则不同元素会撞车。 n mol.GetNumAtoms() A np.zeros((n, n), dtypenp.float32) for bond in mol.GetBonds(): i, j bond.GetBeginAtomIdx(), bond.GetEndAtomIdx() A[i, j] A[j, i] 1.0 # 第一版先忽略键级后续可按单双叁芳拆成多通道 X np.zeros((n, atom_dim), dtypenp.float32) for i, atom in enumerate(mol.GetAtoms()): X[i, atom.GetIdx() % atom_dim] 1.0 # 仅示意实际应查固定元素表 X[i, -1] atom.GetDegree() # 度数是廉价但有效的拓扑特征 X[i, -2] atom.GetFormalCharge() return A, X这段里最值得说的是注释提到的那件事元素到 one-hot 索引的映射必须全局固定。用GetAtomicNum() % atom_dim这类写法在 demo 里能跑在真实数据上会让原子序数相差 64 的两个元素共用同一个通道模型学到的就是一堆混淆的化学语义。同理邻接矩阵只记 0/1 会丢掉键级信息而键级对共轭、芳香性、反应性判断都关键实际实现里通常把邻接拆成单键、双键、三键、芳香四个通道。3.3 表示决定边界三种路线什么时候会翻车把这三条路线摆在一起看会发现它们各自擅长的事情非常不同。字符串路线吃的是“序列上的局部模式”对反应中心和官能团转化敏感但对三维构象几乎无感图路线吃的是拓扑连接对同分异构体区分得很干净但对立体化学、对构象依赖的性质比如某些结合亲和力表达力不够卷积那一路是从图像领域借过来的把分子投影成网格或者把电子密度当图像处理优势在于保留空间连续性代价是失去了原子的离散身份需要额外的对齐和分辨率选择。这就是典型的没有免费的午餐表示本身就是一种先验假设你假设了什么不变、什么重要模型就只能在这些假设下工作。做性质预测时如果发现模型对立体异构体完全无感别急着换模型结构先回头看看输入里到底有没有手性信息。4. 多目标设计为什么药化问题从来不是单目标回归讲义里的案例章节落在了药物化学的多目标设计上。这个选择的用意很明确药物发现是化学里最典型的“多个指标同时要”的场景一个候选分子要同时满足活性、选择性、溶解度、渗透性、代谢稳定性、安全性等一系列约束而其中任何一项不达标整条路线就废了。4.1 单目标回归的天花板如果只训一个“活性预测模型”你会得到一个在测试集上 R² 很漂亮的模型然后发现它在项目里几乎没用。原因很朴素高活性的分子往往溶解度差、代谢快、毒性高模型对着活性一个维度优化生成的候选分子会精准地落在那些“活性极高但什么都不能做”的区域。这不是模型不准是目标函数写错了。化学里的真实决策是一个带约束的多目标优化问题在可合成、可溶、无毒的前提下把活性推到尽可能高。用单目标损失去逼近它等于把所有约束都折进一个标量里权重怎么定全凭手感换一个项目就得重调一遍。4.2 帕累托支配与标量化损失处理多目标的标准工具是帕累托前沿。一个解支配另一个解当且仅当它在所有目标上都不差、且至少一个目标更好不被任何解支配的点构成的集合就是前沿它们代表了当前无法再同时改进的取舍边界。def dominates(a, b): a 支配 b所有目标不劣且至少一个严格更优目标统一为越大越好。 return all(x y for x, y in zip(a, b)) and any(x y for x, y in zip(a, b)) def pareto_front(points): O(n^2) 的朴素实现候选池上万时应该换快速非支配排序。 front [] for p in points: if not any(dominates(q, p) for q in points if q is not p): front.append(p) return front这个函数简单到不像话但它改变了整个优化流程的方向。用单目标模型时你的输出是一个数决策是“取最大的那个”用帕累托前沿时输出是一组点决策变成“在这几个取舍方案里选一个”。后者才符合化学项目的真实运作方式——项目组会议上讨论的从来不是“哪个分子活性最高”而是“愿不愿意为了溶解性牺牲两个单位的活性”。代价是工程复杂度上去了。前沿上的点数量随目标数增长很快五个目标的真实问题里非支配解可能占候选池的一大半前沿本身失去了筛选意义。常见的做法是先降到两三个关键目标上看前沿其余目标改成硬约束比如 LogP 必须落在某个区间把问题重新压回可决策的维度。另一个绕不开的点是模型的不确定性。前沿是用模型预测值算出来的而预测值有误差误差大的点可能只是“看起来好”。稳妥的做法是把不确定性也作为一个目标维度推进去或者在采集阶段对高方差区域降权否则优化器会系统性地偏向那些模型最不熟悉、预测最离谱的分子——这个现象在多目标场景里比单目标更容易失控因为目标一多总有一个维度能被“幸运地”预测得很高。5. 踩坑排查那些指标漂亮但项目失败的常见原因5.1 数据侧切分方式决定你看到的是不是幻觉现象模型在验证集上 R² 到 0.9换到项目里的新化合物上预测全线崩盘相关性接近 0。原因随机切分让同一骨架的类似物同时出现在训练集和测试集里模型实际上是在做近邻检索而不是学习化学规律。数据泄漏在分子数据上格外隐蔽因为结构相似度高的分子天然聚集。解决按骨架scaffold切分把同一母核的所有衍生物划到同一侧更严格的做法是按时间切分用早期数据训、近期数据测模拟真实部署场景。指标掉下来是正常的掉下来之后的数字才是可信的。现象换成骨架切分后某些目标上的性能直接归零。原因训练集里根本没有覆盖那类骨架模型只能外推。化学空间太大任何数据集都只是极稀疏的采样。解决先做适用域applicability domain判定——用分子指纹相似度或模型自身的方差估计把明显在分布外的输入直接标成“无法预测”比给一个错数强得多。拒绝预测不是模型无能是负责任。现象活性悬崖附近的预测误差比全局平均高出好几倍模型完全分不清结构微调带来的活性剧变。原因活性悬崖是化学里的常见现象几十道尔顿的取代基变化能让活性差三个数量级。这类样本在数据集中占比低损失函数被大量平滑样本主导模型学不到这种突变。解决在损失里对悬崖对做加权或者构造成对样本pairwise训练让模型直接学习“这两个分子哪个更好”而不是各自回归绝对值。5.2 评估与部署不确定性和多目标权重最容易骗人现象主动学习跑了十轮模型在高不确定区域给出的候选分子合成出来全是垃圾。原因不确定性没校准。集成模型的方差、高斯过程的 sigma这些都只是相对量未经校准的方差会系统性地放大某些化学空间的预测偏差采集函数被这些虚假的高方差带偏。解决留一个独立的校准集画预测值-真实值散点检查 80% 置信区间的实际覆盖率用保形预测conformal prediction之类的方法把方差映射成真实覆盖率可控的区间再拿去驱动采集。现象多目标优化跑出来的分子在某一个目标上极其优秀其他目标勉强及格项目组看了没人要。原因加权标量化里某个权重压过了其他所有项或者目标之间量纲差了几个数量级活性是纳摩尔对数值溶解度是微克每毫升。解决先把所有目标标准化到同一量级再谈权重更稳的做法是不做标量化直接算帕累托前沿把取舍显式暴露给决策者而不是藏在一个自己都说不清的加权系数里。6. 该算还是该学一条判断线和它背后的代价回到最初那个问题什么时候该把化学问题交给模型什么时候老老实实去算。我的判断线是看“这个量是不是已经有足够多的已知答案”。如果数据集里同一类问题的样本寥寥无几模型能给你的只是对邻近样本的插值精度不可能超过物理方法反过来如果某类计算或实验已经积累了成千上万条记录而新的候选点还铺在 10^60 的空间里等着被排序模型的边际收益就非常明显——它不是替代物理是把物理算力花在模型筛过的那一小部分上。这条线的具体位置可以粗糙地这样分场景更优选择理由单个新分子的精确能量、过渡态量化计算无先例可循物理求解是唯一可靠来源上万候选的初筛排序模型成本差四到五个数量级精度够用于排序反应收率的精细预测视数据量样本少时模型极易过拟合到条件细节已知骨架的活性优化模型 少量实验验证骨架内插值可靠外推区间需实验兜底更值得警惕的是“用模型来逃避思考”这个倾向。多目标优化跑出一堆帕累托前沿的点看起来决策科学了但如果目标定义本身就是错的——比如把一个实际上必须满足的硬约束写成了软目标——那前沿画得再漂亮也只是把一个错误答案包装得更精致。模型不会替你判断哪个指标真的重要它只会忠实地把你写下的目标函数优化到极致包括其中的错误。我自己的血泪经验是数据切分那一关。早年做过一个分子性质预测随机切分下指标漂亮得让人膨胀拿到新一批化合物上全线崩盘回头查才发现训练集和测试集里全是同骨架的类似物。从那以后每次建模我都强制先跑一遍骨架切分指标难看也认因为那个难看的数字才是能拿去和项目组交代的。那篇讲义里引用了一句在制药圈流传很广的话大意是取代化学家的不会是机器而是会用机器的化学家。这话听着像口号落到工程上其实很具体知道模型在哪个区间可信、哪一步必须交回物理和实验比把模型精度再榨出两个点重要得多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?