来自 C++ 中多元正态/高斯分布的样本
我一直在寻找一种从多元正态分布中采样的便捷方法.有谁知道一个现成的代码片段来做到这一点?对于矩阵/向量,我更喜欢使用 Boost 或 Eigen 或其他我不熟悉的非凡库,但是我可以在紧要关头使用 GSL.如果该方法接受非负-定协方差矩阵而不是要求正定(例如,与 Cholesky 分解一样),我也喜欢它.这存在于 MATLAB、NumPy 和其他软件中,但我很难找到现成的 C/C++ 解决方案.
I've been hunting for a convenient way to sample from a multivariate normal distribution. Does anyone know of a readily available code snippet to do that? For matrices/vectors, I'd prefer to use Boost or Eigen or another phenomenal library I'm not familiar with, but I could use GSL in a pinch. I'd also like it if the method accepted nonnegative-definite covariance matrices rather than requiring positive-definite (e.g., as with the Cholesky decomposition). This exists in MATLAB, NumPy, and others, but I've had a hard time finding a ready-made C/C++ solution.
如果我必须自己实现它,我会抱怨,但没关系.如果我这样做,维基百科听起来就像我应该的
If I have to implement it myself, I'll grumble but that's fine. If I do that, Wikipedia makes it sound like I should
- 生成n 0 均值、单位方差、独立的正态样本(boost 可以做到这一点)
- 求协方差矩阵的特征分解
- 按相应特征值的平方根缩放 n 个样本中的每一个
- 通过将缩放向量与分解找到的正交特征向量矩阵进行预乘来旋转样本向量
- generate n 0-mean, unit-variance, independent normal samples (boost will do this)
- find the eigen-decomposition of the covariance matrix
- scale each of the n samples by the square-root of the corresponding eigenvalue
- rotate the vector of samples by pre-multiplying the scaled vector by the matrix of orthonormal eigenvectors found by the decomposition
我希望这能快速工作.有人对何时值得检查协方差矩阵是否为正有直觉,如果是,请改用 Cholesky?
I would like this to work quickly. Does someone have an intuition for when it would be worthwhile to check to see if the covariance matrix is positive, and if so, use Cholesky instead?
推荐答案
由于这个问题已经获得了很多观点,我想我会发布我找到的最终答案的代码,部分是通过 在 Eigen 论坛上发帖.代码使用 Boost 作为单变量法线,使用 Eigen 进行矩阵处理.感觉很不正统,因为它涉及使用内部"命名空间,但它有效.如果有人提出建议,我愿意改进它.
Since this question has garnered a lot of views, I thought I'd post code for the final answer that I found, in part, by posting to the Eigen forums. The code uses Boost for the univariate normal and Eigen for matrix handling. It feels rather unorthodox, since it involves using the "internal" namespace, but it works. I'm open to improving it if someone suggests a way.
#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
" << mean << std::endl;
std::cout << "Covar
" << covar << std::endl;
std::cout << "Samples
" << samples << std::endl;
}
相关文章