最近我做了一个很小但很完整的 AI 制药入门项目:输入一个氨基酸的 SMILES 字符串,程序会判断它更偏向亲水还是疏水。
这个项目的名字叫 AminoHydro Classifier。它不追求复杂模型,也不假装自己已经能替代真正的药物性质预测系统。它更像一个“最小可运行样例”:把计算化学里的分子表示、RDKit 特征提取、机器学习分类器训练和模型预测串成一条完整链路。
核心流程可以概括为:
SMILES 字符串 -> RDKit 解析 -> Mol 对象 -> Morgan 指纹 -> MLP 神经网络 -> 亲水 / 疏水
这条链路虽然短,但已经包含了很多 AI 制药项目里反复出现的基础概念。
为什么从氨基酸开始
氨基酸是蛋白质的基本组成单元。标准蛋白质编码氨基酸有 20 种,它们可以通过不同排列组合形成大量蛋白质。每个氨基酸都有共同的主链结构:
- 氨基:
-NH2 - 羧基:
-COOH - 一个氢原子:
H - 一个侧链基团:
R group
真正决定不同氨基酸性质的,主要是侧链 R。有的侧链偏非极性,更容易和脂溶性环境相处,通常被认为更疏水;有的侧链带有羟基、羧基、氨基等极性或可电离基团,更容易和水形成相互作用,通常被认为更亲水。
亲疏水性在药物设计里非常重要。比如,一个分子如果过于亲水,可能难以穿过脂质膜;如果过于疏水,又可能带来溶解度差、非特异性结合强等问题。血脑屏障 BBB 也是一个典型例子:一般来说,更合适的脂溶性有助于分子跨越屏障,但真实情况还会受到分子量、极性表面积、氢键供受体、转运蛋白等多种因素影响。
所以,氨基酸亲疏水性是一个很适合作为入门任务的性质:概念直观,数据量小,容易验证,也能自然引出分子机器学习的基本流程。
SMILES:把分子写成字符串
计算机不能直接理解“亮氨酸”“赖氨酸”这样的中文名称,也不能直接理解课本上的二维结构图。我们需要一种机器可读的表示方法。
SMILES 就是其中一种常见格式。它可以用一串文本描述分子的原子、键、环、支链等结构信息。例如:
亮氨酸 Leu: CC(C)CC(N)C(=O)O
丝氨酸 Ser: NC(CO)C(=O)O
赖氨酸 Lys: NCCCCC(N)C(=O)O
这些字符串看起来像化学版的“密码”,但对 RDKit 这样的化学信息学工具来说,它们是可以被解析的结构描述。
项目的数据集在 data/amino_acids.csv,一共包含 20 个蛋白质编码氨基酸,每条数据包括名称、缩写、三字母代码、SMILES 字符串和亲疏水标签。
标签约定是:
1 = 疏水
0 = 亲水
当前数据集中有 9 个疏水氨基酸、11 个亲水氨基酸。
RDKit 负责什么
一个很容易混淆的问题是:RDKit 是不是分类器?
答案是:不是。
RDKit 更像是一个“分子工具箱”。它不会自动告诉我们一个分子是亲水还是疏水,但它能做很多机器学习前必需的事情,比如:
- 读取 SMILES 字符串
- 检查分子是否合法
- 构建内部的分子对象
Mol - 计算分子指纹、描述符、子结构等特征
在这个项目里,RDKit 的第一步工作是把 SMILES 转成 Mol 对象:
mol = Chem.MolFromSmiles(smiles)
可以把 Mol 理解成 RDKit 在程序内部使用的分子结构。它比字符串更结构化,知道哪些原子连在一起,哪些是支链,哪些是环结构。
但是机器学习模型仍然不能直接吃 Mol 对象。下一步,我们还要把 Mol 转成数值向量。
Morgan 指纹:把分子变成 2048 位向量
这个项目使用 Morgan fingerprint,也就是常说的 Morgan 指纹。它和 ECFP 类指纹思想接近,会围绕每个原子观察一定半径内的局部化学环境,然后把这些结构信息编码到一个固定长度的二进制向量里。
项目里默认使用:
radius = 2
n_bits = 2048
也就是说,每个氨基酸最终都会变成一个长度为 2048 的 0/1 向量。
可以这样理解:
SMILES: CC(C)CC(N)C(=O)O
Morgan 指纹:
[0, 1, 0, 0, 1, 0, ..., 1, 0]
这个向量不是给人看的,而是给模型看的。它把“分子结构”转换成了“机器学习特征”。
项目中的对应代码在 src/features.py:
generator = rdFingerprintGenerator.GetMorganGenerator(radius=radius, fpSize=n_bits)
fp = generator.GetFingerprint(mol)
return np.array(fp, dtype=int)
这里有一个关键点:Morgan 指纹不是神经网络,也不是预测模型。它只是特征工程的一部分。真正做分类的是后面的 MLP。
MLP:最经典的前馈神经网络
MLP,全称 Multi-Layer Perceptron,多层感知机。它是最经典的前馈神经网络之一。
它的基本结构可以简单理解为:
输入层 -> 隐藏层 -> 激活函数 -> 隐藏层 -> 输出层
每一层都在做类似这样的事情:
加权求和 -> 加上偏置 -> 经过激活函数 -> 传给下一层
如果没有激活函数,多层线性变换叠在一起本质上仍然只是线性模型。加入 ReLU、Sigmoid、Tanh 这样的非线性激活函数后,模型才有能力学习更复杂的非线性关系。
在这个项目里,MLP 的输入是 2048 维 Morgan 指纹,输出是一个二分类结果:
0 = 亲水
1 = 疏水
模型定义在 src/model.py:
model = MLPClassifier(
hidden_layer_sizes=(128, 64),
activation="relu",
solver="adam",
alpha=0.001,
max_iter=1000,
random_state=42,
early_stopping=True,
validation_fraction=0.2,
n_iter_no_change=20,
)
这里用了两层隐藏层,分别有 128 和 64 个神经元。early_stopping=True 表示如果验证集效果长时间没有提升,训练会提前停止,避免在小数据集上过度训练。
训练流程
训练入口在 main.py,运行:
python main.py train
完整训练流程如下:
- 从
data/amino_acids.csv读取 20 个氨基酸。 - 每个 SMILES 先通过 RDKit 转成
Mol。 - 每个
Mol再转成 2048 维 Morgan 指纹。 - 得到特征矩阵
X和标签数组y。 - 使用
train_test_split(..., stratify=y)切分训练集和测试集。 - 训练
MLPClassifier。 - 打印准确率、分类报告和混淆矩阵。
- 使用
joblib保存模型到models/mlp_classifier.pkl。
其中 stratify=y 是一个小但重要的细节。因为数据集很小,只有 20 条。如果随机切分时某一类样本在测试集里太少,评估结果会非常不稳定。stratify=y 可以尽量保证训练集和测试集中的亲水/疏水比例接近原始数据。
训练时,项目还做了 5 折交叉验证。交叉验证会重复多次切分训练集和验证集,比单次切分更能反映模型在小数据集上的波动。
实验结果
在当前数据和默认参数下,我运行了一次训练,得到结果如下:
数据集大小: 20 个氨基酸
疏水性氨基酸: 9 个
亲水性氨基酸: 11 个
5 折交叉验证准确率: 0.5000 (+/- 0.1581)
测试集准确率: 0.6000
混淆矩阵:
[[2 1]
[1 1]]
这个结果并不惊艳,甚至可以说很朴素。但这正是这个项目值得写出来的地方:数据只有 20 条,模型还使用了 2048 维稀疏指纹,这在机器学习里是典型的“小样本、高维特征”场景。
在这种情况下,模型很容易受到训练/测试划分影响。一次测试集准确率 0.60,并不能说明模型已经学到了稳定、可泛化的化学规律。它更多说明:这条技术链路可以跑通,模型能基于分子指纹做出一个可解释的二分类输出。
如果要做成更严肃的性质预测模型,需要更多数据、更稳健的评估方式,以及更仔细的特征和模型选择。
预测流程
训练完成后,模型会保存为:
models/mlp_classifier.pkl
预测时的流程和训练时保持一致:
- 读取已经保存的模型。
- 输入一个新的 SMILES 字符串。
- 用 RDKit 解析成
Mol。 - 转成同样参数的 Morgan 指纹。
- 输入 MLP,得到类别和概率。
运行演示:
python main.py demo
也可以进入交互模式:
python main.py interactive
预测函数在 src/predict.py,返回结果包括:
{
"smiles": smiles,
"prediction": 0 or 1,
"label": "亲水" or "疏水",
"confidence": confidence,
"hydrophobic_prob": hydrophobic_prob,
"hydrophilic_prob": hydrophilic_prob,
}
这里的 confidence 本质上是模型对预测类别的概率估计。它可以作为参考,但在这么小的数据集上,不能把它理解成严格可靠的化学置信度。
这个项目真正学到什么
我觉得这个项目的价值不在于“用 MLP 精准预测氨基酸亲疏水性”,而在于把 AI 制药中一个常见问题拆成了几个清楚的层次:
化学对象 -> 机器可读表示 -> 数值特征 -> 机器学习模型 -> 性质预测
更具体地说,它帮助我理解了:
- SMILES 是分子的文本表示,不是模型特征本身。
- RDKit 是化学信息学工具箱,不是神经网络分类器。
Mol是 RDKit 内部的结构化分子对象。- Morgan 指纹把分子局部结构编码成固定长度向量。
- MLP 接收的是数值向量,而不是原始化学名称或 SMILES。
- 亲疏水标签是人为定义的数据监督信号,模型是在学习这个标签规则。
- 小数据集上的准确率很容易波动,不能过度解读。
如果把这个项目看成一个“分子机器学习 Hello World”,它已经足够完整。
局限性
这个项目也有明显局限。
第一,数据量太小。20 个样本无法支撑复杂神经网络稳定学习,尤其输入还是 2048 维指纹。
第二,亲疏水标签本身是简化的。真实分子的亲疏水性通常会用 logP、logD、溶解度、极性表面积等更连续、更细致的指标描述,而不是简单二分类。
第三,氨基酸在不同 pH 条件下会有不同电荷状态。当前 SMILES 和标签没有显式考虑环境 pH、电离状态、两性离子形式等因素。
第四,Morgan 指纹虽然经典,但它会压缩分子信息,也可能发生哈希碰撞。对于更复杂任务,可以尝试更多分子描述符、图神经网络或预训练分子模型。
第五,模型评估还很初级。当前的交叉验证和测试集评估适合教学演示,但不适合宣称真实性能。
可以怎么继续优化
如果继续扩展这个项目,我会优先考虑这些方向:
- 增加数据量,引入更多天然或人工氨基酸及其性质数据。
- 把二分类标签换成连续性质,例如 logP 或实验溶解度。
- 加入更多 RDKit 分子描述符,例如分子量、TPSA、氢键供体/受体数量、可旋转键数量。
- 对比不同模型,例如 Logistic Regression、Random Forest、SVM、XGBoost。
- 使用留一法交叉验证,适配 20 条样本这种极小数据集。
- 做一个简单 Web 页面,让用户输入 SMILES 后实时看到预测结果。
- 增加非法 SMILES 检查和可视化分子结构展示。
这些扩展会让项目从“跑通流程”逐渐接近一个可展示、可解释、可交互的小型 AI 制药工具。
总结
AminoHydro Classifier 是一个很小的项目,但它覆盖了分子机器学习中最基础也最重要的一条链路:
分子字符串 -> 化学结构 -> 分子指纹 -> 神经网络 -> 性质预测
通过它,我更清楚地理解了 RDKit 和机器学习模型之间的分工:RDKit 负责把化学结构变成可计算特征,MLP 负责从特征中学习分类边界。
这个项目目前不适合作为严肃的药物性质预测模型,但非常适合作为 AI 制药入门练习。它足够小,小到每一步都能看懂;又足够完整,完整到可以从数据、训练、评估一直走到预测。
对我来说,这正是学习 AI 制药很好的起点:不是一上来追逐巨大的模型,而是先把一条最小的分子机器学习流水线亲手跑通。