BBKNN 核心参数速览:neighbors、n_pcs、approx 到底调什么?
BBKNN 的参数其实不多,但每个都掐着算法的命门。我最初接触时,总以为参数越多越厉害,后来才发现,调好三五个关键参数,效果就能甩开默认值一大截。
这一章我们直接拆解三个核心参数:neighbors、n_pcs 和 approx。别的参数以后再说,先把这三尊大佛请明白了。
1. neighbors:你希望每个细胞认识几个邻居?
BBKNN 的全称是 Batch Balanced KNN。核心思想是:对于每个细胞,在 每个批次里 各找 K 个最近邻,然后合并成一个局部图。这个 K 就是 neighbors 参数。
默认值通常是 3 或 5。但实际项目中,很少直接用默认值。
- 设太小(比如 1~3):每个批次只拉来极个别邻居,局部结构可能断裂。我在处理 10X 多批次数据时,设成 2 结果出现了很多“孤儿细胞”,就是没连上任何邻居的细胞群。排查半天才发现是邻居数不够。
- 设太大(比如 15~30):每个细胞强行认识太多远亲,批次校正会变得过于平滑——说白了就是把不同生物学亚群也给抹平了。你想想看,那还怎么分群?
我的常用检查法:先跑一次小规模测试(比如每个批次 500 个细胞),对比 neighbors=3、5、10 三个值。观察 UMAP 图上有没有“孤岛”或者“过度混合”现象。如果孤岛多,增加 neighbors;如果亚群边界模糊,减小 neighbors。一般控制在 5~10 之间。
2. n_pcs:你保留了多少主成分信息?
这个参数控制 BBKNN 在哪个空间里找邻居。它接受 PCA 降维后的前 N 个主成分。说白了,就是你要用多“高清”的数据去算距离。
默认值通常是 50。但我个人的习惯是 先看方差解释率曲线再定,很少直接用 50。
import scanpy as sc
import matplotlib.pyplot as plt
# 先运行 PCA 并查看方差解释率
sc.tl.pca(adata, n_comps=100, svd_solver='arpack')
sc.pl.pca_variance_ratio(adata, n_pcs=100, log=True, show=True)
你会看到一条快速下降然后趋于平稳的曲线。拐点通常在 15~40 之间。
- n_pcs 太小(比如 5~10):保留的信息太少,批次间细微的生物学差异可能被丢掉。我曾经在脑组织数据上设 n_pcs=8,结果神经元亚群完全混在一起,根本分不开。
- n_pcs 太大(比如 80~100):引入了大量噪声,尤其对于高噪声的 scRNA-seq 数据,高维主成分往往代表技术噪声而非生物信号。你会得到“看起来很连续”但亚群结构模糊的 UMAP 图。
注意:n_pcs 与 downstream 的聚类、可视化不是独立关系。如果你后续用 leiden 聚类在 20 个 PC 上跑,那么 BBKNN 的 n_pcs 也应该接近这个数,否则邻居图本身就会带着不同的噪声偏好。我见过有人这边 BBKNN 用 50 PC,那边聚类用 15 PC,结果图上的邻居关系根本对不上。
3. approx:你要快还是要准?
这个参数是一个布尔选项(True/False)。它决定 BBKNN 是否使用 近似最近邻搜索 算法(通常是 Annoy 或 HNSW 的实现)。默认一般 False——走精确搜索。
- approx=True:速度快,尤其当细胞数 > 5 万时,速度差一个数量级以上。但代价是:找出的邻居可能不是绝对最近的,偶尔会破坏局部结构。
- approx=False:精确,但计算量随细胞数平方级增长。10 万细胞级别,精确搜索可能跑几分钟甚至更久。
我的建议很直接:
- 数据集 ≤ 3 万细胞:用 False,精确且快,不要省这一点时间。
- 数据集 3 万~10 万细胞:先试 False,如果单次运行超过 10 分钟,就切 True。
- ≥ 10 万细胞:直接 True,否则你可能要等一个小时。我处理过 30 万细胞的骨髓数据,精确搜索跑了 45 分钟,改用近似搜索就 3 分钟,肉眼基本看不出差别。
一句话总结:
neighbors 决定图的连接密度,n_pcs 决定图的信息质量,approx 决定图的构建时间。三个参数互相影响,但各自有清晰的分工。
neighbors 决定图的连接密度,n_pcs 决定图的信息质量,approx 决定图的构建时间。三个参数互相影响,但各自有清晰的分工。
参数对比速查表
| 参数 | 默认值 | 推荐范围 | 核心作用 | 我踩过的坑 |
|---|---|---|---|---|
| neighbors | 3~5 | 5~10 | 控制每个批次中邻居数的多少 | 设太小产生孤岛,设太大过度平滑 |
| n_pcs | 50 | 15~40(取决于数据复杂度) | 决定邻居计算使用的特征维度 | 直接用了默认 50,结果噪声引入太多 |
| approx | False | 取决于细胞数 | 控制是否使用近似搜索 | 小数据集用近似,偶尔造成局部结构偏移 |
核心参数之间的逻辑关系
这三个参数并不独立。我的做法是:先定 n_pcs,再调 neighbors,最后根据数据规模决定 approx。这个顺序是固定的。
一个小技巧:在调参时,可以在
scanpy.pp.neighbors() 之前先设定 use_rep='X_pca' 以及 n_pcs,然后再指定 n_neighbors。代码里长这样:
import bbknn
# 先跑 PCA
sc.tl.pca(adata, n_comps=40, svd_solver='arpack')
# 然后跑 BBKNN,这里用的就是前 40 个 PC
bbknn.bbknn(adata, n_pcs=40, neighbors_within_batch=5, approx=False)
图上这个流程挺清楚:先有 PCA 空间,n_pcs 决定你保留多少信息,neighbors 决定图密度,approx 决定构建策略,最后导向一个高质量的结果。
我每次调参都照着这个顺序来,基本不会出大乱子。你也试试看。