4. SPOTlight 算法原理:非负最小二乘与主题建模
这一章我们来啃 SPOTlight 最核心的部分——它是怎么把空间位置信息和单细胞数据“拼”在一起的。坦白讲,我刚接触这个算法时也绕了好一阵,但搞清楚 NNLS 和主题建模后,后面的内容就顺畅多了。
4.1 核心思想:从混合信号到空间解卷积
空间转录组测出来的每个 spot,其实是一个“大杂烩”。它包含好几种细胞的 RNA 混合物。我们的目标很明确:从这堆混合信号里,反向推算出每种细胞各占多少。
这像什么?就像给你一杯混合果汁,你要尝出里面橙汁、苹果汁、西瓜汁各加了多少毫升。只不过 SPOTlight 尝的是转录组数据,用的是数学方法。
整个流程可以拆成两步:
- 先学“配方”——从单细胞参考数据里学出每种细胞类型的表达特征
- 再算“比例”——对每个 spot,用学到的特征去解卷积,算出细胞组成
我个人习惯把第一步叫“训练”,第二步叫“预测”。嗯,这里要注意,两个步骤用的是同一套数学框架——非负最小二乘。
4.2 非负最小二乘(NNLS):为什么它比普通回归更合适?
普通的最小二乘回归大家都很熟:给一堆自变量,求因变量,允许系数为正或为负。但问题来了——细胞比例怎么可能是负的?
NNLS 的数学表达式其实很简洁:
目标:最小化 ||Ax - b||²
约束:x ≥ 0
其中:
A —— 参考矩阵(基因 × 细胞类型)
x —— 系数向量(待求解的细胞比例)
b —— 目标向量(某个 spot 的表达谱)
我第一次用 NNLS 做空间映射时,差点踩坑——忘了对参考矩阵做“标记基因筛选”。如果你直接把所有基因扔进去,运算慢不说,细胞类型之间的区分度也不够。后来我养成了一个习惯:先用 wilcoxon 或 t-test 选出 top 50-100 个标记基因,再构建参考矩阵 A。
4.3 主题建模:让参考矩阵更“聪明”
如果你直接拿纯的细胞类型表达谱去做 NNLS,会有一个问题:同一个细胞类型在不同样本、不同状态下,表达谱其实在漂移。主题建模就是来解决这个问题的。
说白了,主题建模是一种降维+软聚类的方法。它把“同一个细胞类型”的表达谱,分解成几个“主题”(topics)。每个主题是一组共表达的基因模块。这样,参考矩阵就不是简单的细胞类型×基因,而是主题×基因。
为什么这样做更鲁棒?
- 主题能捕捉细胞状态的连续性(比如从 resting 到 activated 的过渡)
- 主题之间共享基因,但不完全重叠,提高了表达的冗余度
- 空间 spot 里的混合信号,可以用几个主题的线性组合来拟合,比用纯细胞类型更精准
我记得有人问过我:“这不就是换个方式做非负矩阵分解(NMF)吗?” 对,主题建模的数学本质和 NMF 是一样的。只是它在单细胞领域被重新包装了一下,强调“主题”代表基因的共表达模块。
4.4 SPOTlight 的完整工作流
把 NNLS 和主题建模串起来,就是 SPOTlight 的完整流程。我画个图方便理解:
流程图展示得很清楚:从单细胞数据出发,经过主题建模生成参考矩阵 A,然后结合空间数据的每个 spot 表达谱 b,用 NNLS 求解细胞比例 x。最后再把主题的权重映射回原始的细胞类型名称。
4.5 关键细节:主题数量怎么选?
这里有个现实问题:每个细胞类型应该分多少个主题?
SPOTlight 的默认做法是为每个细胞类型设置 k 个主题(k 一般在 3-10 之间)。但我在项目中发现,这个参数对结果挺敏感。
实际代码里是这样调的:
# R 伪代码,SPOTlight 中的主题建模参数
library(SPOTlight)
# 为每个细胞类型设置主题数
# 神经元:5个主题,少突胶质细胞:3个主题
ct_topic <- c("Neuron" = 5, "Oligo" = 3, "Astro" = 4)
# 构建参考矩阵
spotlight_ls <- spotlight(
reference = sce_reference, # 单细胞参考
clusters = "cell_type", # 标签列名
group = ct_topic, # 每个类型的主题数
verbose = TRUE
)
这一行就完成了主题建模+参考矩阵构建。输出结果是一个 spotlight 对象,里面包含了主题×基因的参考矩阵 A、以及每个细胞类型映射到主题的权重。
4.6 NNLS 在 SPOTlight 中的实现细节
NNLS 的求解器在 R 里有现成的包 nnls 和 quadprog。SPOTlight 用的是 R 自带 nnls 包的实现,做了两件事:
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 对每个 spot,取参考矩阵 A 中与 spot 共有的基因 | 对齐特征空间 |
| 2 | 调用 nnls(A, b),求解非负系数 x | 得到主题权重 |
| 3 | 将主题权重按细胞类型汇总 | 映射回细胞比例 |
| 4 | 归一化,使比例之和为 1 | 获得概率解释 |
这里有个微妙之处:NNLS 是不保证系数之和为 1 的,它只保证非负。所以 SPOTlight 在最后一步做了 softmax 或简单的除以总和,把系数转为比例。
我在肠道数据分析时就遇到过这种情况——隐含了“未知细胞类型”。后来我在参考数据里多加了成纤维细胞,拟合度才上去。所以,参考数据的覆盖度直接影响映射质量。
4.7 主题建模 vs. 纯 NNLS:哪个更靠谱?
你可能想问:为什么不直接用纯细胞类型表达谱做 NNLS?偏要加一层主题建模?
我拿自己的项目经验说:
- 纯 NNLS:在细胞类型区分度高的时候(比如神经元 vs. 胶质细胞),效果还行。但如果细胞类型间有连续状态(比如 T 细胞的不同激活阶段),它容易把同一种细胞的不同状态割裂开。
- 主题建模 + NNLS:主题建模先做了“软聚类”,把相似的细胞状态 merge 成主题。NNLS 求解的是主题比例,而不是直接解细胞类型。最后一步才把主题映射回细胞类型。这个“缓冲层”让结果更平滑。
说白了,主题建模就像给细胞类型加了一层“容错垫”。它在状态连续的情况下更有优势。
4.8 运行时的注意事项
最后,我整理几个实战中遇到的坑:
- 基因数量不能太少:我试过只用 top 10 标记基因,NNLS 求解不稳定,各个 spot 的系数震荡很大。建议至少 30-50 个基因。
- 避免高度相关的细胞类型:比如 B 细胞和 plasma 细胞,基因表达谱高度相似。NNLS 会在这两个类型之间分配权重不稳定。我一般会先合并,映射完再拆分。
- 内存问题:如果参考数据有 50 万细胞、3 万基因,跑主题建模会非常吃内存。我通常先做 downsampling,每个细胞类型取 200-500 个细胞,够用就行。
嗯,关于 NNLS 和主题建模的原理,就讲到这里。你把这一章消化掉,后面的“自定义参考矩阵”和“结果评估”就很好理解。说白了,SPOTlight 的核心就是一个带约束的优化问题,而主题建模是为它提供高质量参考矩阵的前处理步骤。