来自C ++中的多元正态/高斯分布的样本

时间:2011-05-26 17:25:09

标签: c++ statistics linear-algebra gaussian normal-distribution

我一直在寻找一种方便的方法来从多元正态分布中进行采样。有没有人知道有一个现成的代码片段可以做到这一点?对于矩阵/向量,我更喜欢使用BoostEigen或我不熟悉的另一个现象库,但我可以在紧要关头使用GSL。如果方法接受非负 - 无限协方差矩阵而不是要求正定(例如,与Cholesky分解一样),我也会喜欢它。这存在于MATLAB,NumPy等中,但我很难找到现成的C / C ++解决方案。

如果我必须自己实施,我会发牢骚但这很好。如果我这样做,Wikipedia makes it sound就像我应该

  1. 生成 n 0均值,单位方差,独立正态样本(提升将执行此操作)
  2. 找到协方差矩阵的特征分解
  3. 按照相应特征值的平方根对每个 n 样本进行缩放
  4. 通过将缩放后的矢量乘以分解找到的标准正交特征向量矩阵来旋转样本矢量
  5. 我希望这可以快速完成。是否有人有直觉知道何时值得检查协方差矩阵是否为正,如果是,请使用Cholesky代替?

3 个答案:

答案 0 :(得分:23)

由于这个问题已经获得了很多观点,我想我会在posting to the Eigen forums部分找到我找到的最终答案的代码。该代码使用Boost作为单变量法线和Eigen进行矩阵处理。它感觉相当不正统,因为它涉及使用“内部”命名空间,但它的工作原理。如果有人提出建议,我愿意改进它。

#include <Eigen/Dense>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>    

/*
  We need a functor that can pretend it's const,
  but to be a good random number generator 
  it needs mutable state.
*/
namespace Eigen {
namespace internal {
template<typename Scalar> 
struct scalar_normal_dist_op 
{
  static boost::mt19937 rng;    // The uniform pseudo-random algorithm
  mutable boost::normal_distribution<Scalar> norm;  // The gaussian combinator

  EIGEN_EMPTY_STRUCT_CTOR(scalar_normal_dist_op)

  template<typename Index>
  inline const Scalar operator() (Index, Index = 0) const { return norm(rng); }
};

template<typename Scalar> boost::mt19937 scalar_normal_dist_op<Scalar>::rng;

template<typename Scalar>
struct functor_traits<scalar_normal_dist_op<Scalar> >
{ enum { Cost = 50 * NumTraits<Scalar>::MulCost, PacketAccess = false, IsRepeatable = false }; };
} // end namespace internal
} // end namespace Eigen

/*
  Draw nn samples from a size-dimensional normal distribution
  with a specified mean and covariance
*/
void main() 
{
  int size = 2; // Dimensionality (rows)
  int nn=5;     // How many samples (columns) to draw
  Eigen::internal::scalar_normal_dist_op<double> randN; // Gaussian functor
  Eigen::internal::scalar_normal_dist_op<double>::rng.seed(1); // Seed the rng

  // Define mean and covariance of the distribution
  Eigen::VectorXd mean(size);       
  Eigen::MatrixXd covar(size,size);

  mean  <<  0,  0;
  covar <<  1, .5,
           .5,  1;

  Eigen::MatrixXd normTransform(size,size);

  Eigen::LLT<Eigen::MatrixXd> cholSolver(covar);

  // We can only use the cholesky decomposition if 
  // the covariance matrix is symmetric, pos-definite.
  // But a covariance matrix might be pos-semi-definite.
  // In that case, we'll go to an EigenSolver
  if (cholSolver.info()==Eigen::Success) {
    // Use cholesky solver
    normTransform = cholSolver.matrixL();
  } else {
    // Use eigen solver
    Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigenSolver(covar);
    normTransform = eigenSolver.eigenvectors() 
                   * eigenSolver.eigenvalues().cwiseSqrt().asDiagonal();
  }

  Eigen::MatrixXd samples = (normTransform 
                           * Eigen::MatrixXd::NullaryExpr(size,nn,randN)).colwise() 
                           + mean;

  std::cout << "Mean\n" << mean << std::endl;
  std::cout << "Covar\n" << covar << std::endl;
  std::cout << "Samples\n" << samples << std::endl;
}

答案 1 :(得分:9)

这是一个在Eigen中生成多元正态随机变量的类,它使用C ++ 11随机数生成,并使用library(tidyr) M = as.data.frame(M) M["rowid"] = row.names(M) gather(M, colid, value, -rowid) 来避免Eigen::internal内容:

Eigen::MatrixBase::unaryExpr()

可以用作

struct normal_random_variable
{
    normal_random_variable(Eigen::MatrixXd const& covar)
        : normal_random_variable(Eigen::VectorXd::Zero(covar.rows()), covar)
    {}

    normal_random_variable(Eigen::VectorXd const& mean, Eigen::MatrixXd const& covar)
        : mean(mean)
    {
        Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigenSolver(covar);
        transform = eigenSolver.eigenvectors() * eigenSolver.eigenvalues().cwiseSqrt().asDiagonal();
    }

    Eigen::VectorXd mean;
    Eigen::MatrixXd transform;

    Eigen::VectorXd operator()() const
    {
        static std::mt19937 gen{ std::random_device{}() };
        static std::normal_distribution<> dist;

        return mean + transform * Eigen::VectorXd{ mean.size() }.unaryExpr([&](auto x) { return dist(gen); });
    }
};

答案 2 :(得分:0)

如何进行SVD​​,然后检查矩阵是否为PD?请注意,这不需要您计算Cholskey分解。虽然,我认为SVD比Cholskey慢,但它们必须都是立方数的触发器。