【问题标题】:Distance calculation on large vectors [performance]大向量的距离计算[性能]
【发布时间】:2015-01-08 18:36:02
【问题描述】:

大家好,我有以下问题,我有一些维度(4-6 个集群)的集群中心和一个非常大的数据集,我需要将每一行分配给最近的集群。所以这不是距离问题,而是性能问题,我的代码如下:

distances <- matrix(NA, nrow = nrow(ClusterCenters), ncol = nrow(data))
calcData <- data[, colnames(ClusterCenters), drop=FALSE]
for(i in 1:nrow(ClusterCenters)) {
    distances[i,] <- (rowSums((matrix(unlist(apply(calcData, 1, function(x) {x - ClusterCenters[i,]})), ncol = ncol(calcData), byrow = TRUE))^2))^0.5
}
ClusterMemberships <- vector(mode="numeric", length=nrow(calcData))
for(i in 1: nrow(calcData)) {
  ClusterMemberships[i] <- which.min(distances[,i])
}
return(ClusterMemberships)

有没有办法加快速度?我在 Windows 服务器上工作。

【问题讨论】:

  • 如果您能解释一下您刚刚未选中的答案如何不符合您的要求,将会很有用。
  • 你能提供一个可重现的例子吗?也许是您数据的一小部分?

标签: r


【解决方案1】:

对于一个 50 x 100 万的数据行矩阵与六个集群匹配,每个集群有 50 个值,我在大约 3 秒内得到结果:

vals <- 50
clusts <- 6
clusters <- matrix(runif(vals * clusts), nrow=clusts)

data.count <- 1e6  # large number
data <- matrix(runif(data.count * vals), nrow=data.count)

system.time({
  dists <- apply(clusters, 1, function(x) rowSums((data - x) ^ 2) ^ .5)
  min.dist <- max.col(-dists, ties.method="first")
})
# user  system elapsed 
# 2.96    0.47    3.49 

关键是要确保我们限制 R 函数调用的数量,因为这些调用变得昂贵。请注意我如何apply 覆盖集群(其中只有六个)而不是覆盖一百万个数据行。然后我使用回收来计算每个集群与整个集合的距离(注意data 与您的数据相比是转置的,集群中的项目有多少行;这是回收工作所必需的)。

感谢@user20650 提供max.col 作品。

【讨论】:

  • 'dists
  • @wafflecop,提供一个带有预期输出的小样本数据集,这样我们就可以在同一页面上。
【解决方案2】:

有几种方法可以优化 R 的性能,例如使用 BrodieG 进行矢量化。

或者,您可以利用现有计算模式的性能优势,例如矩阵乘法、排序。进一步阅读本书中的模式

[Michael McCool 等,结构化并行编程 - 高效计算模式]

我们还可以从多核 CPU 或 GPU 的这些现有模式的并行库中获得进一步的性能优势。在这种情况下,我们可以用矩阵运算或 KNN 来表示计算。

1.分析

通过system.time对大数据集输入(1e6)分析这段代码,我们可以看到第一个循环计算两个向量之间的距离,占总计算时间的95%。所以,我们的优化将从这里开始。

# Original code
vals <- 50
clusts <- 6
ClusterCenters <- matrix(runif(vals * clusts), nrow=clusts)

data.count <- 1e6  # large number
calcData <- matrix(runif(data.count * vals), nrow=data.count)

system.time({
   for(i in 1:nrow(ClusterCenters)) {
     dists[i,] <- (rowSums((matrix(unlist(apply(calcData, 1, function(x) {x      ClusterCenters[i,]})), ncol = ncol(calcData), byrow = TRUE))^2))^0.5
   }
})
user  system elapsed 
71.62    1.13   73.13  

system.time({
   for(i in 1: nrow(calcData)) {
     ClusterMemberships[i] <- which.min(dists[,i])
   }
})
user  system elapsed 
5.29    0.00    5.31 

2。矢量化

向量化是加速 R 代码的绝对有用的方法,尤其是对于“循环”,如 @BrodieG 所示。顺便说一句,我对他的解决方案进行了一些修改以获得正确的结果,如下所示,它可以比原始代码获得大约 3-5X 的加速。

#Vectorization:  From BrodieG
dists1 <-matrix(NA, nrow = nrow(ClusterCenters), ncol = nrow(calcData))  system.time({
  dists1 <- apply(ClusterCenters, 1, function(x) rowSums(sweep(calcData, 2,x, '-') ^ 2) ^ .5)
  min.dist.vec <- max.col(-dists1, ties.method="first")
})
user  system elapsed 
16.13    1.42   17.61  
all.equal(ClusterMemberships, min.dist.vec)
[1] TRUE

3.矩阵模式

然后,让我们回顾一下第一个循环,它通过汇总 (calcData[i,] – ClusterCenters[j,])^2 的列来计算距离。

所以,我们可以通过展开这个等式,将这个运算转化为矩阵:

calcData[i, ]^2 – 2 * calcData[i, ] * ClusterCenters[j, ] + 集群中心[j,]^2

因此,对于第一部分和第三部分,我们可以做简单的矩阵扫描乘法,例如

计算数据 * 计算数据

而对于第二项,我们需要一个矩阵转移的技巧,然后它变成一个矩阵乘法

ClusterCenters %*% t(calcData)

最后,矩阵运算的整个代码如下:

# Pattern Representation 1: Matrix 
dists2 <-matrix(NA, nrow = nrow(ClusterCenters), ncol = nrow(calcData))
system.time({
  data2     <- rowSums(calcData*calcData)
  clusters2 <- rowSums(ClusterCenters*ClusterCenters)
  # NVIDIA GPU: nvBLAS can speedup this step
  # Futher Info on ParallelR.com
  ClustersXdata <- calcData %*% t(ClusterCenters)
  # compute distance 
  dists2  <- sweep(data2 - 2 * ClustersXdata, 2, clusters2, '+') ^0.5
  min.dist.matrix <- max.col(-dists2, ties.method="first")
})
user  system elapsed 
1.17    0.09    1.28
all.equal(ClusterMemberships, min.dist.matrix)
[1] TRUE

现在,我们可以看到所有这三个部分都可以通过矩阵运算来完成。通过这种方法,计算时间从 10^3 到 10^7 几乎是线性的,并且它比 1e6 calcData 集的原始代码快大约 50X

4. KNN

在这种情况下,计算可以看作是在集群数据集中找到第一个最近邻,所以它是一种最简单的 KNN,k=1。而且我们可以很容易地使用C编写的类包中的knn函数。同时,它也将非常高效。示例代码如下:

# Pattern Representation 2: KNN 
library("class")
system.time(
  min.dist.knn <- knn(ClusterCenters, calcData, cl = 1:nrow(ClusterCenters), k = 1)
)
user  system elapsed 
1.21    0.12    1.35 

all.equal(ClusterMemberships, as.integer(min.dist.knn))
[1] TRUE

KNN的运行时间和我们1e6的矩阵运算代码差不多,但是如果我们对这两个算法应用更多的大数据,我们可以看到矩阵算法还是赢了,矩阵算法是2X 比 KNN 快(15.9 .vs. 29.1)。

最后,在这篇文章中我只是展示了一些性能调优的想法,我们可以继续微调这段代码,包括架构指定的优化和使用 c/c++ 重写它。甚至在多核CPU和NVIDIA GPU上并行化矩阵运算和K​​NN,可以参考ParallelR

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-19
    • 2018-08-23
    • 2016-04-02
    相关资源
    最近更新 更多