
scvi-tools 深度学习单细胞分析:快速入门
Written By

技能练习生
30 分钟上手你的第一个 scvi-tools 项目
本章通过一个简化案例,带你快速掌握 scvi-tools 的核心工作流程。即使没有深度学习基础,也能轻松上手。
实战案例:整合两批次 PBMC 数据
场景描述
你获得了两个批次的 PBMC(外周血单核细胞)scRNA-seq 数据:
- 批次 1:健康供体,10X v2 化学,5,000 细胞
- 批次 2:健康供体,10X v3 化学,8,000 细胞
目标:整合两批次数据,去除技术批次效应,保留真实的生物学差异。
预期输出
- 整合后的低维表示(潜在空间)
- 校正批次效应的 UMAP 可视化
- 聚类结果和细胞类型注释
- 差异表达基因列表
步骤 1:环境准备与数据加载 [5 分钟]
1.1 安装依赖
# 创建新环境(推荐)
conda create -n scvi-tutorial python=3.10 -y
conda activate scvi-tutorial
# 安装核心包
pip install scvi-tools scanpy anndata matplotlib seaborn
# 验证安装
python -c "import scvi; print(scvi.__version__)"1.2 准备数据
假设你已有两个 .h5ad 文件(如果没有,可使用 scanpy 的示例数据):
import scanpy as sc
import anndata as ad
# 加载数据
batch1 = sc.read_h5ad('pbmc_batch1.h5ad')
batch2 = sc.read_h5ad('pbmc_batch2.h5ad')
# 检查数据结构
print(f"Batch 1: {batch1.n_obs} cells × {batch1.n_vars} genes")
print(f"Batch 2: {batch2.n_obs} cells × {batch2.n_vars} genes")
# 关键检查:确保 X 是原始计数(未归一化)
print(f"Data type: {batch1.X.dtype}") # 应该是整数类型
print(f"Sample values: {batch1.X[0, :5].toarray()}") # 查看前 5 个值⚠️ 重要提示:
- scvi-tools 必须使用原始计数,不能是 log-normalized 数据
- 如果你的数据已经归一化,需要重新加载原始数据或从已保存的层中恢复
1.3 合并批次
# 添加批次标识
batch1.obs['batch'] = 'batch1'
batch2.obs['batch'] = 'batch2'
# 合并数据
adata = batch1.concatenate(batch2, index_unique=None)
print(f"合并后:{adata.n_obs} cells × {adata.n_vars} genes")
print(f"批次分布:\n{adata.obs['batch'].value_counts()}")步骤 2:数据预处理 [10 分钟]
2.1 质量控制(假设已完成)
我们假设你的数据已通过基本 QC(过滤低质量细胞和基因)。如果尚未完成,可使用单细胞 QC 技能进行预处理。
2.2 高变基因选择
# 使用 Seurat v3 方法选择高变基因(推荐)
sc.pp.highly_variable_genes(
adata,
n_top_genes=2000, # 通常选择 2000-4000 个基因
batch_key='batch', # 考虑批次信息
flavor='seurat_v3',
layer='counts', # 如果有单独的 counts 层
subset=True # 直接过滤到高变基因
)
print(f"高变基因数量:{adata.var['highly_variable'].sum()}")2.3 保存原始计数
scvi-tools 需要访问原始计数,我们将其保存在单独的层:
# 确保 counts 层存在
if 'counts' not in adata.layers:
adata.layers['counts'] = adata.X.copy()
# 验证
print(f"原始计数类型:{adata.layers['counts'].dtype}")步骤 3:模型训练 [10 分钟]
3.1 设置 AnnData
import scvi
# 设置 scVI 模型
scvi.model.SCVI.setup_anndata(
adata,
layer='counts', # 指定原始计数层
batch_key='batch' # 批次信息键名
)参数解释:
layer:原始计数所在的层(必需)batch_key:批次标识的列名(整合任务必需)
3.2 初始化模型
# 创建 scVI 模型
model = scvi.model.SCVI(
adata,
latent_dim=30, # 潜在空间维度(默认 10,通常 20-50)
n_layers=2, # 神经网络层数(默认 2)
n_hidden=128, # 隐藏层神经元数(默认 128)
)超参数说明:
latent_dim:潜在表示维度,越大保留信息越多,但过拟合风险增加n_layers和n_hidden:控制神经网络复杂度,通常使用默认值
3.3 训练模型
# 训练模型(GPU 自动检测并使用)
model.train(
max_epochs=100, # 训练轮数(默认 400,可减少用于快速测试)
batch_size=256, # 批大小(默认 128)
early_stopping=True, # 早停策略
early_stopping_patience=10, # 容忍轮数
use_gpu=True # 自动检测并使用 GPU
)
# 查看训练历史
import matplotlib.pyplot as plt
plt.plot(model.history['elbo_train'])
plt.xlabel('Epoch')
plt.ylabel('ELBO')
plt.title('Training Loss')
plt.show()训练时间参考:
- CPU:13,000 细胞约 5-10 分钟
- GPU:13,000 细胞约 1-2 分钟
3.4 获取潜在表示
# 提取潜在表示(批次校正后的低维嵌入)
adata.obsm["X_scvi"] = model.get_latent_representation()
print(f"潜在表示形状:{adata.obsm['X_scvi'].shape}")
# 应该是 (n_cells, latent_dim)步骤 4:下游分析 [5 分钟]
4.1 聚类与可视化
# 使用 scVI 潜在表示进行聚类
sc.pp.neighbors(adata, use_rep='X_scvi')
sc.tl.leiden(adata, resolution=0.5)
sc.tl.umap(adata)
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 按批次着色(应该混合良好)
sc.pl.umap(adata, color='batch', ax=axes[0], show=False)
# 按聚类着色
sc.pl.umap(adata, color='leiden', ax=axes[1], show=False)
plt.tight_layout()
plt.show()预期结果:
- 左图:不同批次的细胞在 UMAP 上充分混合
- 右图:显示出清晰的细胞簇结构
4.2 差异表达分析
scvi-tools 提供了基于零膨胀负二项分布的 DE 分析,比传统方法更准确:
# 获取每个簇的标志基因
de_df = model.differential_expression(
groupby='leiden',
group1='0', # 簇 0
group2='1' # 簇 1
)
print(f"差异表达基因数量:{len(de_df)}")
print(de_df.head(10)) # 显示前 10 个最显著的基因4.3 保存结果
# 保存 AnnData 对象
adata.write('pbmc_integrated.h5ad')
# 保存模型(用于后续的标签转移或新数据映射)
model.save('pbmc_scvi_model/')
print("分析完成!结果已保存。")完整代码清单
# ============ 完整流程 ============
import scanpy as sc
import scvi
import matplotlib.pyplot as plt
# 1. 加载和合并数据
batch1 = sc.read_h5ad('pbmc_batch1.h5ad')
batch2 = sc.read_h5ad('pbmc_batch2.h5ad')
batch1.obs['batch'], batch2.obs['batch'] = 'batch1', 'batch2'
adata = batch1.concatenate(batch2, index_unique=None)
# 2. 数据准备
adata.layers['counts'] = adata.X.copy()
sc.pp.highly_variable_genes(
adata,
n_top_genes=2000,
batch_key='batch',
flavor='seurat_v3',
layer='counts',
subset=True
)
# 3. 训练 scVI 模型
scvi.model.SCVI.setup_anndata(adata, layer='counts', batch_key='batch')
model = scvi.model.SCVI(adata, latent_dim=30)
model.train(max_epochs=100, use_gpu=True)
# 4. 获取潜在表示和下游分析
adata.obsm["X_scvi"] = model.get_latent_representation()
sc.pp.neighbors(adata, use_rep='X_scvi')
sc.tl.leiden(adata, resolution=0.5)
sc.tl.umap(adata)
# 5. 可视化
sc.pl.umap(adata, color=['batch', 'leiden'], wspace=0.4)
# 6. 保存
adata.write('pbmc_integrated.h5ad')
model.save('pbmc_scvi_model/')常见问题排查
❌ 问题 1:训练损失不下降
可能原因:
- 数据未正确归一化或包含异常值
- 学习率不合适
解决方案:
# 检查数据分布
import seaborn as sns
sns.histplot(adata.layers['counts'].data)
plt.xlim(0, 100) # 查看低表达区域
# 如果发现异常值,重新过滤
sc.pp.filter_cells(adata, min_counts=1000)
sc.pp.filter_genes(adata, min_cells=10)❌ 问题 2:批次效应仍然存在
可能原因:
batch_key参数未正确指定- 模型未充分训练
解决方案:
# 检查 batch_key 是否正确
print(adata.obs['batch'].unique())
# 增加训练轮数
model.train(max_epochs=400)❌ 问题 3:内存不足
解决方案:
# 减少批大小
model.train(batch_size=64)
# 或减少高变基因数量
sc.pp.highly_variable_genes(adata, n_top_genes=1000, ...)进阶提示
使用参考数据进行标签转移
如果你有已注释的参考数据集,可以使用 scANVI 进行标签转移:
# 假设参考数据有 'cell_type' 列
adata.obs['cell_type'] = adata.obs['cell_type'].astype(str)
adata.obs['cell_type'][adata.obs['batch'] == 'batch2'] = 'Unknown'
# 使用 scANVI
scvi.model.SCANVI.setup_anndata(
adata,
layer='counts',
batch_key='batch',
labels_key='cell_type',
unlabeled_category='Unknown'
)
scanvi_model = scvi.model.SCANVI.from_scvi_model(model, unlabeled_category='Unknown')
scanvi_model.train(max_epochs=50)
# 获取预测的细胞类型
adata.obs['predicted_cell_type'] = scanvi_model.predict()下一步
恭喜你已完成第一个 scvi-tools 分析!现在你应该能够:
- ✅ 加载和预处理单细胞数据
- ✅ 训练 scVI 模型进行批次校正
- ✅ 执行下游分析和可视化
在第三章中,我们将深入探讨:
- 各种模型的详细使用场景
- 超参数调优策略
- 处理多模态数据
- GPU 加速技巧
→ 继续阅读:第三章 - 功能详解