把高维的分子表示投影到二维平面,能直观看到化合物库的结构、数据集的覆盖、模型的适用域。但降维图极易被过度解读——理解每种方法保留什么、丢失什么,才能正确使用。
三种方法的对比
| PCA | t-SNE | UMAP | |
|---|---|---|---|
| 类型 | 线性 | 非线性 | 非线性 |
| 保留什么 | 全局方差结构 | 局部邻域 | 局部 + 部分全局 |
| 簇间距离有意义吗 | 有 | 没有 | 部分有 |
| 速度 | 最快 | 慢 | 较快 |
| 可复现 | 确定性 | 依赖种子 | 依赖种子 |
| 可投影新点 | 可以 | 不能 | 可以 |
| 参数敏感性 | 低 | 高(perplexity) | 中(n_neighbors) |
最关键的差异:t-SNE 图中簇与簇之间的距离没有意义。两个簇画得远,不代表它们在化学上差异更大——这是最常见的误读。
实现
import numpy as np
from rdkit import Chem
from rdkit.Chem import rdFingerprintGenerator, Descriptors
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
def featurize(smiles_list, mode="fp"):
X = []
for s in smiles_list:
m = Chem.MolFromSmiles(s)
if m is None:
continue
if mode == "fp":
X.append(np.array(gen.GetFingerprint(m)))
else:
X.append([Descriptors.MolWt(m), Descriptors.MolLogP(m),
Descriptors.TPSA(m), Descriptors.NumHDonors(m),
Descriptors.NumHAcceptors(m),
Descriptors.NumRotatableBonds(m),
Descriptors.RingCount(m),
Descriptors.FractionCSP3(m)])
return np.array(X)
# ---- PCA ----
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
def pca_projection(X, n_components=2, scale=True):
if scale:
X = StandardScaler().fit_transform(X)
pca = PCA(n_components=n_components, random_state=0)
Z = pca.fit_transform(X)
print(f"解释的方差比例: {pca.explained_variance_ratio_}")
print(f"累计: {pca.explained_variance_ratio_.sum():.1%}")
# 【关键】:如果前两个主成分只解释 15% 的方差,
# 那么这张图丢失了大部分信息
return Z, pca
# ---- t-SNE ----
from sklearn.manifold import TSNE
def tsne_projection(X, perplexity=30, seed=42):
# 【建议先用 PCA 降到 50 维再做 t-SNE】
# → 加速且降噪
if X.shape[1] > 50:
X = PCA(n_components=50, random_state=0).fit_transform(X)
return TSNE(n_components=2, perplexity=perplexity,
random_state=seed, init="pca",
metric="euclidean").fit_transform(X)
# 【perplexity 的影响很大】:
# 5 → 强调很局部的结构,簇多而碎
# 30 → 常用默认
# 50+ → 更全局的结构
# → 【应该试几个值,看结论是否稳定】
# ---- UMAP ----
# pip install umap-learn
import umap
def umap_projection(X, n_neighbors=15, min_dist=0.1, seed=42,
metric="jaccard"):
# 【对二值指纹,用 jaccard 距离而非欧氏距离】
reducer = umap.UMAP(n_components=2, n_neighbors=n_neighbors,
min_dist=min_dist, metric=metric,
random_state=seed)
return reducer.fit_transform(X), reducer
# 【UMAP 的优势】:
# 1) 比 t-SNE 快
# 2) 【可以 transform 新数据】
# → 训练一次,之后可以把新分子投影到同一空间
# 3) 【支持 jaccard 等适合二值数据的距离】
# 4) 部分保留全局结构
可视化与着色
import matplotlib.pyplot as plt
def plot_chemical_space(Z, color_by=None, labels=None, title="",
cmap="viridis"):
fig, ax = plt.subplots(figsize=(8, 7))
if color_by is not None:
sc = ax.scatter(Z[:, 0], Z[:, 1], c=color_by, s=8,
alpha=0.6, cmap=cmap)
plt.colorbar(sc, ax=ax)
else:
ax.scatter(Z[:, 0], Z[:, 1], s=8, alpha=0.6)
ax.set_title(title)
ax.set_xlabel("维度 1")
ax.set_ylabel("维度 2")
return fig
# 【着色维度决定了这张图回答什么问题】:
#
# 按活性着色
# → 【活性分子是否聚集在特定区域?】
# → 如果分散,说明多个不同的作用模式
#
# 按数据集来源着色(训练/测试)
# → 【划分是否合理?】
# → 测试集与训练集完全重叠 = 划分太松(见 051)
#
# 按模型预测误差着色
# → 【模型在哪些区域表现差?】
# → 系统性的误差区域提示适用域边界
#
# 按骨架/聚类着色
# → 【化学系列的分布】
#
# 按物化性质着色(logP、MW)
# → 库的性质覆盖
#
# 按不确定性着色(见 166)
# → 【模型在哪里没把握】
#
# 【多个数据集叠加】:
# 自家化合物库 vs 商业库 vs 已上市药物
# → 【直观看出「我们在化学空间的哪个位置」】
# → 常常是有启发的:
# 很多公司的库集中在很小的区域
解读的陷阱
# 【陷阱一:过度解读簇间距离】
# t-SNE 图中,两个簇画得远
# → 【不代表它们化学上差异更大】
# → t-SNE 只保证「近的还是近的」
#
# 【陷阱二:把簇当作有意义的分类】
# 降维产生的簇可能只是算法的产物
# → 【应该用原始空间的聚类验证】(见 050)
# → 或人工检查簇内的分子是否真的相似
#
# 【陷阱三:忽略解释的方差】
# PCA 前两个主成分可能只解释 10~20% 的方差
# → 【图上看起来分开的点,在完整空间中可能很近】
# → 【必须报告解释的方差比例】
#
# 【陷阱四:参数敏感性】
# 换一个 perplexity 或随机种子,图可能大不相同
# → 【任何从图中得出的结论,
# 都应该在多个参数下验证】
#
# 【陷阱五:距离度量不匹配】
# 二值指纹用欧氏距离
# → 【不符合化学相似性的直觉】
# → 应该用 jaccard(= 1 - Tanimoto)
#
# 【陷阱六:把可视化当作结论】
# 降维图是【探索工具】,不是【证据】
# → 从图中发现的模式,
# 应该在原始空间中定量验证
# 【一个稳健性检查】:
def stability_check(X, method="umap", n_seeds=3):
"""用不同种子生成投影,看结论是否稳定"""
projections = []
for seed in range(n_seeds):
if method == "umap":
Z, _ = umap_projection(X, seed=seed)
else:
Z = tsne_projection(X, seed=seed)
projections.append(Z)
# 【比较:同一对分子在不同投影中的相对位置是否一致】
return projections
实用的应用场景
| 场景 | 做法 |
|---|---|
| 检查数据划分 | 训练/测试着色,看是否重叠(见 051《Scaffold Split》) |
| 诊断模型的适用域 | 按预测误差着色(见 166《不确定性估计》) |
| 评估库的多样性 | 看覆盖的面积与密度(见 053《分子多样性评估》) |
| 比较不同的库 | 叠加显示 |
| 展示筛选结果 | 标出命中分子的位置 |
| 跟踪项目进展 | 不同时期的分子着不同色 |
| 与同事沟通 | 一张图胜过一堆数字 |
最有价值的用法是「发现意料之外的模式」:比如发现活性分子集中在两个完全分开的区域(提示两种结合模式),或发现模型误差大的分子集中在某个区域(提示适用域边界)。但发现之后必须在原始空间中定量验证。
关键要点
- t-SNE 图中簇间距离没有意义——这是最常见的误读;
- PCA 必须报告解释的方差比例——只解释 15% 的图丢失了大部分信息;
- 二值指纹应该用 jaccard 距离而非欧氏距离;UMAP 支持且可投影新数据;
- 降维图是探索工具不是证据——发现的模式必须在原始空间定量验证。
延伸资源
- 聚类:050《分子相似性聚类》;多样性评估:053《分子多样性评估》;Tanimoto:039《Tanimoto 相似性》;
- Scaffold Split:051《Scaffold Split》;不确定性:166《不确定性估计》。