R-查找按位二进制邻居(一次翻转一位)

时间:2018-06-22 19:00:53

标签: r matrix

使用大型矩阵时,是否存在更有效的方式来匹配矩阵行? 我有一个向量,其值对应于2 ^ N行的矩阵。 N通常较大,例如> 20。每行是N = {0,1}个值的唯一组合,代表决策空间上的“位置”。即,对于N = 3,行将是 0 0 0, 0 0 1 0 1 0, 1 0 0, ..., 1 1 1

我需要确定某个位置是否是局部最大值,即N个相邻位置是否具有较低的值。例如,对于位置0 0 0,相邻位置分别是1 0 0、0 1 0和0 0 1。 我已经对以下解决方案进行了编码,但对于大型N来说,它的工作速度非常慢。

library(prodlim) #for row.match command

set.seed(1234)
N=10

space = as.matrix(expand.grid(rep(list(0:1), N))) #creates all combinations of 0-1 along N-dimensions

performance = replicate(2^N, runif(1, min=0, max=1)) #corresponding values for each space-row (position)

#determine whether a space position is a local maxima, that is, the N neighboring positions are smaller in performance value


system.time({
local_peaks_pos = matrix(NA,nrow=2^N, ncol=1)
for(v in 1:2^N)
{

  for(q in 1:N)
  {
    temp_local_pos = space[v,1:N]
    temp_local_pos[q] = abs(temp_local_pos[q]-1)

    if(performance[row.match(temp_local_pos[1:N], space[,1:N])] > performance[v])
    {
      local_peaks_pos[v,1] = 0
      break
    }

  }

}
local_peaks_pos[is.na(local_peaks_pos)] = 1
})

  user  system elapsed 
   9.94    0.05   10.06 

2 个答案:

答案 0 :(得分:1)

正如Gabe在评论中提到的那样, 您可以利用您的决策空间可以解释为单个整数这一事实:

set.seed(1234L)
N <- 10L
performance <- runif(2^N)
powers_of_two <- as.integer(rev(2L ^ (0L:(N - 1L))))

is_local_max <- sapply(0L:(2^N - 1), function(i) {
  multipliers <- as.integer(rev(intToBits(i)[1L:N])) * -1L
  multipliers[multipliers == 0L] <- 1L
  neighbors <- i + powers_of_two * multipliers
  # compensate that R vectors are 1-indexed
  !any(performance[neighbors + 1L] > performance[i + 1L])
})

# compensate again
local_peaks_int <- which(is_local_max) - 1L
local_peaks_binary <- t(sapply(local_peaks_int, function(int) {
  as.integer(rev(intToBits(int)[1L:N]))
}))

> head(local_peaks_binary)
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,]    0    0    0    0    0    0    0    1    0     0
[2,]    0    0    0    0    1    0    0    1    1     0
[3,]    0    0    0    0    1    1    1    1    0     0
[4,]    0    0    0    1    0    0    0    1    1     1
[5,]    0    0    0    1    0    1    0    1    0     1
[6,]    0    0    0    1    1    0    1    1    1     0

以十进制表示 multipliers包含powers_of_two的符号,因此, 当添加到当前整数时, 它代表二进制的翻转。 例如, 如果原始二进制文件是0 0,而我们翻转一位以获取1 0, 就像我们在小数点后加上2 ^ 1, 但是如果它最初是1 0,而我们翻转一点以获得0 0, 然后我们用十进制减去2 ^ 1。

local_peaks_binary中的每一行都是您决策空间中的二进制文件, 最低有效位在右侧。 因此,例如,第一个局部峰是十进制4。

有关整数到二进制的映射,请参见this question

编辑:并且如果要并行执行:

library(doParallel)
set.seed(1234L)
N <- 20L
performance <- runif(2^N)
powers_of_two <- as.integer(rev(2 ^ (0:(N - 1))))

num_cores <- detectCores()
workers <- makeCluster(num_cores)
registerDoParallel(workers)

chunks <- splitIndices(length(performance), num_cores)
chunks <- lapply(chunks, "-", 1L)
local_peaks_int <- foreach(chunk=chunks, .combine=c, .multicombine=TRUE) %dopar% {
  is_local_max <- sapply(chunk, function(i) {
    multipliers <- as.integer(rev(intToBits(i)[1L:N])) * -1L
    multipliers[multipliers == 0L] <- 1L
    neighbors <- i + powers_of_two * multipliers
    # compensate that R vectors are 1-indexed
    !any(performance[neighbors + 1L] > performance[i + 1L])
  })

  # return
  chunk[is_local_max]
}

local_peaks_binary <- t(sapply(local_peaks_int, function(int) {
  as.integer(rev(intToBits(int)[1L:N]))
}))

stopCluster(workers); registerDoSEQ()

以上操作在我的系统中约2.5秒内完成, 具有4个CPU内核。


这里是使用多线程的C ++版本,但是, 至少在我的系统中有4个线程, 它似乎并不比Gabe的Fortran版本快。 但是,当我尝试在新会话中运行Gabe的Fortran代码时, 我收到N <- 29L的以下错误: cannot allocate vector of size 4.0 Gb

编辑:显然,我在此过程中做了一些重要的更改, 因为再次测试之后 C ++版本实际上似乎更快。

// [[Rcpp::plugins(cpp11)]]
// [[Rcpp::depends(RcppParallel)]]
#include <cstddef> // size_t
#include <vector>
#include <Rcpp.h>
#include <RcppParallel.h>

using namespace std;
using namespace Rcpp;
using namespace RcppParallel;

class PeakFinder : public Worker
{
public:
  PeakFinder(const NumericVector& performance, vector<int>& peaks, const int N)
    : performance_(performance)
    , peaks_(peaks)
    , N_(N)
  { }

  void operator()(size_t begin, size_t end) {
    vector<int> peaks;
    for (size_t i = begin; i < end; i++) {
      bool is_local_peak = true;
      unsigned int mask = 1;
      for (int exponent = 0; exponent < N_; exponent++) {
        unsigned int neighbor = static_cast<unsigned int>(i) ^ mask; // bitwise XOR
        if (performance_[i] < performance_[neighbor]) {
          is_local_peak = false;
          break;
        }

        mask <<= 1;
      }

      if (is_local_peak)
        peaks.push_back(static_cast<int>(i));
    }

    mutex_.lock();
    peaks_.insert(peaks_.end(), peaks.begin(), peaks.end());
    mutex_.unlock();
  }

private:
  const RVector<double> performance_;
  vector<int>& peaks_;
  const int N_;
  tthread::mutex mutex_;
};

// [[Rcpp::export]]
IntegerVector local_peaks(const NumericVector& performance, const int N) {
    vector<int> peaks;
    PeakFinder peak_finder(performance, peaks, N);
    // each thread call will check at least 1024 values
    parallelFor(0, performance.length(), peak_finder, 1024);

    IntegerVector result(peaks.size());
    int i = 0;
    for (int peak : peaks) {
        result[i++] = peak;
    }
    return result;
}

local-peaks.cpp中保存C ++代码后, 此代码:

library(Rcpp)
library(RcppParallel)

sourceCpp("local-peaks.cpp")

set.seed(1234L)
N <- 29L
performance <- runif(2^N)
system.time({
    local_peaks_int <- local_peaks(performance, N)
})

约2秒完成 (无需考虑分配performance所需的时间)。

如果您确实需要二进制表示形式, 您可以像这样更改local_peaks (请参阅this question):

// [[Rcpp::export]]
IntegerMatrix local_peaks(const NumericVector& performance, const int N) {
  vector<int> peaks;
  PeakFinder peak_finder(performance, peaks, N);
  // each thread call will check at least 1024 values
  parallelFor(0, performance.length(), peak_finder, 1024);

  // in case you want the same order every time, #include <algorithm> and uncomment next line
  // sort(peaks.begin(), peaks.end());

  IntegerMatrix result(peaks.size(), N);
  int i = 0;
  for (int peak : peaks) {
    for (int j = 0; j < N; j++) {
      result(i, N - j - 1) = peak & 1;
      peak >>= 1;
    }

    i++;
  }

  return result;
}

答案 1 :(得分:1)

这是一种遵循与示例代码相同的一般结构的解决方案。 intToBitspackBits对应于每个整数的二进制表示形式(从1减去零开始)。内部循环翻转每个N位以获取邻居。在我的笔记本电脑上,N=10只需不到一秒钟的时间,N=20只需大约一分钟的时间。注释的代码存储了一些来自已经测试过的邻居的信息,以免重做计算。取消注释这些行会使N=20的运行时间大约为35秒。

loc_max <- rep(1, 2^N)
for (v in 1:2^N){
  ## if (loc_max[v] == 0) next
  vbits <- intToBits(v-1)
  for (q in 1:N){
    tmp <- vbits
    tmp[q] <- !vbits[q]
    pos <- packBits(tmp, type = "integer") + 1
    if (performance[pos] > performance[v]){
      loc_max[v] <- 0
      break
    ## } else {
    ##   loc_max[pos] <- 0
    }
  }
}

identical(loc_max, local_peaks_pos[, 1])
## [1] TRUE

编辑: 听起来您需要尽可能的提高速度,所以这是另一个建议,该建议依赖编译后的代码以比我的第一个示例更快的速度运行。 N=20只需不到一秒钟的时间,N=29只需不到20秒的时间(这是我可以放入笔记本电脑RAM的最大示例)。

这使用的是单核;您可以并行化它,或者在单个内核中运行它,然后并行化蒙特卡洛模拟。

library(inline)

loopcode <-
"  integer v, q, pos
   do v = 0, (2**N)-1
      do q = 0, N-1
         if ( btest(v,q) ) then
            pos = ibclr(v, q)
         else
            pos = ibset(v, q)
         end if
         if (performance(pos) > performance(v)) then
            loc_max(v) = 0
            exit
         end if
      end do
   end do
"

loopfun <- cfunction(sig = signature(performance="numeric", loc_max="integer", n="integer"),
                     dim=c("(0:(2**n-1))", "(0:(2**n-1))", ""),
                     loopcode,
                     language="F95")

N <- 20
performance = runif(2^N, min=0, max=1)
system.time({
  floop <- loopfun(performance, rep(1, 2^N), N)
})
##  user  system elapsed
## 0.049   0.003   0.052

N <- 29
performance = runif(2^N, min=0, max=1)
system.time({
  floop <- loopfun(performance, rep(1, 2^N), N)
})
##   user  system elapsed
## 17.892   1.848  19.741

在我看来,对邻居进行预先计算不会有太大帮助,因为我猜想比较访问如此大数组的不同部分是最耗时的部分。