【问题标题】:how to produce every permutation of 20 one (-1) in a 1-by-41 vector of ones and a simple calculation on each row?如何在 1×41 向量中生成 20 个一 (-1) 的每个排列,并对每一行进行简单计算?
【发布时间】:2018-07-30 12:52:14
【问题描述】:

我想生成 20 减一 (-1) 和 21 一 (1) 的所有排列,这个矩阵有 269128937220 行和 41 列。我想对该矩阵的每一行进行以下计算:

(SLS')/4 

地点:

S 是该矩阵的每一行(一个 1 x 41 数组)。

S' 是 S(41 x 1 数组)的转置。

L 是一个 41 x 41 矩阵

每次计算的最终结果都是一个数字。

有什么方法可以在合理的时间内生成这个矩阵并进行计算而不会出现内存错误?

提前致谢。

【问题讨论】:

  • 我个人不明白你的问题。 (1, 1, ...., 1) 的排列是 (1, 1, ..., 1)。和你的矩阵有什么关系。你能改写一下吗?
  • 啊,我看到你有一个包含 41 个条目的向量,20 个“-1”和 21 个“1”?
  • 实际上我想要二十个“-1”和二十一个“1”的所有排列。这个问题的标题有一个小问题。可以修改题目的标题吗?
  • 这种排列由RcppAlgos 包提供。原则上做permuteGeneral(c(1,-1), freqs = c(21,20))。但是数量太多了:Error in CombinatoricsRcpp(v, m, repetition, freqs, lower, upper, constraintFun, : The number of rows cannot exceed 2^31 - 1.
  • @StéphaneLaurent,为了超过 2^31 - 1 结果,您使用参数 lowerupper 来生成块中的排列。

标签: r combinations


【解决方案1】:

首先,您最好重新考虑您的方法。话虽如此,让我们开始解决您的问题。

这是一个非常困难的问题,主要是由于资源的限制。下面,我有一个解决方案,如果您可以访问相当数量的存储空间(至少7 TB),它将在家用计算机上在合理的时间内完成。下面的算法不需要那么多内存,可以调整以减少内存使用。

在我们开始之前,我们注意到一开始仅仅生成这么多排列似乎是不可能的。然而,在高度优化的C++ 代码和并行计算的帮助下,这项任务又回到了可能的领域。这在我的answer 对 OP 的上一个问题中得到了证明。我们利用RcppAlgos(我是作者)和parallel 包在使用8 个核心的100 万块中每秒生成约3600 万个排列。

现在,我们负责尽可能快地对每个排列进行特定计算。计算如下:

(SLS') / 4, where S is a permutation, L is a 41 x 41 matrix

这里有几个base R 方法(注意m1[x, ] %*% m2 %*% m1[x, ]m1[x, ] %*% m2 %*% as.matrix(m1[x, ], ncol = 1) 相同):

baseTest1 <- function(m1, m2) {
    vapply(1:nrow(m1), function(x) {
        m1[x, ] %*% m2 %*% m1[x, ]
    }, FUN.VALUE = 1.1111, USE.NAMES = FALSE) / 4
}

baseTest2 <- function(m1, m2) {
    temp <- m1 %*% m2
    vapply(1:nrow(m1), function(x) {
        crossprod(temp[x, ], m1[x, ])
    }, FUN.VALUE = 1.1111, USE.NAMES = FALSE) / 4
}

让我们稍微考虑一下。我们有一堆数字一和负一的排列。当我们将这些排列乘以实数矩阵时,例如 M,我们最终只是简单地从 M 中添加和减去值。我敢打赌,我们可以使用 Rcpp 加快这一速度,并避免浪费(和无用)的身份乘法(即乘以 1)。

#include <Rcpp.h>

//[[Rcpp::export]]
Rcpp::NumericVector makeVecCpp(Rcpp::NumericMatrix A, 
                               Rcpp::NumericMatrix B, 
                               unsigned long int mySize) {

    Rcpp::NumericVector result = Rcpp::no_init_vector(mySize);
    double temp = 0;

    for (std::size_t i = 0; i < mySize; ++i) {
        for (std::size_t j = 0; j < 41u; ++j) {
            for (std::size_t k = 0; k < 41u; ++k) {
                if (A(i, j) + A(i, k)) { 
                    temp += B(j, k);     
                } else {
                    temp -= B(j, k);
                }
            }
        }

        result[i] = temp / 4;
        temp = 0;
    }

    return result;
}

现在让我们看看它们是否给出了相同的结果并对其进行基准测试:

options(scipen = 999)
library(RcppAlgos)
library(microbenchmark)

set.seed(42)
M <- matrix(rnorm(41*41), nrow = 41, ncol = 41)

negOne <- permuteGeneral(c(1L, -1L), freqs = c(21, 20), upper = 100000)

all.equal(baseTest1(negOne, M), baseTest2(negOne, M))
# [1] TRUE
all.equal(baseTest1(negOne, M), makeVecCpp(negOne, M, 100000))
# [1] TRUE

microbenchmark(base1 = baseTest1(negOne, M), base2 = baseTest2(negOne, M), 
               myRcpp = makeVecCpp(negOne, M, 100000), times = 25)
Unit: milliseconds
  expr      min       lq     mean   median       uq      max neval
 base1 555.0256 582.2273 597.6447 593.7708 599.1380 690.3882    25
 base2 471.0251 494.2367 541.2632 531.1858 586.6774 632.7279    25
myRcpp 202.7637 207.2463 210.0255 209.0399 209.9648 240.6664    25

我们的Rcpp 实现显然是赢家!!接下来,我们将其纳入我们的最终答案:

## WARNING Don't run this unless you have a few DAYS on your hand

library(parallel)
## break up into even intervals of one hundred thousand
firstPart <- mclapply(seq(1, 269128900000, 100000), function(x) {
    negOne <- permuteGeneral(c(1L, -1L), freqs = c(21, 20), 
                              lower = x, upper = x + 99999)
    vals <- makeVecCpp(negOne, M, 100000)
    write.csv(vals, paste0("myFile", x, ".csv", collapse = ""))
    x
}, mc.cores = 8)

## get the last few results and complete analysis
lastPart <- permuteGeneral(c(1L, -1L), freqs = c(21, 20), 
                           lower = 269128900001, upper = 269128937220)
vals <- makeVecCpp(lastPart, M, 37220)
write.csv(vals, paste0("myFile", 269128900001, ".csv", collapse = ""))

您会注意到,我们通过将每十万个结果写入主存储器来避免将所有内容存储在内存中,因此需要一个巨大的硬盘驱动器。当我对此进行测试时,每个文件大约是2.5 Mb,总计大约是6.5 TB

a <- 2.5 * (2^20) ### convert to bytes
a * (269128937220 / 1e5) / 2^40 ## get terabytes
[1] 6.416534

为了让您了解此计算需要多长时间,以下是前一亿个结果的时间安排:

system.time(firstPart <- mclapply(seq(1, 100000000, 100000), function(x) {
    negOne <- permuteGeneral(c(1L, -1L), freqs = c(21, 20), 
                              lower = x, upper = x + 99999)
    vals <- makeVecCpp(negOne, M, 100000)
    write.csv(vals, paste0("myFile", x, ".csv", collapse = ""))
    x
}, mc.cores = 8))

   user  system elapsed 
529.931   9.557  80.690

80 秒还不错!这意味着我们只需要等待大约 2.5 天!!!!!!:

(269128937220 / 100000000 / 60 / 60 / 24) * 80
[1] 2.491935

如果你真的想减少这个时间,你将不得不使用高性能计算服务。

所有结果均在 MacBook Pro 2.8GHz 四核(4 个虚拟核心......总共 8 个)上获得。

【讨论】:

  • 感谢您的完美回答,但问题是我没有 6.5 TB 的硬盘。但我只需要“(SLS')/ 4”的最小和最大结果及其相应的排列。如果有超过 1 分钟或最大值我想要所有这些数字和矩阵的相应行(导致最大或最小的排列导致“(SLS')/ 4”计算)。所以没有必要保留所有这些结果。你觉得有帮助吗?
  • @ehsun,这是个好消息(您不需要存储 6.5 TB)...只需删除 write.csv 并返回 min/max 作为列表(其中 x就在mc.cores = ..) 的上方,就像list(min(vals), x + which.min(vals) - 1, max(vals), x + which.max(vals) - 1)。这将为您提供每个块的最小值和最大值以及它们的相关索引。如果你想减少输出的数量,你可以将你的块的大小从十万增加到一百万。这将使您的结果列表更易于管理。
  • @ehsun,索引很重要,因为您可以使用它们通过permuteSample(c(1L, -1L), freqs = c(21, 20), sampleVec = c("your indices here")) 生成特定排列。我们可以这样做,因为排列是按字典顺序排列的。
【解决方案2】:

首先请注意,您期望的结果是一个包含超过 2690 亿个元素的数值向量。每个元素需要 8 个字节,即超过 2TB 的 RAM 来存储结果。如果你没有那么多,那么做你所要求的就没有希望了。另请注意,您需要long vector 来存储结果。

如果您确实有这么多的 RAM,这里有一个基于 combn 及其 FUN 参数的解决方案。这对于内存使用来说应该是相当理想的。如果你想让它更快,尝试直接用 Rcpp 实现compute_one

k = 15 # should be 20
n = 2*k+1
L = matrix(runif(n*n), ncol=n)

compute_one = function(indices) {
    s = rep.int(1,n)
    s[indices] = -1
    drop(t(s) %*% L %*% s / 4)
}

res = combn(n, k, compute_one)

【讨论】:

    猜你喜欢
    • 2019-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-17
    • 1970-01-01
    • 2022-10-15
    • 2022-12-05
    • 1970-01-01
    相关资源
    最近更新 更多