mvtnorm :: pmvnorm的rcpp实现比原始R函数慢 [英] Rcpp implementation of mvtnorm::pmvnorm slower than original R function

查看:147
本文介绍了mvtnorm :: pmvnorm的rcpp实现比原始R函数慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正试图获得Rcpp版本的pmvnorm的运行速度至少与R中的mvtnorm :: pmvnorm一样快.

I am trying to get a Rcpp version of pmvnorm to work at least as fast as mvtnorm::pmvnorm in R.

我找到了 https://github.com/zhanxw/libMvtnorm 并创建了一个Rcpp框架打包相关的源文件.我添加了以下使用Armadillo的功能(因为我正在我编写的其他代码中使用它).

I have found https://github.com/zhanxw/libMvtnorm and created a Rcpp skeleton package with the relevant source files. I have added the following functions which make use of Armadillo (since I'm using it across other code I've been writing).

//[[Rcpp::export]]
arma::vec triangl(const arma::mat& X){
  arma::mat LL = arma::trimatl(X, -1);  // omit the main diagonal
  return LL.elem(arma::find(LL != 0));
}

//[[Rcpp::export]]
double pmvnorm_cpp(arma::vec& bound, arma::vec& lowtrivec){
  double error;
  int n = bound.n_elem;
  double* boundptr = bound.memptr();
  double* lowtrivecptr = lowtrivec.memptr();
  double result = pmvnorm_P(n, boundptr, lowtrivecptr, &error);
  return result;
}

从R编译程序包开始,这是一个可重现的示例:

From R after building the package, this is a reproducible example:

set.seed(1)
covar <- rWishart(1, 10, diag(5))[,,1]
sds <- diag(covar) ^-.5
corrmat <- diag(sds) %*% covar %*% diag(sds)
triang <- triangl(corrmat)

bounds <- c(0.5, 0.9, 1, 4, -1)

rbenchmark::benchmark(pmvnorm_cpp(bounds, triang),
                      mvtnorm::pmvnorm(upper=bounds, corr = corrmat),
                      replications=1000)

哪个显示pmvnorm_cpp比mvtnorm :: pmvnorm慢得多.结果是不同的.

Which shows that pmvnorm_cpp is much slower than mvtnorm::pmvnorm. and the result is different.

> pmvnorm_cpp(bounds, triang)
[1] 0.04300643
> mvtnorm::pmvnorm(upper=bounds, corr = corrmat)
[1] 0.04895361

这让我感到困惑,因为我认为基本的fortran代码是相同的.我的代码中是否有某些东西会使一切变慢?还是应该尝试直接移植mvtnorm :: pmvnorm代码?我几乎没有fortran的经验.

which puzzles me because I thought the base fortran code was the same. Is there something in my code that makes everything go slow? Or should I try to port the mvtnorm::pmvnorm code directly? I have literally no experience with fortran.

建议表示赞赏,请原谅我的无能.

Suggestions appreciated, excuse my incompetence othewise.

与其他方法进行快速比较,

to make a quick comparison with an alternative, this:

//[[Rcpp::export]]
NumericVector pmvnorm_cpp(NumericVector bound, NumericMatrix cormat){
  Environment stats("package:mvtnorm"); 
  Function f = stats["pmvnorm"];

  NumericVector lower(bound.length(), R_NegInf);
  NumericVector mean(bound.length());
  NumericVector res = f(lower, bound, mean, cormat);
  return res;
}

与R调用的性能基本相同(以下是40维mvnormal的性能):

has essentially the same performance as an R call (the following on a 40-dimensional mvnormal):

> rbenchmark::benchmark(pmvnorm_cpp(bounds, corrmat),
+                       mvtnorm::pmvnorm(upper=bounds, corr = corrmat),
+                       replications=100)
                                              test replications elapsed relative user.self sys.self
2 mvtnorm::pmvnorm(upper = bounds, corr = corrmat)          100   16.86    1.032     16.60     0.00
1                     pmvnorm_cpp(bounds, corrmat)          100   16.34    1.000     16.26     0.01

因此,在我看来,先前的代码中肯定有某些事情正在进行.无论是我如何用Armadillo处理事物,还是其他事物如何连接.我认为与上一个实现相比应该有性能上的提升.

so it seems to me there must be something going on in the previous code. either with how I'm handling things with Armadillo, or how the other things are connected. I would assume that there should be a performance gain compared to this last implementation.

推荐答案

我将尝试使用由mvtnorm,c.f导出的C API,而不是尝试为此使用其他库. https://github.com/cran/mvtnorm/blob/master/inst/NEWS#L44-L48 .这样做时,我发现了结果不同的三个原因.其中之一也是造成性能差异的原因:

Instead of trying to use an additional library for this, I would try to use the C API exported by mvtnorm, c.f. https://github.com/cran/mvtnorm/blob/master/inst/NEWS#L44-L48. While doing so, I found three reasons why the results differ. One of them is also responsible for the preformance difference:

  1. mvtnorm使用R的RNG,而这已从您正在使用的库c.f中删除. https://github.com/zhanxw/libMvtnorm/blob/master/libMvtnorm/randomF77.c .

  1. mvtnorm uses R's RNG, while this has been removed from the library you are using, c.f. https://github.com/zhanxw/libMvtnorm/blob/master/libMvtnorm/randomF77.c.

您的triangl功能不正确.它以列优先顺序返回下三角矩阵.但是,底层的fortran代码希望按行优先顺序c.f进行操作. https://github.com/cran/mvtnorm/blob/master/src/mvt.f#L36-L39

Your triangl function is incorrect. It returns the lower triangular matrix in column-major order. However, the underlying fortran code expects it in row-major order, c.f. https://github.com/cran/mvtnorm/blob/master/src/mvt.f#L36-L39 and https://github.com/zhanxw/libMvtnorm/blob/master/libMvtnorm/mvtnorm.cpp#L60

libMvtnorm使用1e-6而不是1e-3作为相对精度c.f. https://github.com/zhanxw/libMvtnorm/blob/master/libMvtnorm/mvtnorm.cpp#L65 .这也是造成性能差异的原因.

libMvtnorm uses 1e-6 instead of 1e-3 as relative precision, c.f. https://github.com/zhanxw/libMvtnorm/blob/master/libMvtnorm/mvtnorm.cpp#L65. This is also responsible for the performance difference.

我们可以使用以下代码进行测试:

We can test this using the following code:

// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>
// [[Rcpp::depends(mvtnorm)]]
#include <mvtnormAPI.h>

//[[Rcpp::export]]
arma::vec triangl(const arma::mat& X){
  int n = X.n_cols;
  arma::vec res(n * (n-1) / 2);
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < i; ++j) {
      res(j + i * (i-1) / 2) = X(i, j);
    }
  }
  return res;
}

// [[Rcpp::export]]
double pmvnorm_cpp(arma::vec& bound,
           arma::vec& lowertrivec,
           double abseps = 1e-3){

  int n = bound.n_elem;
  int nu = 0;
  int maxpts = 25000;     // default in mvtnorm: 25000
  double releps = 0;      // default in mvtnorm: 0
  int rnd = 1;            // Get/PutRNGstate

  double* bound_ = bound.memptr();
  double* correlationMatrix = lowertrivec.memptr();
  double* lower = new double[n];
  int* infin = new int[n];
  double* delta = new double[n];

  for (int i = 0; i < n; ++i) {
    infin[i] = 0; // (-inf, bound]
    lower[i] = 0.0;
    delta[i] = 0.0;
  }

  // return values
  double error;
  double value;
  int inform;

  mvtnorm_C_mvtdst(&n, &nu, lower, bound_,
           infin, correlationMatrix, delta,
           &maxpts, &abseps, &releps,
           &error, &value, &inform, &rnd);
  delete[] (lower);
  delete[] (infin);
  delete[] (delta);

  return value;
}

/*** R
set.seed(1)
covar <- rWishart(1, 10, diag(5))[,,1]
sds <- diag(covar) ^-.5
corrmat <- diag(sds) %*% covar %*% diag(sds)
triang <- triangl(corrmat)
bounds <- c(0.5, 0.9, 1, 4, -1)
set.seed(1)
system.time(cat(mvtnorm::pmvnorm(upper=bounds, corr = corrmat), "\n"))
set.seed(1)
system.time(cat(pmvnorm_cpp(bounds, triang, 1e-6), "\n"))
set.seed(1)
system.time(cat(pmvnorm_cpp(bounds, triang, 0.001), "\n"))
 */

结果:

> system.time(cat(mvtnorm::pmvnorm(upper=bounds, corr = corrmat), "\n"))
0.04896221 
   user  system elapsed 
  0.000   0.003   0.003 

> system.time(cat(pmvnorm_cpp(bounds, triang, 1e-6), "\n"))
0.04895756 
   user  system elapsed 
  0.035   0.000   0.035 

> system.time(cat(pmvnorm_cpp(bounds, triang, 0.001), "\n"))
0.04896221 
   user  system elapsed 
  0.004   0.000   0.004 

在相同的RNG(和RNG状态),正确的下三角相关矩阵和相同的相对精度下,结果是相同的,并且性能是可比的.精度更高,性能会受到影响.

With the same RNG (and RNG state), the correct lower triangular correlation matrix and the same relative precision, results are identical and performance is comparable. With higher precision, performance suffers.

所有这些都是使用Rcpp::sourceCpp的独立文件.为了在软件包中使用它,您需要将LinkingTo: mvtnorm添加到您的DESCRIPTION文件中.

All this is for a stand-alone file using Rcpp::sourceCpp. In order to use this in a package, you need to add LinkingTo: mvtnorm to your DESCRIPTION file.

这篇关于mvtnorm :: pmvnorm的rcpp实现比原始R函数慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆