單細胞交響樂16-處理大型數據

劉小澤寫於2020.7.18
爲何取名叫“交響樂”?因爲單細胞分析就像一個大樂團,需要各個流程的協同配合
單細胞交響樂1-常用的數據結構SingleCellExperiment
單細胞交響樂2-scRNAseq從實驗到下游簡介
單細胞交響樂3-細胞質控
單細胞交響樂4-歸一化
單細胞交響樂5-挑選高變化基因
單細胞交響樂6-降維
單細胞交響樂7-聚類分羣
單細胞交響樂8-marker基因檢測
單細胞交響樂9-細胞類型註釋
單細胞交響樂9-細胞類型註釋
單細胞交響樂10-數據集整合後的批次矯正
單細胞交響樂11-多樣本間差異分析
單細胞交響樂12-檢測Doublet
單細胞交響樂13-細胞週期推斷
單細胞交響樂14-細胞軌跡推斷
單細胞交響樂15-scRNA與蛋白丰度信息結合

1 前言

scRNA-seq技術越來越成熟,測的細胞數也在日益增長,越來越多的研究結果發表在GEO數據庫中,而且還有大型單細胞項目的開展(例如人類圖譜計劃HCA)。因此分析方法需要考慮到逐漸龐大的數據集,這次就來看看怎麼做可以幫助我們獲得更快的處理速度和分析效率。

2 快速估算

2.1 近似而非精確的近鄰搜索

在高維空間中對近鄰細胞進行判斷基本上是必備流程,像buildSNNGraph(), doubletCells() 都是需要經歷這一步。默認是利用KNN(k-nearest neighbours)算法找到更準確的近鄰數量,而犧牲速度。但是大型數據更需要的是速度,例如這個BiocNeighbors R包就可以通過BNPARAM=輕鬆轉換近鄰搜索方法

以pbmc數據爲例,這個數據之前應該分享過

load('clustered.sce.pbmc.RData')
sce.pbmc

## class: SingleCellExperiment 
## dim: 33694 3922 
## metadata(1): Samples
## assays(2): counts logcounts
## rownames(33694): RP11-34P13.3 FAM138A ... AC213203.1 FAM231B
## rowData names(2): ID Symbol
## colnames(3922): AAACCTGAGAAGGCCT-1 AAACCTGAGACAGACC-1 ...
##   TTTGTCACAGGTCCAC-1 TTTGTCATCCCAAGAT-1
## colData names(4): Sample Barcode sizeFactor label
## reducedDimNames(3): PCA TSNE UMAP
## altExpNames(0):

看到這裏的數據是經過了PCA、t-SNE、UMAP降維操作的,並已經做好了分羣,使用的也是默認的精確KNN搜索。

下面使用近似搜索:

AnnoyParam的解釋是:A class to hold parameters for the Annoy algorithm for approximate nearest neighbor identification. 也就是它包裝了近似搜索的一套參數,並傳遞給buildSNNGraph

library(scran)
library(BiocNeighbors)
snn.gr <- buildSNNGraph(sce.pbmc, BNPARAM=AnnoyParam(), use.dimred="PCA")
clusters <- igraph::cluster_walktrap(snn.gr)
table(Exact=colLabels(sce.pbmc), Approx=clusters$membership)

這裏因爲數據量不大,並看不出速度上的優勢,只是爲了證明二者在準確度上相差無幾

2.2 奇異值分解

術語叫做:singular value decomposition (SVD),在 denoisePCA(), fastMNN(), doubletCells()都有應用。默認使用base::svd() 也是進行了精確的計算,同樣不適合大型數據集。好在 irlbarsvd 兩個R包都提供了近似計算方法,具體的參數配置和AnnoyParam 一樣也是做成了BiocSingular包的一個函數

library(scater)
library(BiocSingular)

# 方法一:randomized SVD (RSVD)
set.seed(101000)
r.out <- runPCA(sce.pbmc, ncomponents=20, BSPARAM=RandomParam())
str(reducedDim(r.out))
##  num [1:3922, 1:20] 15.3 13.41 -8.46 -7.86 6.38 ...
##  - attr(*, "dimnames")=List of 2
##   ..$ : chr [1:3922] "AAACCTGAGAAGGCCT-1" "AAACCTGAGACAGACC-1" "AAACCTGAGGCATGGT-1" "AAACCTGCAAGGTTCT-1" ...
##   ..$ : chr [1:20] "PC1" "PC2" "PC3" "PC4" ...
##  - attr(*, "percentVar")= num [1:20] 20.26 10.02 5.36 2.19 1.41 ...
##  - attr(*, "rotation")= num [1:500, 1:20] 0.2015 0.182 0.1764 0.1067 0.0649 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:500] "LYZ" "S100A9" "S100A8" "HLA-DRA" ...
##   .. ..$ : chr [1:20] "PC1" "PC2" "PC3" "PC4" ...

# 方法二:IRLBA
set.seed(101001)
i.out <- runPCA(sce.pbmc, ncomponents=20, BSPARAM=IrlbaParam())
str(reducedDim(i.out))
##  num [1:3922, 1:20] 15.3 13.41 -8.46 -7.86 6.38 ...
##  - attr(*, "dimnames")=List of 2
##   ..$ : chr [1:3922] "AAACCTGAGAAGGCCT-1" "AAACCTGAGACAGACC-1" "AAACCTGAGGCATGGT-1" "AAACCTGCAAGGTTCT-1" ...
##   ..$ : chr [1:20] "PC1" "PC2" "PC3" "PC4" ...
##  - attr(*, "percentVar")= num [1:20] 20.26 10.02 5.36 2.19 1.41 ...
##  - attr(*, "rotation")= num [1:500, 1:20] 0.2015 0.182 0.1764 0.1067 0.0649 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:500] "LYZ" "S100A9" "S100A8" "HLA-DRA" ...
##   .. ..$ : chr [1:20] "PC1" "PC2" "PC3" "PC4" ...

這兩種方法的速度都比準確的SVD方法快,丟失的準確度也微乎其微,也因此被scran和scater包設爲了默認的參數,也因此需要設置隨機種子(畢竟都是估計的方法,每次運行結果都不一致)。當然,IRLBA相比RSVD更準確,但速度不如RSVD。

3 並行計算

3.1 爲什麼?

參考:https://cosx.org/2016/09/r-and-parallel-computing/

R 採用的是內存計算模式(In-Memory),被處理的數據需要預取到主存(RAM)中。其優點是計算效率高、速度快,但缺點是這樣一來能處理的問題規模就非常有限(小於 RAM 的大小)。另一方面,R 的核心(R core)是一個單線程的程序,在多核處理器上,R 無法有效地利用所有的計算內核。即使機器性能很強大,有32個核心,但它也只能使用1/32的計算能力,浪費了31/32。

3.2 怎麼做?

使用BiocParallel包,可以將並行運算覆蓋到基於Bioconductor的分析中

不同的硬件和操作系統,選擇的並行方法也不同

參考:https://bioconductor.org/packages/3.11/bioc/vignettes/BiocParallel/inst/doc/Introduction_To_BiocParallel.pdf

library(BiocParallel)
registered() #可以看看支持哪些類型的加速,最頂上的是默認的

一般包括:

  • SerialParam:全平臺支持
  • MulticoreParam:適用於Unix和Mac。在windows上它等同於SerialParam
  • SnowParam:全平臺支持
  • BatchtoolsParam:集羣
  • DoparParam:全平臺支持

如果要更改

# 本來的default是 MulticoreParam
default <- registered()

# 現在改成BatchtoolsParam
register(BatchtoolsParam(workers = 10), default = TRUE)
names(registered())
## [1] "BatchtoolsParam" "MulticoreParam"  "SnowParam"       "SerialParam"

# 要再恢復原來的設置
for (param in rev(default)) register(param)

可以這麼使用

# 不同的方法
dec.pbmc.mc <- modelGeneVar(sce.pbmc, BPPARAM=MulticoreParam(2))
dec.pbmc.snow <- modelGeneVar(sce.pbmc, BPPARAM=SnowParam(5))

在SLURM HPC中,可以使用BatchtoolsParam

參考:https://bioconductor.org/packages/3.11/BiocParallel/vignettes/BiocParallel_BatchtoolsParam.pdf

# 設置每個任務2小時、8G內存、1CPU,總共10個任務
bpp <- BatchtoolsParam(10, cluster="slurm",
    resources=list(walltime=7200, memory=8000, ncpus=1))
注意
  • 這個並行計算只是加速了CPU的計算速度,但如果任務受限於內存或硬盤的讀入讀出,它依然是沒辦法加速的;
  • R實現多線程還是很麻煩的,它的操作邏輯是:先設置一個或多個session(對話窗口);然後加載相關的包;session之間進行數據傳遞。可能最後還不如單線程運行效果好

4 可能會遇到內存不足

想一下,我們處理的核心是不是基於表達矩陣?既然是核心,就需要完全載入內存中,然後才能實現後面的順利讀取、處理。現在單細胞的表達矩陣有兩種形式:稀疏矩陣dgCMatrix或者普通矩陣matrix 。如果我們的數據是10X產生的130萬個腦細胞數據,如果是以普通矩陣讀入,就需要消耗100G內存,即使使用稀疏矩陣,也需要消耗30G內存左右。因此沒有很好的服務器基本上幹不了這個事。

用處理速度換取內存不足

如果真的面臨無法增加內存的困境,還有一個plan B,就是用硬盤空間來存儲數據,必要時再調用一部分數據到內存,雖然這一來一回很影響處理速度,但畢竟可以用。

使用這個HDF5ArrayR包可以做到(類似的還有 bigmemory, matter),它會將底層數據做成HDF5格式

例如,從130萬個腦細胞數據中選取了2萬個

library(TENxBrainData)
sce.brain <- TENxBrainData20k() 
sce.brain
## class: SingleCellExperiment 
## dim: 27998 20000 
## metadata(0):
## assays(1): counts
## rownames: NULL
## rowData names(2): Ensembl Symbol
## colnames: NULL
## colData names(4): Barcode Sequence Library Mouse
## reducedDimNames(0):
## altExpNames(0):

看一下這個表達矩陣,顯然是一個HDF5的矩陣

counts(sce.brain)
## <27998 x 20000> matrix of class HDF5Matrix and type "integer":
##              [,1]     [,2]     [,3]     [,4] ... [,19997] [,19998] [,19999]
##     [1,]        0        0        0        0   .        0        0        0
##     [2,]        0        0        0        0   .        0        0        0
##     [3,]        0        0        0        0   .        0        0        0
##     [4,]        0        0        0        0   .        0        0        0
##     [5,]        0        0        0        0   .        0        0        0
##      ...        .        .        .        .   .        .        .        .
## [27994,]        0        0        0        0   .        0        0        0
## [27995,]        0        0        0        1   .        0        2        0
## [27996,]        0        0        0        0   .        0        1        0
## [27997,]        0        0        0        0   .        0        0        0
## [27998,]        0        0        0        0   .        0        0        0
##          [,20000]
##     [1,]        0
##     [2,]        0
##     [3,]        0
##     [4,]        0
##     [5,]        0
##      ...        .
## [27994,]        0
## [27995,]        0
## [27996,]        0
## [27997,]        0
## [27998,]        0

看一下這個大小

object.size(counts(sce.brain))
## 2328 bytes

但實際上這個底層數據的大小是

file.info(path(counts(sce.brain)))$size
## [1] 76264332

歡迎關注我們的公衆號~_~  
我們是兩個農轉生信的小碩,打造生信星球,想讓它成爲一個不拽術語、通俗易懂的生信知識平臺。需要幫助或提出意見請後臺留言或發送郵件到[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章