使用大型矩阵时,是否存在更有效的方式来匹配矩阵行? 我有一个向量,其值对应于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
答案 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)
这是一种遵循与示例代码相同的一般结构的解决方案。 intToBits
和packBits
对应于每个整数的二进制表示形式(从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
在我看来,对邻居进行预先计算不会有太大帮助,因为我猜想比较访问如此大数组的不同部分是最耗时的部分。