【问题标题】:Computing sparse pairwise distance matrix in R在R中计算稀疏的成对距离矩阵
【发布时间】:2011-07-30 10:25:20
【问题描述】:

我有一个NxM 矩阵,我想计算M 点之间的欧几里得距离的NxN 矩阵。在我的问题中,N 大约是 100,000。由于我计划将此矩阵用于 k-最近邻算法,因此我只需要保持 k 最小距离,因此生成的 NxN 矩阵非常稀疏。这与来自 dist() 的结果形成对比,例如,这将导致密集矩阵(对于我的尺寸 N 而言可能存在存储问题)。

到目前为止,我发现的 kNN 包(knnflexkknn 等)似乎都使用了密集矩阵。此外,Matrix 包不提供成对距离函数。

更接近我的目标,我看到spam 包有一个nearest.dist() 函数,它允许人们只考虑小于某个阈值delta 的距离。然而,就我而言,delta 的特定值可能会产生太多的距离(因此我必须密集存储NxN 矩阵)或太少的距离(因此我不能使用 kNN)。

我之前看到过关于尝试使用 bigmemory/biganalytics 包执行 k-means clustering 的讨论,但在这种情况下我似乎无法利用这些方法。

有人知道在 R 中以稀疏方式计算距离矩阵的函数/实现吗?我(可怕的)备份计划是有两个 for 循环并将结果保存在 Matrix 对象中。

【问题讨论】:

  • 只是确保...你知道diststat.ethz.ch/R-manual/R-patched/library/stats/html/dist.html,对吧?
  • 抱歉,我不清楚为什么 dist() 不适合我的情况。它会产生一个密集的矩阵,存储 NxN 矩阵有点烦人。
  • 您可能应该在此处接受您认为实际上可以回答问题的答案之一(如果您认为它最合适,您自己的答案),或者编辑您的问题以澄清为什么他们不这样做。
  • “有点烦人”是轻描淡写的——如果 N 是 100,000,那是一个 480Gb 矩阵

标签: r distance sparse-matrix knn


【解决方案1】:

好吧,我们不能让你诉诸 for 循环,现在我们可以 :)

当然还有如何表示稀疏矩阵的问题。一种简单的方法是让它只包含最接近的点的索引(并根据需要重新计算)。但在下面的解决方案中,我将距离('d1' 等)和索引('i1' 等)放在一个矩阵中:

sparseDist <- function(m, k) {
    m <- t(m)
    n <- ncol(m)
    d <- vapply( seq_len(n-1L), function(i) { 
        d<-colSums((m[, seq(i+1L, n), drop=FALSE]-m[,i])^2)
        o<-sort.list(d, na.last=NA, method='quick')[seq_len(k)]
        c(sqrt(d[o]), o+i) 
        }, numeric(2*k)
    )
    dimnames(d) <- list(c(paste('d', seq_len(k), sep=''),
        paste('i', seq_len(k), sep='')), colnames(m)[-n])
    d
}

在 9 个二维点上尝试一下:

> m <- matrix(c(0,0, 1.1,0, 2,0, 0,1.2, 1.1,1.2, 2,1.2, 0,2, 1.1,2, 2,2),
              9, byrow=TRUE, dimnames=list(letters[1:9], letters[24:25]))
> print(dist(m), digits=2)
    a   b   c   d   e   f   g   h
b 1.1                            
c 2.0 0.9                        
d 1.2 1.6 2.3                    
e 1.6 1.2 1.5 1.1                
f 2.3 1.5 1.2 2.0 0.9            
g 2.0 2.3 2.8 0.8 1.4 2.2        
h 2.3 2.0 2.2 1.4 0.8 1.2 1.1    
i 2.8 2.2 2.0 2.2 1.2 0.8 2.0 0.9
> print(sparseDist(m, 3), digits=2)
     a   b   c   d   e   f   g   h
d1 1.1 0.9 1.2 0.8 0.8 0.8 1.1 0.9
d2 1.2 1.2 1.5 1.1 0.9 1.2 2.0  NA
d3 1.6 1.5 2.0 1.4 1.2 2.2  NA  NA
i1 2.0 3.0 6.0 7.0 8.0 9.0 8.0 9.0
i2 4.0 5.0 5.0 5.0 6.0 8.0 9.0  NA
i3 5.0 6.0 9.0 8.0 9.0 7.0  NA  NA

并尝试解决一个更大的问题(10k 分)。尽管如此,在 100k 点和更多维度上,这将需要很长时间(例如 15-30 分钟)。

n<-1e4; m<-3; m=matrix(runif(n*m), n)
system.time( d <- sparseDist(m, 3) ) # 9 seconds on my machine...

附:刚刚注意到您在我写这篇文章时发布了一个答案:这里的解决方案大约快两倍,因为它不会两次计算相同的距离(点 1 和 13 之间的距离与点 13 和 1 之间的距离相同)。

【讨论】:

  • 感谢您的回答。我同意它的速度大约是原来的两倍。但是,对于我的应用程序(kNN),我认为只有距离矩阵的上三角形实际上有点不方便。我想我可能会坚持使用我提交的代码的并行版本。不过再次感谢!
【解决方案2】:

现在我正在使用以下内容,灵感来自this answer。输出是一个n x k 矩阵,其中元素(i,k) 是最接近kth 的数据点的索引i

n <- 10
d <- 3
x <- matrix(rnorm(n * d), ncol = n)

min.k.dists <- function(x,k=5) {
  apply(x,2,function(r) {
    b <- colSums((x - r)^2)
    o <- order(b)
    o[1:k]
  })
}

min.k.dists(x)  # first row should be 1:ncol(x); these points have distance 0
dist(t(x))      # can check answer against this

如果有人担心如何处理关系等等,也许应该合并rank()

上面的代码看起来有点快,但我确信它可以改进(虽然我没有时间去Cfortran 路线)。所以我仍然对上述的快速和稀疏的实现持开放态度。

下面我包含了一个我最终使用的并行版本:

min.k.dists <- function(x,k=5,cores=1) {
  require(multicore)
  xx <- as.list(as.data.frame(x))
  names(xx) <- c()
  m <- mclapply(xx,function(r) {
    b <- colSums((x - r)^2)
    o <- order(b)
    o[1:k]
  },mc.cores=cores)
  t(do.call(rbind,m))
}

【讨论】:

  • 您需要执行 dist(t(x)) 以获得可比较的答案。
【解决方案3】:

如果您想保留 min.k.dist 函数的逻辑并返回重复的距离,您可能需要考虑对其进行一些修改。以 0 距离返回第一行似乎毫无意义,对吧? ...通过在我的其他答案中加入一些技巧,您可以将您的版本加快 30%:

min.k.dists2 <- function(x, k=4L) {
  k <- max(2L, k + 1L)
  apply(x, 2, function(r) {
    sort.list(colSums((x - r)^2), na.last=NA, method='quick')[2:k]
  })
}

> n<-1e4; m<-3; m=matrix(runif(n*m), n)
> system.time(d <- min.k.dists(t(m), 4)) #To get 3 nearest neighbours and itself
   user  system elapsed 
  17.26    0.00   17.30 
> system.time(d <- min.k.dists2(t(m), 3)) #To get 3 nearest neighbours
   user  system elapsed 
   12.7     0.0    12.7 

【讨论】: