Выборка из многомерного нормального распределения Гаусса в C++
Я искал удобный способ выборки из многомерного нормального распределения. Кто-нибудь знает об легко доступном фрагменте кода для этого? Для матриц / векторов я бы предпочел использовать Boost или Eigen или другая феноменальная библиотека, с которой я не знаком, но я мог бы использовать GSL в крайнем случае. Я также хотел бы, чтобы метод был принят неотрицательных - определенные ковариационные матрицы, а не требующие положительно определенных (например, как с разложением Холецкого). Это существует в MATLAB, NumPy и других, но мне было трудно найти готовое решение C/C++.
Если мне придется реализовать его самостоятельно, я буду ворчать, но это нормально. Если я это сделаю,Википедия дает звук как надо
- создать n 0-среднее, единичная дисперсия, независимые нормальные образцы (boost сделает это)
- найти собственное разложение ковариации матрица
- масштаб каждого из n выборки квадратным корнем соответствующего собственного значения
- поверните вектор выборок, предварительно умножив масштабированный вектор на матрицу ортонормированных собственных векторов, найденных разложением
Я хотел бы, чтобы это работало быстро. Есть ли у кого-то интуиция, когда стоит проверить, является ли матрица ковариации положительной, и если да, используйте Cholesky вместо этого?
3 ответов
поскольку этот вопрос собрал много просмотров, я подумал, что отправлю код для окончательного ответа, который я нашел, частично,публикация на форумах Eigen. Код использует Boost для одномерного normal и 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;
}
вот класс для генерации многомерных нормальных случайных величин в Eigen, который использует генерацию случайных чисел c++11 и избегает 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); });
}
};
его можно использовать как
int size = 2;
Eigen::MatrixXd covar(size,size);
covar << 1, .5,
.5, 1;
normal_random_variable sample { covar };
std::cout << sample() << std::endl;
std::cout << sample() << std::endl;
Как насчет выполнения SVD, а затем проверки, является ли матрица PD? Обратите внимание, что это не требует вычисления факторизации Cholskey. Хотя, я думаю, SVD медленнее, чем Cholskey, но они оба должны быть кубическими по количеству флопов.