提高在大型矩阵中计算加权Jaccard的性能

提高在大型矩阵中计算加权Jaccard的性能,r,performance,similarity,doparallel,parallel-foreach,R,Performance,Similarity,Doparallel,Parallel Foreach,R输入:矩阵(测量x个样本)(2291 x 265)(矩阵[i,j]=介于0和1之间的值) 输出:在所有样本对之间计算加权jaccard的simmetric相似矩阵 问题:找到产生输出的最快方法。我找到了一个使用“doParallel”和“foreach”的好方法,但这还不够,因为它仍然太慢。我没有找到任何能够计算加权jaccard的函数包,但可能我没有找到。无论如何,你可以用你喜欢的解决方案和方法来回答。谢谢大家的回答。 这是我目前的脚本: rm(list=ls()) #Load libra

R输入:矩阵(测量x个样本)(2291 x 265)(矩阵[i,j]=介于0和1之间的值)

输出:在所有样本对之间计算加权jaccard的simmetric相似矩阵

问题:找到产生输出的最快方法。我找到了一个使用“doParallel”和“foreach”的好方法,但这还不够,因为它仍然太慢。我没有找到任何能够计算加权jaccard的函数包,但可能我没有找到。无论如何,你可以用你喜欢的解决方案和方法来回答。谢谢大家的回答。 这是我目前的脚本:

rm(list=ls())

#Load libraries ----
require(doParallel)
require(foreach)
require(doSNOW)
require(doMPI)
#Imported data ----
dim(input_m) #2291 x 265

#Set clusters ----
no_cores <- 3
cl <- makeCluster(as.integer(no_cores))
registerDoParallel(cl)

#I build all the combinations of the pairs of samples ----
samples=seq(1:ncol(input_m))
combs<-as.matrix(expand.grid(samples,samples))
combs<-unique(t(parApply(cl=cl, combs, 1, sort)))

#Prepare the resulting matrix ----
res_m <- matrix(ncol = ncol(input_m), nrow = ncol(input_m))
rownames(res_m)=colnames(input_m)
colnames(res_m)=colnames(input_m)

#Compute Weighted Jaccard similarity btw all pairs of samples ----
sim_m=foreach(s = 1:nrow(combs), .combine=rbind, .noexport=c("pair","num","den"), .inorder=FALSE) %dopar% {
    pair=input_m[,c(combs[s,1],combs[s,2])]
    num=sum(apply(pair,1,min))
    den=sum(apply(pair,1,max))
    return(c(combs[s,1],combs[s,2],num/den))
}

#Fill the prepared matrix with the results in sim_m
for (k in 1:nrow(sim_m)){
    sim=sim_m[k,3]
    idx1=sim_m[k,1]
    idx2=sim_m[k,2]
    res_m[idx1,idx2]=sim
    res_m[idx2,idx1]=sim
}

#Stop clusters
stopCluster(cl)
rm(list=ls())
#加载库----
要求(双平行)
需要(foreach)
要求(doSNOW)
要求(doMPI)
#导入的数据----
尺寸(输入尺寸)#2291 x 265
#集簇----

没有核心我没有一个完整的版本可以为您运行,因为我不完全确定输入看起来像什么,以及所需的输出应该是什么。不过,我有一些提示可以显著加快代码的速度

步骤1

你最大的麻烦就是这段代码

samples=seq(1:ncol(input_m))
combs<-as.matrix(expand.grid(samples,samples))
combs<-unique(t(parApply(cl=cl, combs, 1, sort)))
现在看看这个速度增益
f()
对应于上面的三行

microbenchmark::microbenchmark(f(100), MESS::pairwise_combination_indices(100, self=TRUE))
Unit: microseconds
                                                 expr        min          lq
                                               f(100) 355670.517 386745.3550
 MESS::pairwise_combination_indices(100, self = TRUE)     31.006     44.3855
        mean     median         uq        max neval cld
 414465.6852 409732.726 427356.848 575404.135   100   b
     85.7078     65.962     84.804    679.408   100  a 
现在需要计算265列的索引矩阵,而不仅仅是100列,因此速度增益应该更大。没有多少内核可以与之竞争,所以用

combs <- MESS::pairwise_combination_indices(ncols(input_m), self=TRUE)
试试这些,看看是否有用


在Rcpp中,加权Jaccard相似度btw all PAIR可能可以非常快速地计算。

我找到了一个非常好的解决方案,它替换了所有原始代码,并在几行代码中解决了这个问题

rm(list=ls())
load("data.rda")
# dim(input_m) 2291 x 265
res_m=outer(1:ncol(input_m), 1:ncol(input_m) , FUN=Vectorize(function(r,c){
 require(matrixStats);
 sum(rowMins(input_m[,c(r,c)]))/sum(rowMaxs(input_m[,c(r,c)]))}))
rownames(res_m)=colnames(input_m)
colnames(res_m)=colnames(input_m)

使用您的答案和@HenrikB注释,我成功地编写了一个更快的方法:

## simulate data
nr <- 2291; nc <- 265
set.seed(420)
input_m <- matrix(rnorm(nr * nc), nrow = nr, ncol = nc)
input_m[1:5, 1:5]
#             [,1]       [,2]        [,3]        [,4]        [,5]
# [1,] -0.76774389  1.2623614  2.44166184 -1.86900934  1.61130129
# [2,] -1.44513238 -0.5469383 -0.31919480 -0.03155421  0.09293325
# [3,] -0.71767075 -0.2753542  2.28792301  0.41545393 -0.47370802
# [4,]  0.06410398  1.4956864  0.06859527  2.19689076 -0.96428109
# [5,] -1.85365878  0.1609678 -0.52191522 -0.79557319 -0.33021108

jaccardLuke <- function(input_m) {
  res_m = outer(1:ncol(input_m), 1:ncol(input_m) ,
                FUN = Vectorize(function(r,c) {
                  require(matrixStats)
                  sum(rowMins(input_m[,c(r,c)]))/sum(rowMaxs(input_m[,c(r,c)]))
                  })
                )
  rownames(res_m) = colnames(input_m)
  colnames(res_m) = colnames(input_m)
  res_m
}

jaccardHenrikB <- function(input_m) {
  require(matrixStats)
  res_m = outer(1:ncol(input_m), 1:ncol(input_m) ,
                FUN = Vectorize(function(r, r2) {
                  x <- rowRanges(input_m, cols = c(r, r2))
                  s <- colSums(x)
                  s[1] / s[2]
                })
  )
  rownames(res_m) = colnames(input_m)
  colnames(res_m) = colnames(input_m)
  res_m
}
基准:

system.time(jaccardLuke(input_m)) # 6.05 sek
system.time(jaccardHenrikB(input_m)) # 2.75 sek
system.time(jaccardMinem(input_m)) # 1.74 sek

## for larger data:
nr <- 5000; nc <- 500
set.seed(420)
input_m <- matrix(rnorm(nr * nc), nrow = nr, ncol = nc)
system.time(jaccardLuke(input_m)) # 41.55 sek
system.time(jaccardHenrikB(input_m)) # 19.87 sek
system.time(jaccardMinem(input_m)) # 11.17 sek
system.time(jaccardLuke(input#m))#6.05瑞典克朗
系统时间(jaccardHenrikB(输入m))#2.75瑞典克朗
系统时间(jaccardMinem(input#m))#1.74瑞典克朗
##对于较大的数据:

nr您可以看看
代理
包。我知道它对Jaccard有一个方法,但不确定加权方面。无论如何可能值得检查。@lmo代理包没有实现WJ。它允许定义一个自定义函数,但不会改变性能,因为它将再次依赖于我的脚本。不过,谢谢你的回答,任何反馈都很重要。使用
rowMins(input_m[,c(r,c)])
代替
rowMins(input_m,cols=c(r,c))
,并类似地使用rowMaxs()。这将在内部进行矩阵子集设置,而无需创建副本;这样效率更高(速度和内存)。2.使用
库(matrixStats)
-您很少需要
require()
。但进一步的改进是使用
r一次性计算(最小值、最大值),谢谢您的评论;加上@minem的回答,你完全解决了我的问题。哇,这是一个我无法想象的解决方案。感谢您的回答,也感谢您改进了我在R.Wow中的编码方式。我用另外两个代码实现对它进行了测试,结果是最快的。感谢您对这个问题做出的杰出贡献。
## simulate data
nr <- 2291; nc <- 265
set.seed(420)
input_m <- matrix(rnorm(nr * nc), nrow = nr, ncol = nc)
input_m[1:5, 1:5]
#             [,1]       [,2]        [,3]        [,4]        [,5]
# [1,] -0.76774389  1.2623614  2.44166184 -1.86900934  1.61130129
# [2,] -1.44513238 -0.5469383 -0.31919480 -0.03155421  0.09293325
# [3,] -0.71767075 -0.2753542  2.28792301  0.41545393 -0.47370802
# [4,]  0.06410398  1.4956864  0.06859527  2.19689076 -0.96428109
# [5,] -1.85365878  0.1609678 -0.52191522 -0.79557319 -0.33021108

jaccardLuke <- function(input_m) {
  res_m = outer(1:ncol(input_m), 1:ncol(input_m) ,
                FUN = Vectorize(function(r,c) {
                  require(matrixStats)
                  sum(rowMins(input_m[,c(r,c)]))/sum(rowMaxs(input_m[,c(r,c)]))
                  })
                )
  rownames(res_m) = colnames(input_m)
  colnames(res_m) = colnames(input_m)
  res_m
}

jaccardHenrikB <- function(input_m) {
  require(matrixStats)
  res_m = outer(1:ncol(input_m), 1:ncol(input_m) ,
                FUN = Vectorize(function(r, r2) {
                  x <- rowRanges(input_m, cols = c(r, r2))
                  s <- colSums(x)
                  s[1] / s[2]
                })
  )
  rownames(res_m) = colnames(input_m)
  colnames(res_m) = colnames(input_m)
  res_m
}
jaccardMinem <- function(input_m) {
  require(data.table)
  require(matrixStats)

  samples <- 1:ncol(input_m)
  comb <- CJ(samples, samples)
  comb[, i := .I]
  comb <- melt(comb, 'i')
  setorder(comb, value)
  v2 <- paste0("V", 1:2)
  comb[, variable2 := v2 , keyby = i]
  comb2 <- dcast(comb, i ~ variable2, value.var = 'value')
  combUnique <- unique(comb2, by = c('V1', 'V2'))

  XX <- apply(combUnique[, -'i'], 1, function(x) {
    x2 <- rowRanges(input_m, cols = x)
    s <- colSums2(x2)
    s[1] / s[2]
  })

  set(combUnique, j = 'xx', value = XX)
  rez2 <- merge(comb2, combUnique[, -'i'], by = c('V1', 'V2'), all.x = T)
  setorder(rez2, i)
  rez2 <- array(rez2$xx, dim = rep(ncol(input_m), 2))
  rownames(rez2) <- colnames(input_m)
  colnames(rez2) <- colnames(input_m)
  rez2
}
all.equal(jaccardLuke(input_m), jaccardHenrikB(input_m))
# [1] TRUE
all.equal(jaccardLuke(input_m), jaccardMinem(input_m))
# [1] TRUE
system.time(jaccardLuke(input_m)) # 6.05 sek
system.time(jaccardHenrikB(input_m)) # 2.75 sek
system.time(jaccardMinem(input_m)) # 1.74 sek

## for larger data:
nr <- 5000; nc <- 500
set.seed(420)
input_m <- matrix(rnorm(nr * nc), nrow = nr, ncol = nc)
system.time(jaccardLuke(input_m)) # 41.55 sek
system.time(jaccardHenrikB(input_m)) # 19.87 sek
system.time(jaccardMinem(input_m)) # 11.17 sek