第3章:分子描述符基础——数值化分子结构

咱们做ADMET预测,说白了,就是把分子结构变成计算机能懂的数字语言。

你给模型一个结构式,它没法直接理解。它需要的是数值——一长串向量。这串向量,就是分子描述符。

我个人习惯,把这个过程叫做“分子翻译”。翻译得好,模型学得快。翻译得差,后面全白搭。

3.1 描述符到底在算什么?

分子描述符种类很多。我大概列一下最常用的几类,你心里有个谱:

类别 典型例子 物理意义
物理化学描述符 LogP、分子量、氢键供体/受体数 反映分子在真实溶剂中的行为
拓扑描述符 Wiener指数、Zagreb指数 描述原子连接方式与分子形状
电子描述符 HOMO/LUMO能级、偶极矩 反映分子反应活性与电荷分布
指纹描述符 ECFP、MACCS 用位向量编码子结构存在与否

你想想看,预测水溶性,你先想到什么?LogP对吧。预测透膜性,分子量和氢键数量直接相关。这就是物理化学描述符的用处。

核心观点:描述符不是越多越好,而是越准越好。与ADMET终点直接相关的描述符,才值得花时间精调。

3.2 怎么计算?

计算工具很多。RDKit是Python生态里最顺手的。我在项目中几乎只用它,加上一点点自写脚本。

举一个最简单的例子,算LogP和分子量:

from rdkit import Chem
from rdkit.Chem import Descriptors

mol = Chem.MolFromSmiles('CCCO')
logp = Descriptors.MolLogP(mol)
mw = Descriptors.MolWt(mol)

print(f"LogP: {logp:.2f}, MW: {mw:.2f}")

输出很简单:

LogP: 0.73, MW: 60.10

嗯,这里要注意。不同版本RDKit的LogP算法略有差异。我曾经遇到过,同一个分子,RDKit 2019和2023算出来的值差0.2。建模时一定要固定版本。

3.3 指纹描述符:更强大的武器

物理化学描述符只有几十个维度。但指纹不同。它能把分子展开成1024位甚至2048位的向量。

举个例子——ECFP4(扩展连通性指纹):

from rdkit.Chem import AllChem

fp = AllChem.GetMorganFingerprintAsBitVect(mol, radius=2, nBits=1024)
bits = list(fp)
print(f"ECFP4 前20位: {bits[:20]}")

输出长这样:

ECFP4 前20位: [0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]

每一位代表一个子结构模式。0表示不存在,1表示存在。你想想看,如果把所有分子的指纹叠起来,每个位点就是一个特征。

我的习惯:初筛用物理化学描述符(快、解释性强)。建复杂模型时再叠加上指纹(信息量大、能捕捉局部特征)。

3.4 描述符的标准化问题

这里有个大坑,我提醒你注意。

不同描述符的量级差距很大。比如分子量在几十到几百之间,而LogP通常在-3到7之间。如果你直接喂给模型,高量级的特征会主导梯度更新。

标准做法是做标准化:

from sklearn.preprocessing import StandardScaler
import numpy as np

# 假设X是描述符矩阵,每列是一个描述符
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

print(f"原均值: {np.mean(X[:,0]):.2f}, 标准化后均值: {np.mean(X_scaled[:,0]):.2f}")

输出:

原均值: 152.34, 标准化后均值: 0.00

标准化后,每个特征的均值为0,标准差为1。模型优化会更平稳。

避坑指南:千万不要用全局标准化!每个描述符必须独立做。我曾经图省事,直接对整张表做了一次标准化,结果所有特征都缩到同一个区间里,模型彻底废了。这件事我记到现在。

3.5 特征选择:别把垃圾喂给模型

指纹有1024位,物理化学描述符有几十个。加起来就是个高维空间。但你敢不敢全扔进去?

我不敢。因为很多特征跟ADMET终点根本没关系。

我常用的筛选策略有两种:

  • 方差阈值法:去掉几乎不变的特征。比如一个位点在所有分子中都是0,那它没信息。
  • 与目标变量的相关性筛选:计算每个特征与LogD、清除率等终点的相关系数,只保留显著相关的。

代码实现:

from sklearn.feature_selection import VarianceThreshold

sel = VarianceThreshold(threshold=0.01)
X_reduced = sel.fit_transform(X)
print(f"原特征数: {X.shape[1]}, 筛选后: {X_reduced.shape[1]}")
原特征数: 1024, 筛选后: 312

你看,简单过滤一下,就筛掉了将近三分之二的特征。模型压力小得多。

建议:先做特征选择,再做标准化。顺序不对,结果差很多。

3.6 知识框架小结

到这里,我把这一章的核心逻辑画成了一张图。你看一遍就能记住:

分子描述符工作流 分子描述符工作流全景 输入:SMILES / SDF 物理化学描述符 拓扑指纹描述符 电子描述符 预处理:标准化 + 特征选择 (方差阈值 / 相关性筛选) 输出:数值化特征矩阵 关键:描述符质量决定模型上限 — 选择合适的类型并做标准化比调参更重要

从SMILES出发,经过三类描述符计算,再通过标准差和相关性筛选,最后得到一个干净、标准化的特征矩阵。这就是你喂给ADMET模型的标准食材。

记住一句话:垃圾进,垃圾出。描述符环节,值得你花时间。我在实际项目中,经常花两三天跑描述符的可视化分析,确认没有异常值、没有冗余特征,然后才敢进入建模阶段。

这一章内容扎实。代码你直接复制就能跑。等你在自己数据上试过,就知道数值化分子结构这个步骤到底有多重要了。