scGPT-spatial复现实战:空间转录组分析从原理到排坑
发布时间:2026/9/17 10:15:12 锦皓数字建站

复现 scGPT-spatial 这个项目前前后后折腾了我快两周。开头先给结论这个模型确实值得花时间去跑通尤其如果你在做空间转录组分析还在用 Leiden 聚类然后照着组织切片一个个手动注释区域那 scGPT-spatial 能帮你省掉大部分主观判断的环节。空间域注释做到后面其实是个体力活但很多人不知道scGPT-spatial 干的就是这件事——它把 scGPT 在单细胞数据上预训练出来的细胞状态理解能力迁移到空间转录组上再结合坐标信息一次性把整张切片上的细胞聚成有生物学意义的空间区域。我最初是在 scGPT 官方仓库里看到 spatial 专题的 notebook当时第一反应是这玩意不就是把单细胞模型搬到空间数据上再跑一次嵌入吗实际复现之后我才意识到里面涉及了数据格式转换、预训练权重加载、空间位置编码的拼接、以及和 Scanpy 生态的配合每一步都有不少细节。这篇博客就按照我实际复现的顺序把整个流程、原理、参数和踩过的坑完整写一遍适合正在准备上手 scGPT-spatial 的各位参考。1. 项目背景与应用场景分析1.1 为什么复现 scGPT-spatial 值得投入空间转录组技术这几年发展太快Slide-seq、Visium、MERFISH、Stereo-seq 各种平台的数据特点差异很大。拿我自己跑过的 Slide-seqV2 小鼠大脑数据来说单张切片就有几万甚至十几万个 beads空间捕获点每个点上有几十到几百个转录本数据稀疏度比单细胞还要夸张。传统做法是先用 Seurat 或者 Scanpy 做聚类再把聚类结果跟 Allen Brain Atlas 这种已知解剖结构对齐手动标注每一个 cluster 对应哪个脑区。这个过程最大的痛点在于聚类结果跟你选的参数强相关而且空间连续性往往被忽略跑出来的 cluster 在空间上一块一块的碎片化注释的时候你很难拿切片图去圆场。scGPT-spatial 解决的正是这个问题。它属于基础模型微调/零样本预测的思路scGPT 本身在超过千万级单细胞转录组数据上做过自监督预训练对细胞类型、状态、基因调控模块已经有很强的表征能力spatial 模块再加上了空间坐标编码让 Transformer 的自注意力机制不仅能看到这个点的基因表达长什么样还能看到它周围都有哪些邻居、处于什么位置。因此它输出的细胞嵌入天然同时融合了表达信息和空间信息后续做聚类时空间连续性和区域边界都更接近真实解剖结构。复现这个项目不只是为了跑一个 notebook 看效果。我的实际目的是要搞清楚这个流程能不能迁移到自己手里的空间数据上能不能把官方训练好的模型拿过来直接做零样本标注以及如果要微调自己的数据需要改哪些地方。这些问题只看论文是得不到答案的必须亲手把代码跑通、把参数调过一遍才能真正理解。1.2 复现目标与预期效果在开始之前我先明确了本次复现的边界不从头训练 scGPT 主模型而是用官方发布的预训练权重复现空间转录组的嵌入生成、聚类、区域注释和可视化这条主链路。之所以不从头训练一方面是因为从头预训练基础模型需要几十张 A100 外加海量数据个人实验室很难复现另一方面官方已经发布了在千万级细胞上训练好的权重空间任务属于下游任务加载预训练权重做迁移是完全可行的这也是 scGPT 设计时希望用户采用的方式。我这次选用的数据集是官方示例用的小鼠大脑 Slide-seqV2 数据它包含比较精细的脑区划分适合观察空间域识别效果。预期获得的结果包括每个 bead 的高维嵌入表示、基于嵌入聚类得到空间域spatial domain、每个空间域映射到参考脑区后的注释名称以及用于量化与真实注释一致性的 ARI 指标。实际上跑完之后官方声明可以达到 0.8 以上的 ARI我自己复现时在部分样本上略有波动但整体趋势是对的这点在后面会专门展开。2. 环境准备与工具选型2.1 硬件与 CUDA 版本选择先说硬件这是很多人的第一道坎。scGPT-spatial 对显存的要求比你想象的低但也不是随便一张卡都能舒舒服服跑完的。官方示例处理的是上万到十几万个 beads如果用 24GB 显存的 RTX 4090 或者 40GB 的 A100空间嵌入生成阶段基本没有压力。但我最开始试图用 16GB 显存的 V100 跑全量数据batch size 稍微调大一点就直接 OOM后来把 batch size 降到 16 才勉强通过推断时间也拉长了不少。我这里给一个经过实测的配置参考方便你对照自己手上的设备做判断配置项最小可行配置推荐配置说明GPU16GB需调小 batch size24GB 以上主要影响推理/微调速度与批大小内存64GB128GB空间数据读取与嵌入存储非常吃内存存储50GB 可用空间100GB原始数据加权重文件加的很快CUDA11.711.7 或 12.1需要与 PyTorch 版本严格匹配PyTorch1.132.0.x新版 PyTorch 可能出现 API 兼容问题我个人强烈建议直接用 PyTorch 2.0.x 配合 CUDA 11.7 的组合这是经过社区大量验证的稳定组合。不要一上来就装 PyTorch 2.4 或者最新版我在后面踩了一个版本坑跟这里的选择直接相关。2.2 安装 scGPT 与依赖库环境创建和依赖安装看起来简单实际上很容易出问题。我建议直接用 conda 创建独立环境Python 版本选 3.9 或 3.10太新的 3.11 会导致部分依赖包找不到预编译版本。conda create -n scgpt python3.9 -y conda activate scgpt pip install torch2.0.1 torchvision0.15.2 torchaudio2.0.1 --index-url https://download.pytorch.org/whl/cu117注意这里我特意指定了 PyTorch 版本不是为了复古而是 scGPT 仓库里的部分代码依赖旧版 PyTorch 的 API 行为。装好 PyTorch 之后再装 scGPT 本体和它跑空间教程需要的配套库。pip install scgpt pip install scanpy1.9.8 anndata leidenalg igraph scikit-learn einops sentencepiece这里我把几个关键依赖说明一下避免你看着名字不知道干嘛用的。scanpy 是整个空间数据的核心操作框架adata 对象、预处理、聚类可视化都在它里面einops 是 Transformer 里做张量重排的库scGPT 的注意力实现大量用到sentencepiece 则是 gene tokenizer 需要用到的分词库。还有个容易漏掉的 orjson如果你照官方仓库方式从源码安装而不是直接 pip 装 scgpt需要手动补上不然导入模块时会报错。如果你的服务器经常出现网络不稳导致下载中断的问题安装完成后我强烈建议做一次导入测试确认所有依赖都没问题。这一步能帮你省去后面跑代码跑到一半才发现缺包的尴尬。2.3 预训练模型与数据下载scGPT 官方发布了多个预训练权重常用的有人类细胞的 scGPT-human、小鼠细胞的 scGPT-mouse以及跨物种版本。做空间转录组复现时最关键的一点是选择与你的数据物种匹配的权重文件。如果用人类权重跑小鼠数据基因名对不上结果基本不可用。下载权重文件时我遇到的第一个现实问题就是文件非常大解压后好几个 GB而且从海外站点拉取速度很慢。我的处理办法是先把压缩包下载到本地电脑再通过内网工具传到服务器上比直接在服务器上等待下载要稳定得多。传上去之后解压确认目录下包含best_model.pt、args.json、vocab.json这几个关键文件。args.json记录模型结构参数加载模型时必备vocab.json是基因字典预处理阶段要把数据里的基因名称映射到字典里的 ID这里如果对不上后面肯定会报错。数据方面我用的是官方示例中的小鼠大脑 Slide-seqV2 数据。直接从原始数据下载会得到一个非常大的表达矩阵文件一次性 read 进来会让服务器内存直接爆掉。我建议第一次跑通流程时先不加载全量而是读取一部分或先用工具截取子集确认整个链路没问题之后再上全量数据。这样能避免一次失败的参数组合浪费很长时间。3. scGPT-spatial 原理拆解为什么空间信息能融入 Transformer3.1 从 scRNA 基础模型到空间转录组的迁移理解 scGPT-spatial 的原理要先明白 scGPT 本身是什么。你可以把它理解成单细胞领域的 BERT——模型在海量单细胞转录组数据上做自监督预训练把每个细胞的基因表达模式压缩成一串高维向量这个向量可以捕获细胞类型、分化状态、活跃的信号通路等信息。预训练过程不依赖任何标签只根据输入基因表达去重建被掩码掉的基因这让模型学会了基因之间复杂的共表达和调控关系。到了空间转录组场景每个捕获点本质上还是一个带有基因表达谱的微环境只不过它还附带了一个空间坐标。scGPT-spatial 的做法很直接把基因表达谱照旧编码成 token 序列在进入 Transformer 之前额外把空间坐标编码成向量和基因表达嵌入拼在一起送入注意力层。这样做的好处是模型不需要重新学习“基因表达谱长什么样”因为它已经在单细胞上预训练过了它只需要学会如何把空间信息整合进来这显然比从头训一个空间专用模型要高效得多。3.2 空间编码与注意力机制的融合这里有一个很多人没想透的问题空间坐标是二维的基因表达是几百维的两者怎么融合scGPT-spatial 采用的方式是生成一组空间位置编码positional encoding。它的核心思路是把每个 bead 的空间坐标变换成与模型维度一致的高维向量然后在进入 Transformer 时与基因表达嵌入相加或拼接。位置编码的作用是让模型感知“两个相邻的 bead 距离较近它们的位置嵌入在高维空间中也应该比较接近”从而在注意力计算时邻居关系自动获得更高的注意权重。我用一个生活化的例子帮助理解你在派对上认识一个人光听他自己讲经历基因表达你能大致判断他的职业和背景但如果再告诉你他坐在哪个位置、旁边都是什么人空间信息你对他的画像判断会准确得多。scGPT-spatial 做的就是把这个“位置信息”显式地塞进了 Transformer 的输入里。实际实现中由于 beads 数量众多Transformer 的自注意力计算量是 O(n²) 量级的直接在所有 beads 之间做全连接注意力是不现实的。所以实现里通常会对空间窗口做了限制只让每个 bead 关注其空间邻域内的其他 bead这与真实组织里的信号传导情况也更吻合。这里我给一个简化版的伪代码流程可视化大致的数据流# 1. 基因表达嵌入 expr_emb gene_embedding(gene_tokens) # 2. 空间位置编码 pos_emb positional_encoding(adata.obsm[spatial]) # 3. 注入 Transformer 前融合 hidden_state expr_emb pos_emb # 4. 多层自注意力编码 for layer in transformer_layers: hidden_state layer(hidden_state)3.3 损失函数与训练策略scGPT-spatial 并不是从零开始训练一个新模型而是基于 scGPT 预训练权重做下游适配。如果你只是做推理生成嵌入、聚类、注释那直接用预训练权重跑前向传播就行不涉及损失函数。但如果你要在自己的数据上微调需要了解它训练阶段的损失构成。除了标准的掩码基因重建损失也就是让模型根据上下文预测被 mask 掉的基因空间任务还会加入一个空间域分类损失。这个损失项的输入是模型输出层对每个 bead 的空间域类别预测结果与真实区域标签计算交叉熵。这样模型在训练时一边学会基因表达模式一边学会空间连续性最终得到的是既懂表达又懂位置的嵌入表示。从我实际测试的经验来看微调阶段最需要小心的就是损失权重配比。如果空间域分类损失的权重过大模型会过度拟合区域的不可见特征导致嵌入对细胞类型的辨识度下降如果过小空间连续性又体现不出来。建议在自己的数据上从 0.1 开始调试这个权重观察嵌入在 UMAP 上的表现再逐步调整到合理范围。4. 复现实操全流程4.1 数据预处理从原始数据到 AnnData整个复现流程基于 Scanpy 生态第一步是把空间转录组数据整理成 AnnData 对象。AnnData 可以理解为单细胞/空间组学界的万能数据结构细胞bead× 基因的表达矩阵存在.X里所有元数据都存在.obs里空间坐标则放在.obsm[spatial]。下面这段代码基于官方示例流程我加了一些自己的注释方便你理解每一步在做什么import scanpy as sc import pandas as pd import numpy as np # 读取空间表达矩阵 # counts.csv: 行为 bead ID列为基因 counts pd.read_csv(counts.csv, index_col0).T coords pd.read_csv(coords.csv, index_col0) # 构建 AnnData 对象 adata sc.AnnData(counts) adata.obsm[spatial] coords.loc[adata.obs_names].values # 基础过滤删除在极少数细胞中才出现的基因 sc.pp.filter_genes(adata, min_cells10) # 标准化让每个细胞的总表达量一致 sc.pp.normalize_total(adata, target_sum1e4) sc.pp.log1p(adata) # 选取高变基因控制后续计算规模 sc.pp.highly_variable_genes(adata, n_top_genes3000) adata adata[:, adata.var[highly_variable]].copy() print(adata.shape)这里最关键的一步是高变基因筛选。scGPT 预训练模型的词汇表里包含了几万个基因但实际计算时不需要全部输入。选择 3000 个左右的高变基因既能保留绝大部分生物学信号又能大幅压缩计算量。另外标准化方式一定要用normalize_total加log1p这与预训练时的数据分布保持一致跳过这一步会导致嵌入结果偏差非常明显。4.2 执行 scGPT-spatial 嵌入生成与空间域注释数据预处理完成后就进入核心环节加载 scGPT 模型生成包含空间信息的嵌入。这里的核心逻辑是把高变基因映射为 token把空间坐标编码后一起送入模型输出每个 bead 的高维特征向量。然后在这个特征向量上做 KMeans 聚类得到空间域。官方示例中设定聚成 25 个 cluster这个数值对应了小鼠大脑主要解剖区域的粒度你可以根据自己的数据调整。下面这段代码是整个复现的核心流程每一步我都尽量写得和实际执行脚本一致import torch import scgpt as scg from scgpt.model import TransformerModel from scgpt.tokenizer import GeneVocab # 加载基因字典与模型配置 vocab GeneVocab.from_file(scGPT_mouse/vocab.json) model TransformerModel( n_tokenlen(vocab), d_model512, nhead8, n_layers12, dropout0.0, ) model.load_state_dict(torch.load(scGPT_mouse/best_model.pt)) model.eval() model.to(cuda) # 将 AnnData 数据中的基因名映射为 token id adata.var[gene_names] adata.var_names adata.var[gene_ids] [vocab.get(g, 0) for g in adata.var_names] # 生成细胞嵌入内部会同时利用表达与空间坐标 cell_embeddings scg.model.extract_embedding( adata, model, gene_id_keygene_ids, use_fast_transformerTrue, batch_size16, ) # 嵌入聚类得到空间域 from sklearn.cluster import KMeans kmeans KMeans(n_clusters25, random_state0, n_init10) adata.obs[spatial_domain] kmeans.fit_predict(cell_embeddings)运行这段代码时我实际观察到每个 batch 的推理时间大概在几秒到十几秒之间总数据量几万个 beads 的话几分钟就能全部跑完。use_fast_transformerTrue这个参数很关键它启用了 Flash Attention 优化速度和显存占用都有明显改善但前提是你的显卡支持相应的算子。聚类完成后就进入空间域注释阶段。KMeans 给出的只是编号每一个编号对应哪个脑区需要和参考注释做映射。官方示例采用的做法是计算每个 cluster 中已知标记基因的表达特征然后与 Allen Brain Atlas 的解剖学结构对应自动生成注释标签。这段逻辑在官方示例中是核心亮点相当于把“人工肉眼识别”变成了“自动映射”。4.3 结果评估与可视化拿到空间域注释后需要用定量指标确认结果是否可靠。最常用的指标是 ARIAdjusted Rand Index它衡量两个聚类结果之间的一致性程度取值范围从 -1 到 1越接近 1 说明聚类结果与真实区域划分越一致。scGPT-spatial 论文里报告的小鼠大脑数据集 ARI 在 0.8 以上这个数字相当可观远高于传统的基于表达聚类再与空间结构对齐的方法。from sklearn.metrics import adjusted_rand_score # ground_truth: 真实的解剖结构标签 # spatial_domain: 模型聚类得到的空间域标签 ari adjusted_rand_score(adata.obs[ground_truth], adata.obs[spatial_domain]) print(fARI: {ari:.4f})但只看 ARI 还不够视觉检查是不可替代的环节。我在复现时习惯把空间域结果直接画在组织的二维切片图上观察区域边界是否连续、是否与组织形态吻合。处理方式是把scanpy.pl.embedding的basis参数设为spatial这样能直接以实际坐标绘制彩色的空间域分布图。效果好的结果脑区边界应该是平滑连续的而不是像撒胡椒面一样星星点点。5. 常见问题与排坑实录5.1 版本兼容性导致的 API 报错复现过程中遇到最多的就是版本兼容问题。我先遇到的是 PyTorch 版本太高导致模型加载时报错具体错误是TransformerModel里的forward函数在旧版本与新版本 PyTorch 之间的张量操作行为不一致导致维度对不上。后来我检查了 GitHub 仓库的 issues发现这不是个别问题很多人在 PyTorch 2.0 以上的版本都遇到过类似的坑。解决办法无他就是把 PyTorch 降到 2.0.x。如果你已经在使用更高的版本最简单的方式是重新创建 conda 环境然后通过指定版本安装。不要试图在当前环境里降级因为 torch 的依赖树非常复杂原地降级很容易把其他依赖搞坏。另外一个高发问题是 transformer 库版本过高。scGPT 依赖 Hugging Face 的 transformers 库但某些新版 API 废弃了旧函数名。如果你导入 scgpt 时报AttributeError: module transformers has no attribute PretrainedConfig之类的错误试试把 transformers 固定到 4.35 版本左右。5.2 GPU 显存不足的调整策略显存不足是空间数据分析的常态因为空间数据动辄几万个点每个点的基因表达谱展开成 token 序列后batch 再小也会占用不少显存。我实测下来如果直接用默认参数跑几万个 beads 的推理24GB 显存勉强够用但余量不大。一个很有效的策略是降低 batch size。extract_embedding里的batch_size参数默认可能偏大设成 8 或 16显存占用会明显下降代价只是推理时间变长。另一个策略是前面提到的use_fast_transformer这个优化在实际使用中能省掉至少 30% 的显存占用强烈建议开启。如果这两步做完还是 OOM那就得从数据层面想办法先用部分子集调试确认逻辑没问题后再跑全量。5.3 数据格式与内存管理问题空间转录组原始数据经常以 CSV 格式提供这种格式本身不算高效尤其是当你面对大几 GB 的矩阵时pandas 读入不仅慢而且内存占用惊人。我第一次读入完整数据时服务器直接进程被杀。后来改用后台数据读取模式并且提前用子集测试才稳定下来。这里建议几个磁盘策略第一如果数据允许多次使用提前转成 h5ad 格式它的读取速度比 CSV 快一个数量级第二读取时用backedTrue模式AnnData 支持不把所有数据加载到内存而是在需要时从磁盘读取第三高变基因筛选后的数据尽量另存一份后续所有实验都基于这份压缩后的数据跑能省不少时间。5.4 结果复现漂移问题有读者可能会问为什么我用官方代码跑出来的 ARI 没有论文里那么高这个问题我实际也遇到了。排查下来的主要原因有两个一个是数据预处理细节不一致比如标准化方式、高变基因数量的选择都会影响最终嵌入另一个是随机种子没有固定KMeans 的初始中心点选择具有随机性不同初始值会导致最终聚类结果有细微差异。解决办法是在所有涉及随机数的步骤中固定随机种子。尤其是 KMeans 这类对初始化敏感的算法设置random_state和n_init能显著提高结果稳定性。另外如果你使用的是 PyTorch 推理并有使用 cuDNN最好也设置torch.backends.cudnn.deterministic True避免卷积算子带来的微小浮动。下面整理一个故障排查速查表覆盖我遇到和高频出现的问题症状可能原因解决方案导入 scgpt 报 transformers 属性缺失新版 transformers API 变更固定 transformers4.35 后重装模型权重加载维度不匹配下载了不同物种/结构的权重检查 args.json 与模型参数是否一致推理过程 CUDA out of memorybatch size 过大降 batch size开启 fast transformer基因名映射大量为空物种不匹配或基因名格式不同确认权重物种统一基因命名方式聚类结果空间上极度碎片化缺少空间编码或坐标读取错误检查 adata.obsm[spatial] 是否包含正确坐标每次跑出来的 ARI 差异较大随机种子未固定固定 numpy、torch、sklearn 随机种子数据读取时内存被耗尽CSV 全量读入转为 h5ad 并使用 backed 模式6. 复现心得与扩展思路整个 scGPT-spatial 复现下来我最深的一个体会是这个项目的门槛不在模型本身而在环境与数据工程。模型结构就是标准的 Transformer Encoder注意力机制、嵌入拼接这些概念只要对深度学习稍有了解都能理解真正让人卡住的是各种依赖版本、内存限制、数据格式转换这些看似琐碎却必须处理好的工程问题。所以如果你正要开始复现千万不要一上来就指望一个下午跑通给自己留足时间处理环境适配问题。再分享一个我自己最受益的小技巧先把目标切小。第一次复现时不要追求全量数据和高指标先拿 2000 个 beads 的随机子集跑通全链路确认每个环节都没问题再逐步增加数据量。这样做的最大好处是你可以在几分钟内拿到一次完整的端到端反馈任何环节出错都能快速定位而不是等一个小时跑完才发现问题出在最开始的数据读取阶段。最后复现成功之后不要停在官方示例上。scGPT-spatial 最大的价值在于迁移性你可以尝试把预训练模型用到自己的空间数据上先做零样本推理看看效果再考虑是否用少量手工注释做微调。我自己的经验是当你的数据与预训练数据的物种和平台差异较大时零样本效果可能一般但微调之后往往比从零训练的模型强很多这也是基础模型方法论在单细胞领域最有价值的地方。接下来我打算继续测试 scGPT-spatial 在肿瘤切片上的表现以及结合细胞类型解卷积做更细粒度的空间微环境分析这个方向目前看起来可行后续有结果我再专门写一篇。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。