第三章 DeepChem 三大数据结构:Dataset、Molecule、Featurizer
做 ADMET 预测,说白了,就是在和数据打交道。你手头的分子数据,得先变成 DeepChem 能理解的东西,它才能帮你干活。这中间有三个核心角色:Dataset(数据集容器)、Molecule(分子对象)、Featurizer(特征化器)。
我个人习惯,把这三者的关系想象成一条流水线:Featurizer 是原料处理机,Molecule 是中间件,Dataset 是成品仓库。今天我们就把这套流水线给它捋顺了。
3.1 Dataset 的创建与操作
Dataset 是 DeepChem 里的“数据框”。它不像 pandas 那么万能,但专门为化学数据做了优化。我之前做项目时,直接拿 pandas 传给模型,结果格式不对报错,折腾半天。后来学乖了,一律先转成 dc.data.Dataset。
创建 Dataset 最常用的方式,是调用 dc.data.DiskDataset 或 dc.data.NumpyDataset。两者的区别?
| 类型 | 存储方式 | 适用场景 |
|---|---|---|
| DiskDataset | 硬盘(HDF5/CSV) | 数据量大,无法全部载入内存 |
| NumpyDataset | 内存 | 数据量小,快速原型验证 |
3.1.1 从 NumPy 转 Dataset
import deepchem as dc
import numpy as np
# 假数据:100 个样本,每个样本 2048 维特征
X = np.random.rand(100, 2048)
y = np.random.rand(100, 1) # 回归目标
w = np.ones((100, 1)) # 样本权重
ids = np.array([f"mol_{i}" for i in range(100)])
dataset = dc.data.NumpyDataset(X, y, w, ids)
你看,参数就是 (特征, 标签, 权重, ID)。权重在 ADMET 里很实用——比如某个实验数据置信度低,我就给它赋 0.5。
3.1.2 从 CSV 文件加载
实战中,CSV 是最常见的数据来源。比如你有一个 ADMET 数据集:
# 假设已有 CSV 文件: data.csv
# 列名包括 smiles, solubility, toxicity
loader = dc.data.CSVLoader(
tasks=["solubility", "toxicity"],
feature_field="smiles",
featurizer=dc.feat.CircularFingerprint(size=1024)
)
dataset = loader.create_dataset("data.csv")
这里 CSVLoader 自动帮你做了两件事:读文件、跑特征化。你连迭代都得省了。
3.1.3 Dataset 的常用操作
# 查看形状
print(dataset.get_shape()) # 返回 (样本数, 特征维度)
# 迭代数据
for X_batch, y_batch, w_batch, ids_batch in dataset.iterbatches(batch_size=32):
print(X_batch.shape)
# 合并两个数据集
combined = dc.data.DiskDataset.merge([dataset1, dataset2])
# 筛选(按 ID 索引)
subset = dataset.select([0, 2, 5]) # 取第 0、2、5 条
为什么我推荐用 iterbatches?因为它返回的是生成器,不会一下子撑爆内存。你想想看,一个 ADMET 数据集动辄几十万分子,全读进来你电脑马上就挂。
3.2 Molecule 对象的深度理解
Molecule 是 DeepChem 对化学分子的封装。它不是 RDKit 的 Mol 对象,而是在它上面加了一层“便利层”。
说实话,我刚开始用 DeepChem 时,总觉得 Molecule 有点多余。后来发现在做分子属性计算、格式转换时,它省了我不少代码量。
3.2.1 创建 Molecule 对象
from deepchem.utils import Molecule
# 从 SMILES 创建
mol = Molecule("CCO") # 乙醇
# 从 RDKit Mol 对象创建
from rdkit import Chem
rdkit_mol = Chem.MolFromSmiles("CC(=O)O")
mol = Molecule.from_rdkit(rdkit_mol)
# 从 SDF 文件创建(返回列表)
mols = Molecule.from_sdf("molecules.sdf")
3.2.2 常用属性访问
mol = Molecule("CCO")
# 获取 SMILES(规范化后)
print(mol.smiles) # 输出: CCO
# 获取分子量
print(mol.molecular_weight) # 46.07
# 获取原子数
print(mol.num_atoms) # 3
# 访问 RDKit Mol
rdkit_mol = mol.to_rdkit()
molecular_weight 这个属性,我在做 ADMET 早期筛选中天天用。脂溶性分子通常分子量小,水溶性大分子往往吸收差——这些经验可以直接用代码量化。
mol.to_rdkit() 拿原生 RDKit 对象来做。等处理完了,再通过 Molecule.from_rdkit() 装回来。
3.3 Featurizer 的实战运用
Featurizer 是连接“化学世界”和“机器学习世界”的桥梁。说白了,就是把分子结构转成数值向量。
我见过不少新手,直接扔一个 SMILES 给模型,然后问为什么报错。嗯,模型不认识字符串,只认识数字。
3.3.1 常用的 Featurizer 一览
| Featurizer 名称 | 输出维度 | 特点 |
|---|---|---|
| CircularFingerprint | 可配置(默认 1024) | 基于 ECFP,速度快,通用性好 |
| RDKitDescriptors | 约 200 | 包含分子量、logP、氢键供体数等物理化学描述符 |
| GraphConvFeaturizer | 图结构(节点 + 边) | 用于 GNN,保留拓扑信息 |
| WeaveFeaturizer | 图结构 | Weave 网络专用,考虑原子对相互作用 |
3.3.2 实战:从头到尾跑一遍
我们来做一个完整的流程——从三分子开始,到特征化,再到数据集:
import deepchem as dc
from deepchem.utils import Molecule
# 1. 创建 Molecule 对象列表
smiles_list = ["CCO", "CC(=O)O", "c1ccccc1"]
mols = [Molecule(s) for s in smiles_list]
# 2. 选择 Featurizer
featurizer = dc.feat.CircularFingerprint(size=1024)
# 3. 逐一手动特征化
features = []
for mol in mols:
# 注意: featurize 方法接受 RDKit Mol 对象或 SMILES
feat = featurizer.featurize(mol.to_rdkit())
features.append(feat[0]) # featurize 返回二维数组
# 4. 转为 numpy array
import numpy as np
X = np.array(features)
y = np.array([[0.5], [0.8], [0.3]]) # 假标签
# 5. 构建 Dataset
dataset = dc.data.NumpyDataset(X, y)
# 6. 交给模型
print(dataset.get_shape()) # 输出:(3, 1024)
3.3.3 避坑指南:特征对齐
我踩过一个大坑:训练集用 CircularFingerprint(size=1024),测试时不小心用了 RDKitDescriptors,结果模型直接报维度不匹配。为什么?因为两个 Featurizer 输出维度完全不同。
记住:
- 特征化器一旦选定,不能中途换
- 训练和预测必须用同一个 Featurizer 实例
- 保存 Dataset 时顺便把 Featurizer 的名称记下来,方便复盘
# 推荐的做法:序列化 Featurizer 配置
import json
featurizer_config = {
"name": "CircularFingerprint",
"params": {"size": 1024, "radius": 2}
}
with open("featurizer_config.json", "w") as f:
json.dump(featurizer_config, f)
# 下次加载时重建
featurizer = dc.feat.CircularFingerprint(size=1024)
3.4 三者协同:一套完整的构建脚本
展示一个我在实际项目中用过的片段,从原始 CSV 到完备 Dataset,再到保存:
import deepchem as dc
import pandas as pd
from deepchem.utils import Molecule
# 1. 读取原始 CSV
raw_df = pd.read_csv("admet_data.csv")
smiles_list = raw_df["SMILES"].tolist()
labels = raw_df[["logD", "Caco2_Papp", "CYP3A4_inhibition"]].values
# 2. 特征化
featurizer = dc.feat.RDKitDescriptors()
features = []
failed_indices = []
for i, smi in enumerate(smiles_list):
try:
mol = Molecule(smi)
feat = featurizer.featurize(mol.to_rdkit())
features.append(feat[0])
except Exception as e:
failed_indices.append(i)
continue
# 3. 过滤掉失败的样本
labels_valid = np.delete(labels, failed_indices, axis=0)
# 4. 构建 Dataset
X = np.array(features)
dataset = dc.data.NumpyDataset(X, labels_valid)
# 5. 保存到硬盘
dc.data.DiskDataset.from_numpy(
X, labels_valid,
data_dir="./processed_admet_dataset"
)
print(f"成功处理 {len(features)} 个分子,跳过 {len(failed_indices)} 个")
这段代码里,我特意加了一个 try...except。为什么?因为现实中的 ADMET 数据很脏——有的 SMILES 格式不对,有的分子包含特殊元素(如金属配合物)导致特征化出错。如果不处理异常,一个坏分子就能让整个流程中断。
章节小结:Dataset、Molecule、Featurizer 三者构成了 DeepChem 数据层的基石。你只要记住:Molecule 封装化学信息,Featurizer 做数字转换,Dataset 做批量管理。三管齐下,再混乱的原始数据也能变成规整的模型输入。