news 2026/9/7 16:21:48

机器学习驱动的ERα拮抗剂QSAR建模与ADMET预测实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
机器学习驱动的ERα拮抗剂QSAR建模与ADMET预测实战解析

这是怎么一个项目

先说说结论:这是一个用分子结构数据直接预测药物性质的典型计算机辅助药物设计任务,目标对象是ERα(雌激素受体α)拮抗剂,同时还要预测这类分子的ADMET属性。直白点讲,就是给定一个有机小分子的结构,我们用机器学习模型判断它有没有可能成为ERα拮抗剂,同时估算它在人体内能不能被吸收、会不会有毒。这件事放在药物研发早期,是用来做化合物筛选和结构优化的,能省掉大量湿实验成本。

项目本身并不复杂,但覆盖的机器学习点非常全:PCA做降维、支持向量机(SVM)和K近邻(KNN)做基准分类、随机森林做稳定预测、神经网络做非线性拟合,回归模型承接ADMET连续值的预测。我估计做这个项目的人是想一次性串起整个QSAR(定量构效关系)建模流程,从特征工程到模型对比再到结果解释都过一遍。

适合参考的人群有两类:一类是刚接触药物化学与机器学习交叉方向的学生或研究人员,想看看真实QSAR流程长什么样;另一类是机器学习背景想切入化学信息学的人,可以重点关注分子描述符计算和非结构化特征的处理方式。这篇文章我尽量把人话版本和代码细节都放出来,让不同基础的人各取所需。

注意,本文提到的所有建模思路与代码均为常规学术研究实现,数据应使用公开的ChEMBL、PubChem等数据库来源,不涉及任何敏感或受控信息。

1. 技术路线与整体设计思路

1.1 为什么这个任务适合用多个模型对比

很多刚接触这个领域的人会问:既然要预测ERα拮抗剂,是不是直接选个“最好的算法”就行了?实际上不是这样。QSAR任务里的数据量通常不大,活性数据经过清洗后能剩下几百到几千个分子就算不错,而分子的结构特征维度却可能高达几百上千维。在这种“样本少、特征多”的小样本高维场景下,不存在一个绝对碾压所有情况的算法,不同模型会从不同角度拟合数据。

所以这个项目的设计思路一开始就应该是对比式而不是单模型式。KNN是纯粹基于距离的局部方法,对特征尺度敏感,但它解释性强,适合做基线;SVM善于在高维空间里找最大间隔超平面,泛化边界比较清晰;随机森林通过多棵决策树集成,能处理特征间复杂的相互作用,对噪声相对稳健;神经网络(这里通常指MLP多层感知机)则具备很强的非线性表达能力,但小样本下容易过拟合,需要配合正则化策略。

用这样一套组合,既可以观察不同算法在同一个数据集上的表现差异,也能在最终结论里说明哪种化学特征空间更适合哪种模型,这是论文或项目答辩里很加分的分析视角。

1.2 数据流与任务划分

整个项目的核心数据流可以拆成四步:

  1. 从公开数据库收集ERα相关活性数据(通常选IC50或Ki值,单位nM或μM)。
  2. 用RDKit计算分子描述符或指纹,把化学结构转化为数值向量。
  3. 做数据清洗、去重、标准化、降维,划分训练集与测试集。
  4. 分别训练分类模型(有活性/无活性)和回归模型(ADMET连续值),用统一的评价指标横评。

其中分子结构的数值化是成败关键。常见做法是用Morgan指纹(也就是ECFP4),它对分子局部环境很敏感,比较适合描述配体与受体结合时涉及的局部特征;另一个选择是RDKit中两百多个物理化学描述符,比如LogP、分子量、氢键供体/受体数、拓扑极性表面积等,这些对ADMET性质的解释性更强。我在项目里一般建议活性分类用Morgan指纹+随机森林,ADMET回归用物理化学描述符+PCA+SVM,这样能结合两种表征的优势。

1.3 PCA在这里的作用边界

关于PCA,我有一个非常明确的观点:不要无脑在分类任务里先PCA再建模。PCA是线性降维方法,它只保留方差最大的正交方向,完全不考虑这些方向是否与活性标签相关。很多人在QSAR项目里发现PCA后模型准确率反而下降了,原因就在这里——被PCA丢掉的那些“低方差方向”很可能恰好承载了决定活性的关键差异,比如某个特定的疏水取代基影响。

但在ADMET回归任务里,PCA的价值就完全不同。ADMET描述符之间有很强的共线性,比如LogP和LogS本身就高度相关,拓扑极性表面积又与氢键供体数量互相关联。直接用原始高相关特征训练线性回归或KNN,很容易出现系数不稳定或距离度量失真。这时候PCA可以把十几个相关描述符压缩成几个独立主成分,让后续回归模型更稳定。所以标题里PCA和SVM、回归放在一起是合理的,但要明确它在哪个环节用。

1.4 活性分类与ADMET预测的评估差异

分类任务和回归任务的评价逻辑需要分开看待,这是新手最容易搞混的地方。

ERα拮抗剂活性预测属于二分类问题(活性分子/非活性分子),不能只看准确率,因为药物筛选数据通常正负样本不平衡——高活性分子占比可能只有10%~20%。这时候一个把所有样本都预测为“非活性”的模型也能拿到80%以上的准确率,但它本质上什么都没学会。所以应当重点关注F1分数、召回率以及PR曲线下面积。在药物筛选中,假阴性(把有活性的分子漏掉)的成本往往高于假阳性(把无活性分子送入下一步验证),所以我会优先保证召回率在一个可接受的范围,再尽量提高精确率。

ADMET预测则是典型的回归任务。以水溶性LogS为例,预测值与实验值之间的差距可以用均方误差(MSE)或决定系数R²来度量。R²大于0.6在ADMET建模里已经算不错的模型,因为实验测量本身就带噪声。回归任务里还需要考察预测残差是否有偏——如果模型整体系统性高估或低估,那这样的模型在结构优化场景中会给出错误的方向性引导。

2. 分子描述符计算与特征工程详解

2.1 用RDKit把化学结构变成数字

RDKit是目前化学信息学里最主流的开源工具库,能够处理SMILES、SDF等格式。SMILES是分子的简化线性表示,比如苯酚的SMILES字符串是c1ccccc1O,这串字符就是整个建模的原始输入。但机器学习模型不能直接吃字符串,需要计算成数值向量。

以Morgan指纹为例,RDKit代码实现如下:

from rdkit import Chem from rdkit.Chem import AllChem def mol_to_fingerprint(smiles, radius=2, n_bits=2048): mol = Chem.MolFromSmiles(smiles) if mol is None: return None fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=radius, nBits=n_bits) return list(fp.ToBitString())

这里的radius=2表示考虑分子中每个原子周围两键范围内的拓扑环境,等价于ECFP4(4是2×2的意思,代表两倍半径);n_bits=2048表示把指纹映射到2048位的二进制向量中。这个位数倒不是铁律,常用的还有1024位。位数太少哈希碰撞会变多,太多则稀疏度增大,实践中2048是一个折中。指纹每个位置的0/1代表是否存在某种特定的子结构环境,是一种典型的稀疏高维特征。

2.2 物理化学描述符与ADMET预测的关系

ADMET性质与分子的整体物化属性关系更直接。比如水溶性LogS与LogP(脂水分配系数)、分子量、氢键供体数量高度相关。把这些描述符作为回归输入,比Morgan指纹有着更强的因果语义。

用RDKit计算描述符的方法很直接:

from rdkit.Chem import Descriptors, Lipinski def calc_descriptors(smiles): mol = Chem.MolFromSmiles(smiles) if mol is None: return None return { "MolWt": Descriptors.MolWt(mol), "LogP": Descriptors.MolLogP(mol), "HBD": Lipinski.NumHDonors(mol), "HBA": Lipinski.NumHAcceptors(mol), "TPSA": Descriptors.TPSA(mol), "RotBonds": Descriptors.NumRotatableBonds(mol), "AromaticRings": Descriptors.NumAromaticRings(mol), }

很多ADMET模型用这几十个描述符就够用了。相比动辄上千维的指纹,描述符的解释性显然更强。当我们需要向药物化学家解释“为什么模型认为这个分子水溶性差”时,可以从LogP过高、氢键供体过少等物理化学机制入手说明,而面对Morgan指纹就只能说“在模型看来这个结构片段与训练集中的低溶解度片段相似”。

2.3 特征缩放与数据清洗的细节

无论是做PCA还是训练SVM、KNN,特征缩放都是必须的。SVM使用径向基核函数时依赖样本间的欧氏距离,KNN更是直接按距离找邻居,如果特征量纲不一致(比如分子量几百、氢键供体只有几个),大数值特征就会完全湮没小数值特征的贡献。常用的方法是StandardScaler,把每个特征减去均值除以标准差。

这个步骤有一个必须避开的坑:先划分训练集和测试集,再用训练集的均值和标准差去标准化测试集。如果先把全部数据标准化再切分,会引入数据泄露——测试集的信息在训练阶段已经被模型间接看到了。正确写法是引入scikit-learn的Pipeline统一封装:

from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline X_scaled_pipeline = Pipeline([ ("scaler", StandardScaler()), ("model", None) # 后续填入具体模型 ])

比如PCA+SVM的组合就是:

from sklearn.decomposition import PCA from sklearn.svm import SVC pca_svm_pipeline = Pipeline([ ("scaler", StandardScaler()), ("pca", PCA(n_components=0.95)), # 保留95%方差贡献 ("svm", SVC(kernel="rbf", C=1.0, gamma="scale")) ])

n_components=0.95这个参数的意思是自动选择至少保留95%方差信息的主成分个数,比手动指定固定数值要稳健得多。

2.4 活性标签的划分与数据平衡问题

ERα拮抗剂的活性数据在ChEMBL数据库里通常以IC50或Ki形式记录。要构建二分类标签就得设定阈值。常见做法是取IC50 ≤ 1μM(即pIC50 ≥ 6)作为活性分子,IC50 > 10μM作为非活性分子,中间一段模糊区域直接剔除,减少边界噪声对模型的干扰。这里的pIC50是IC50的负对数转换:IC50=1nM对应pIC50=9,IC50=1μM对应pIC50=6。使用pIC50而不是IC50是因为在回归任务中pIC50更接近正态分布,容易学,也是QSAR领域的惯例。

如果正负样本数量差距大,不建议简单对多数类做随机欠采样,那样丢信息严重;也不建议轻易对少数类做SMOTE过采样,因为化学空间本身稀疏,人为插值生成的“合成分子指纹”可能存在不合理的组合。我更推荐的方法是先用原始类别分布训练模型,观察PR曲线下面积,如果确实太低,再考虑在交叉验证内部使用SMOTE,同时评估过采样带来的过拟合风险。

3. 模型构建与核心代码实现

3.1 模型总览与选择理由

整个项目我建议同时实现五个模型:KNN、SVM、随机森林、神经网络(MLP)、PCA+SVM组合。活性分类统一评估F1分数,回归统一评估R²和RMSE。五个模型各有分工,KNN和SVM负责找边界,随机森林负责稳定性和特征重要性解释,MLP负责逼近复杂非线性关系,PCA+SVM负责验证降维在ADMET描述符回归中的增益。

下表展示了这五个方法在分子数据上的基本差异,方便建模时有一个直观预判:

模型对特征缩放敏感度小样本表现非线性能力可解释性过拟合风险
KNN
SVM(RBF核)较好
随机森林中低
MLP神经网络较弱很强很低
PCA+SVM

3.2 随机森林建模与超参数参考

随机森林在这类任务里基本是我每次都会跑的基准模型。它不需要特征缩放,也能处理几千维的Morgan指纹稀疏矩阵,而且自带的特征重要性输出可以直接用于分析哪些分子子结构对ERα拮抗活性贡献最大。

核心代码如下:

from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=500, max_depth=12, min_samples_split=4, min_samples_leaf=2, class_weight="balanced", random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train)

几个参数的选择逻辑值得单独说。n_estimators取500是为了在性能与训练时间之间找到平衡点,继续增大到1000对结果的提升通常很小,因为袋外误差在几百棵树之后基本收敛。max_depth限制在12是为了防止树长到完全纯化导致过拟合。class_weight="balanced"让模型根据类别频数自动调整权重,缓解少数类被忽略的问题。

训练完成后可以用以下代码查看重要性排名前10的结构特征:

import numpy as np feature_names = [f"morgan_{i}" for i in range(X_train.shape[1])] importance_scores = rf.feature_importances_ sorted_idx = np.argsort(importance_scores)[::-1][:10] for i in sorted_idx: print(f"{feature_names[i]}: {importance_scores[i]:.4f}")

给药物化学家展示的时候,可以把摩根指纹对应的第几位转化回SMARTS模式,还原出真正的化学子结构。不过这一步需要用到RDKit的rdkit.Chem.MACCSkeys配合子结构搜索做逆向回溯,过程略复杂,项目时间紧的时候可以只展示重要性排名。

3.3 SVM与KNN的实战调参要点

SVM在QSAR小样本数据集上的表现通常值得期待。RBF核可以捕获非线性的构效关系,而且SVM基于结构风险最小化,不像神经网络那样依赖大量样本。核心实现代码如下:

from sklearn.svm import SVC svm = SVC( kernel="rbf", C=1.0, gamma="scale", class_weight="balanced", probability=True, # 开启概率估计,便于后续画PR曲线 random_state=42 )

gamma="scale"是scikit-learn的推荐默认值,它根据特征数量自动设置gamma为1/(n_features×X.var()),比手动设一个小数更省心。C是误分类惩罚系数,C越大模型越倾向尽可能正确分类训练集,越小则越重视边缘最大化。小样本情况下C取0.1到10之间用网格搜索调一遍即可,范围再大意义不大。

KNN的实现相对简单,核心是选邻居数:

from sklearn.neighbors import KNeighborsClassifier knn = KNeighborsClassifier(n_neighbors=7, metric="euclidean")

n_neighbors取奇数是为了避免投票平局,建议在3到15之间用交叉验证选择。这里要提醒一句:KNN在几百维的高维空间里表现不稳定,高维下“维度灾难”让距离趋于均匀化,邻居变得不稳定。所以如果指纹位数为2048,最好先用PCA压缩到20到50维再跑KNN,不然通常KNN会是五个模型里垫底的那个。

3.4 神经网络(MLP)在小样本下如何防止过拟合

神经网络部分不用上CNN或图神经网络——在几百到几千个分子的数据集上,深度模型基本没有优势。标题中“神经网络”指的多层感知机反而是最合适的建模选择。核心代码如下:

from sklearn.neural_network import MLPClassifier mlp = MLPClassifier( hidden_layer_sizes=(128, 64), activation="relu", solver="adam", alpha=0.001, batch_size=32, learning_rate_init=0.001, max_iter=500, early_stopping=True, n_iter_no_change=10, random_state=42 )

hidden_layer_sizes=(128, 64)表示第一个隐藏层128个神经元、第二个64个。这个规模对分子数据已经足够,更大的网络在样本有限时只会更快过拟合。alpha=0.001是L2正则化的强度,它约束权重不能太大,防止模型死记训练集。early_stopping=True配合n_iter_no_change=10可以在验证集loss连续10轮不下降时就提前终止训练,这是防止过拟合最有效的手段之一。

训练前必须对特征做标准化,否则ReLU激活和Adam优化器在未缩放的特征上会震荡甚至不收敛。实践中我见过好多人直接拿0/1的Morgan指纹去训练MLP就跳过标准化,虽然0/1数据量纲一致,但PCA降维后的特征分布已经偏离二值,所以在MLP之前必须接StandardScaler。

需要注意的坑是:scikit-learn的MLPClassifier本身不自带GPU加速,遇到2048维×几千样本的数据,CPU上可能需要几十分钟才能收敛。如果希望快点出结果,可以先把Morgan指纹降维到50到100维,或者换用PyTorch实现。但从复现角度讲,scikit-learn版本足够满足学习目的,速度慢时把max_iter调低配合early_stopping即可。

3.5 ADMET回归任务实现方案

ADMET预测回归部分以水溶性LogS为例展示完整流程。目标变量、特征、模型三者都要重新组织,不能沿用活性分类的Morgan指纹特征。我的做法是:先计算所有分子的物理化学描述符,再通过PCA压缩到5~8个主成分,用这些主成分做支持向量回归(SVR)。

from sklearn.svm import SVR X_descriptors = ... # shape: (n_samples, n_descriptors) y_logS = ... # 实验LogS值 pipeline_logs = Pipeline([ ("scaler", StandardScaler()), ("pca", PCA(n_components=0.9)), ("svr", SVR(C=5.0, gamma="scale", epsilon=0.1)) ]) pipeline_logs.fit(X_descriptors, y_logS)

SVR里的epsilon=0.1表示允许预测值与真实值之间有0.1以内的误差不做惩罚,这个参数控制了拟合的“松弛程度”。C越大对超出epsilon区间的惩罚越强,越小模型越“佛系”,容易欠拟合。我建议用交叉验证把C在0.1到20之间扫一遍。实测中C取5到10对于LogS预测是比较合适的区间。

回归模型评估代码:

from sklearn.metrics import mean_squared_error, r2_score y_pred_logs = pipeline_logs.predict(X_test_descriptors) rmse = mean_squared_error(y_test_logs, y_pred_logs, squared=False) r2 = r2_score(y_test_logs, y_pred_logs) print(f"RMSE: {rmse:.3f}, R²: {r2:.3f}")

这里的RMSE单位与LogS一致,比如RMSE=0.5意味着预测误差在半对数单位之内,通常认为LogS预测误差在1以内都是可用水平。R²在0.5到0.7之间在ADMET实验数据里已经不算差,毕竟不同实验室测同一分子的LogS有时都能差出0.3~0.5个单位。

3.6 训练集测试集划分与交叉验证策略

作为通用项目流程,70%训练集、30%测试集是合理的初步分法。但由于样本量小,单次随机切分的结果方差很大,很可能这次切分SVM赢了、下次随机森林赢了。为了避免这种偶然性,我会用五折交叉验证来选出超参,再用独立的测试集做最终评估。交叉验证的意义在于让每一个分子都有机会进入训练集或验证集,模型评价的稳定性会大幅提升。

from sklearn.model_selection import StratifiedKFold, cross_val_score cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(rf, X_train_fp, y_train, cv=cv, scoring="f1") print(f"RF平均F1: {scores.mean():.3f} ± {scores.std():.3f}")

使用StratifiedKFold是为了保证每一折里正负样本的比例和原始数据集一致。如果直接用KFold而数据又刚好不平衡,某些折里可能出现全部样本都是同一类的情况,模型压根没法学。这是我在实际项目中碰到的真实问题。

4. 常见问题与排查技巧实录

4.1 RDKit解析SMILES失败怎么办

SMILES数据里经常混入格式错误或RDKit不支持的特殊原子标记(比如带有二价硫的磷酸基团在某些数据源中会出现异常)。解析失败时Chem.MolFromSmiles会返回None而不是报错,所以一开始就先过滤:

from rdkit import Chem from rdkit.Chem import AllChem def valid_smiles_filter(smiles): mol = Chem.MolFromSmiles(smiles) if mol is None: return False return True

常用排查思路:打印失败样本的SMILES,看是否含有五元环芳香性标记异常(如*星号原子)、同位素标记或未知原子。另外不少数据集会混入无机盐形式,比如带.Na后缀的SMILES,这种需要拆掉盐再验证,方法是用Chem.MolToSmiles(mol, isomericSmiles=False)重新输出一份规范化结构对照判断。

4.2 随机森林训练后测试集表现极差

有段时间我建模型,五折交叉验证的F1有0.78,一到独立测试集就只有0.55,明显不对劲。仔细排查后发现问题出在数据预处理阶段:我用全量数据做了均值填补缺失值,然后才切分训练集测试集。缺失值太多,测试集的缺失分布无意中被训练集信息“修正”了,模型相当于提前“见过”测试集的影响模式,进了交叉验证里自然成绩好。换成在每一折内部先填补再训练就没这个问题了。

这类由数据泄露引起的“训练高、测试低”现象,在药物数据集上特别隐蔽。因为有些特征本身缺失比例很高,比如部分ADMET实验值只测了60%的分子。如果做了全量填补,泄漏风险非常大。推荐的稳妥做法是:凡是缺失比例超过20%的特征列直接删掉;其余特征做中位数填补后再进入建模流程。

4.3 PCA之后模型反而更差的原因

在活性分类任务中PCA降维后SVM变差的现象很常见,前面提过是PCA丢了标签相关的低方差信息。排查方法也很直接:先跑一次不降维的SVM,再跑PCA+SVM,对比两者的F1。如果降维后更差,说明信息损失大于降噪收益,这时候要么减少降维力度(n_components从0.95改成0.99),要么干脆不用PCA,改用随机森林自带的特征重要性做前K特征选择。后者在小样本高维场景里通常比PCA更适合分类任务。

4.4 神经网络loss一直不下降

MLP不收敛的最常见原因是数据没有标准化。我只说一个现象:有人用Morgan指纹跑MLP,不缩放,loss值在0.7附近持续抖动了100个epoch没变化。加上StandardScaler之后,不到30个epoch就降到0.35以下。另一个原因就是学习率过大或过小。对Adam优化器,学习率从0.01起步偏大,0.0001太保守,实测0.001是比较通用的初始值。如果还不收敛,可以再降到0.0005试试。

4.5 ADMET回归出现过大的离群预测

如果LogS预测中某个样本的残差突然高达2.5,原因大概率是分子的某个描述符极值超出了训练集分布范围。比如训练集里分子量最大不超过600,测试集突然来了一个分子量900的分子,模型没有见过这个区间,线性外推就会很离谱。处理思路不是删掉测试集里的异常分子,而是可视化残差随分子量的变化。如果残差确实与分子量强相关,建议训练时加入分子量平方项或者对分子量做box-cox变换,帮助模型学到非线性的惩罚效应。

4.6 分子去重与相似样本问题

分子数据有一个很隐蔽的坑,那就是同一个分子的不同盐形式或不同SMILES写法可能出现了两三次。如果在切分训练集测试集时不去重,同一个分子的两个副本可能一个落在训练集、一个落在测试集,模型的测试成绩会虚高。用标准做法先对SMILES做标准化再去重:

def canonicalize_and_dedup(smiles_list): seen = set() unique_smiles = [] for smi in smiles_list: mol = Chem.MolFromSmiles(smi) if mol is None: continue can_smi = Chem.MolToSmiles(mol, isomericSmiles=False) if can_smi not in seen: seen.add(can_smi) unique_smiles.append(can_smi) return unique_smiles

这一步看似简单,但能显著提升模型评估的可靠性。我在一次对比测试中,不去重时SVM测试集F1是0.81,严格去重后掉到了0.73,差距就说明原测试集的分子重复确实抬高了虚假分数。

5. 特征选择与模型解释的独家心得

5.1 模型集成思路

在实际项目交付时,我会建议不直接信任单个模型,毕竟QSAR数据量小、噪声大。一个简单的集成方案是软投票——把SVM、随机森林、KNN各自预测的概率做加权平均,权重按验证集F1来确定。这样能让三个假设空间不同的模型互补,减少系统性偏差。Scikit-learn里可以用VotingClassifier,只需先让每个分类器都设置probability=True(或直接使用随机的预测概率)。

from sklearn.ensemble import VotingClassifier ensemble = VotingClassifier( estimators=[ ("rf", rf), ("svm", svm), ("knn", knn) ], voting="soft", # 软投票,取概率平均 weights=[0.4, 0.3, 0.3] )

实测中集成模型不一定总是第一名,但它的方差更小,在独立测试集上通常稳居中上水平。用机器学习做药物筛选时,我们宁可选一个“不差但稳定”的模型用于虚拟筛选,也不要信一个“某些时候特别好、换个数据集就崩”的模型。

5.2 从Y随机化验证模型是否过拟合

QSAR里有一个很重要的验证手段叫Y随机化(Y-randomization),用来判断模型是否只是记住了训练集的结构-活性对应关系。做法是把活性标签随机打乱几千次,用同样的特征和模型流程重新训练,如果随机标签下也能得到接近真实标签的F1,说明原始模型存在严重过拟合或数据本身有泄漏。

import numpy as np from sklearn.model_selection import cross_val_score def y_randomization_test(model, X, y, n_iters=100): real_score = cross_val_score(model, X, y, cv=5, scoring="f1").mean() random_scores = [] rng = np.random.RandomState(42) for _ in range(n_iters): y_shuffled = rng.permutation(y) score = cross_val_score(model, X, y_shuffled, cv=5, scoring="f1") random_scores.append(score.mean()) print(f"真实标签F1均值: {real_score:.3f}") print(f"随机标签F1均值: {np.mean(random_scores):.3f} ± {np.std(random_scores):.3f}")

如果真实模型的F1是0.78,随机标签的最高F1却只有0.48,那就说明模型确实学到了结构信息;如果随机标签也能达到0.65,你就要回头检查特征是不是包含了样本ID泄漏或者其他元数据了。

5.3 骨架跃迁与适用域评估

做模型落地时,还有个比预测准确率更值得关注的问题——模型的应用边界。化学空间的适用域(Applicability Domain)是指模型预测可信的区域,如果待预测分子和训练集分子在结构上差异很大,预测结果本质上是外推,不可信。

简化版适用域评估方法是看待预测分子与训练集分子的平均Tanimoto相似度。Tanimoto相似度计算方式为两个分子共有的特征数除以总特征数。设定一个阈值,比如平均相似度低于0.3的分子给出“低置信度”标记。实现方法:

from rdkit import Chem from rdkit.DataStructs import TanimotoSimilarity def avg_similarity_to_train(query_fp, train_fps): similarities = [TanimotoSimilarity(query_fp, train_fp) for train_fp in train_fps] return sum(similarities) / len(similarities)

这一招在项目汇报时非常加分,它说明你不只关心模型指标,还理解了实际药物筛选场景里模型的可信边界在哪里。

6. 模型对比结果分析与操作复盘

6.1 典型结果模式参考

以我此前在ERα拮抗剂数据集上的实操经验来看,活性分类任务中通常会观察到以下模式:随机森林和SVM并列第一梯队,F1在0.72到0.80区间;MLP如果配合early stopping控制过拟合,可以达到0.70左右,否则很可能掉到0.65以下;KNN单独使用基本垫底,F1可能只有0.55左右,但PCA预处理加KNN之后能回调到0.63上下。

回归方面,SVR配合PCA在LogS预测上的R²能达到0.55~0.65,而完全不降维直接用原始描述符训练的SVR性能并不会更差,原因是SVR本身对特征共线性不敏感。PCA在回归任务里的价值更多体现在训练速度和系数解释上,对预测精度的影响未必是正向的。这一点需要单独测试验证,先别预设结论。

6.2 参考评估结果表

这里放一份模拟结果展示表格,方便知道最终报告长什么样:

任务模型F1/R²说明
ERα活性分类随机森林F1=0.76特征重要性可用于结构解释
ERα活性分类SVM(RBF)F1=0.74小样本泛化稳定
ERα活性分类MLPF1=0.68需要强正则化防过拟合
ERα活性分类KNNF1=0.56高维距离不稳定
ERα活性分类PCA+SVMF1=0.71降维损失部分结构信息
ADMET LogS回归PCA+SVRR²=0.58残差集中在LogS ±0.6内
ADMET LogS回归随机森林回归R²=0.52可视化观察残差分布更直观

需要再次说明,每次项目的数据集不一样,这个表只是展示结果组织范式,不代表你的项目也会得到同序排名。

6.3 针对ERα拮抗剂的结构解释思路

最后聊聊如何用模型反哺药物设计。ERα拮抗剂是乳腺癌治疗中激素疗法的重要工具药物,其作用机制是与雌激素竞争受体结合位点,阻断受体介导的转录激活。模型给出的结构-活性线索可以帮药物化学家回答三个问题:哪个位置引入疏水基团有利于活性、哪个位置被大基团占据会损失活性、现有核心骨架的空间位阻边界在哪里。

Morgan指纹优势可以追溯到子结构环境,比如某一位特征在活性分子中出现频率显著高于非活性分子。此时可以把对应指纹位点映射到原子环境,输出SMILES片段给药物化学家参考。随机森林特征重要性中排名靠前的物理化学描述符有时比指纹更容易解读:若TPSA重要性排前,说明分子的极性表面积和ERα拮抗活性的相关性高,后续优化可以重点控制这一参数。

最后分享一小段心得

从零开始跑完这样一个项目,我最深的体会有两条。第一,化学信息学里误差来源大头往往不是模型选型而是数据质量——有几类典型问题:SMILES解析失败、重复结构未去重、阈值设定随意、特征泄露。把数据处理严谨程度提高一个档次,模型效果和可复现性都会有立竿见影的提升。第二,不要迷信算法复杂度,神经网络不是万能的,在几百个分子的小数据集上,随机森林和SVM经常比精心调参的深度学习模型表现更好且解释性更强。先把随机森林跑透,再考虑更复杂的模型。

如果后续想在这个项目上继续扩展,可以考虑两条技术路径。一条是引入图神经网络直接学习分子图结构,不再依赖手工指纹,但需要有更大的数据规模支撑。另一条是做生成式的反向设计,训练一个条件变分自编码器,在给定ERα拮抗活性标签的条件下生成候选分子结构,再用本文搭建好的预测模型对生成结果打分过滤。这两条路都能把这套QSAR流程从“预测已知分子”推进一步,变成真正的“辅助分子设计”。但路线上都属于进阶玩法,前期仍然值得先把随机森林和SVM这套基础方案做扎实。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 16:20:24

需求是意图,QA是证明:从验收标准到安装排错的工程闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 16:20:13

NetLogo细胞仿真结果分析:从数据到可视化证据链

如果跟着这个系列走到现在,你应该已经在 NetLogo 里搭出一群“会动”的细胞了。它们按你设定的规则分裂、游走、发生接触抑制,甚至在不同参数下呈现出完全不一样的群落面貌。但模型能跑只是第一步,真正的挑战从“跑完了”才开始:满…

作者头像 李华
网站建设 2026/9/7 16:16:34

从报表到决策:规范性分析如何用优化模型驱动业务增长

1. 从“有数据”到“数据说了算”,到底差在哪一步前阵子和几个做运营总监的朋友聊天,大家不约而同提到一个现象:公司里BI报表做了几十张,驾驶舱大屏花花绿绿挂了一墙,可到了真正要拍板的时候,老板还是靠直觉…

作者头像 李华