【问题标题】:How to create a custom random distribution function?如何创建自定义随机分布函数?
【发布时间】:2017-09-23 20:37:37
【问题描述】:

通常我使用the built in random functions 生成值,但现在我需要创建表单的随机分布

f(x) = k*log(x) + m

是否可以定义自定义随机分布函数?对于我的实际模型,我有x = [1, 1.4e7), k = -0.905787102751, m = 14.913170454。理想情况下,我希望它能够像当前的内置发行版那样工作:

int main() 
{
    std::mt19937 generator;

    std::uniform_real_distribution<> dist(0.0, 1.0);
    my_distribution my_dist(0.0, 10.0); // Distribution using f(x)

    double uni_val = dist(generator);
    double log_val = my_dist(generator);
}

【问题讨论】:

  • 这个问题与 C++ 一样多的数学问题。例如,请参阅en.wikipedia.org/wiki/Inverse_transform_sampling
  • 域名是什么?
  • @YvesDaoust 对于最初的问题,它介于 1 -> 1.4e7 之间。我添加了我如何解决它的答案。
  • 请指定参数mk的预期范围以及范围。特别是,x 是否曾经考虑过&lt;1
  • @Walter 我已将我的实际模型值添加为问题中的编辑。谢谢。

标签: c++ c++11 math random


【解决方案1】:

这很有可能,但它与 C++ 问题一样多是数学问题。创建伪随机数生成器的最通用方法是Inverse transform sampling。本质上,任何 PDF 的 CDF 均匀分布在 0 和 1 之间(如果这不明显,请记住 CDF 的值是概率并考虑这一点)。因此,您只需要在 0 和 1 之间随机抽取一个统一数,然后应用 CDF 的逆。

在您的情况下,使用 $f(x) = k*log(x) + m$ (您没有指定边界,但我假设它们在 1 和某个正数 > 1 之间)CDF 及其逆很乱 - 我留给你一个问题! C++ 中的实现看起来像

double inverseCDF(double p, double k, double m, double lowerBound, double upperBound) {
     // do math, which might include numerically finds roots of equations
}

然后生成代码看起来像

class my_distribution {
     // ... constructor, private variables, etc.
     template< class Generator >
     double operator()( Generator& g ) {
          std::uniform_real_distribution<> dist(0.0, 1.0);
          double cdf = dist(g);
          return inverseCDF(cdf,this->k,this->m,this->lowerBound,this->upperBound);
     }
}

【讨论】:

  • 这是一个很好的建议,让我走上了正确的道路。赞成。我添加了一个答案,概述了我是如何实现它的——这就是你的想法吗?如果您觉得有任何问题,请提出改进​​建议。
【解决方案2】:

我非常接近@jwimberley 的想法,并认为我会在这里分享我的结果。我创建了一个执行以下操作的类:

  1. 构造函数参数:
    • CDF(标准化或非标准化),即 整合 PDF
    • 分布的下限和上限
    • (可选)指示我们应该采用多少 CDF 采样点的分辨率。
  2. 从 CDF 计算映射 -> 随机数 x。这是我们的逆 CDF 函数。
  3. 通过以下方式生成随机点:
    • 使用std::random(0, 1] 之间生成随机概率p
    • 在我们的映射中二进制搜索对应于 p 的 CDF 值。返回与 CDF 一起计算的 x。提供了附近“桶”之间的可选线性积分,否则我们将得到 n == 分辨率离散步数。

代码:

// sampled_distribution.hh
#ifndef SAMPLED_DISTRIBUTION
#define SAMPLED_DISTRIBUTION

#include <algorithm>
#include <vector>
#include <random>
#include <stdexcept>

template <typename T = double, bool Interpolate = true>
class Sampled_distribution
{
public:
    using CDFFunc = T (*)(T);

    Sampled_distribution(CDFFunc cdfFunc, T low, T high, unsigned resolution = 200) 
        : mLow(low), mHigh(high), mRes(resolution), mDist(0.0, 1.0)
    {
        if (mLow >= mHigh) throw InvalidBounds();

        mSampledCDF.resize(mRes + 1);
        const T cdfLow = cdfFunc(low);
        const T cdfHigh = cdfFunc(high);
        T last_p = 0;
        for (unsigned i = 0; i < mSampledCDF.size(); ++i) {
            const T x = i/mRes*(mHigh - mLow) + mLow;
            const T p = (cdfFunc(x) - cdfLow)/(cdfHigh - cdfLow); // normalising 
            if (! (p >= last_p)) throw CDFNotMonotonic();
            mSampledCDF[i] = Sample{p, x};
            last_p = p;
        }
    }

    template <typename Generator>
    T operator()(Generator& g) 
    {
        T cdf = mDist(g);
        auto s = std::upper_bound(mSampledCDF.begin(), mSampledCDF.end(), cdf);
        auto bs = s - 1;
        if (Interpolate && bs >= mSampledCDF.begin()) { 
            const T r = (cdf - bs->prob)/(s->prob - bs->prob);
            return r*bs->value + (1 - r)*s->value;
        }
        return s->value;
    }

private:
    struct InvalidBounds : public std::runtime_error { InvalidBounds() : std::runtime_error("") {} };
    struct CDFNotMonotonic : public std::runtime_error { CDFNotMonotonic() : std::runtime_error("") {} };

    const T mLow, mHigh;
    const double mRes;

    struct Sample { 
        T prob, value; 
        friend bool operator<(T p, const Sample& s) { return p < s.prob; }
    };

    std::vector<Sample> mSampledCDF;
    std::uniform_real_distribution<> mDist;
};

#endif

以下是一些结果图。对于给定的PDF,我们需要先通过积分来解析计算CDF。

对数线性

正弦曲线

您可以通过以下演示自己尝试一下:

// main.cc
#include "sampled_distribution.hh"
#include <iostream>
#include <fstream>

int main()
{
    auto logFunc = [](double x) { 
        const double k = -1.0;
        const double m = 10;
        return x*(k*std::log(x) + m - k); // PDF(x) = k*log(x) + m
    };
    auto sinFunc = [](double x) { return x + std::cos(x); }; // PDF(x) = 1 - sin(x)

    std::mt19937 gen;
    //Sampled_distribution<> dist(logFunc, 1.0, 1e4);
    Sampled_distribution<> dist(sinFunc, 0.0, 6.28);
    std::ofstream file("d.txt");
    for (int i = 0; i < 100000; i++) file << dist(gen) << std::endl;
}

数据是用python绘制的。

// dist_plot.py
import numpy as np
import matplotlib.pyplot as plt

d = np.loadtxt("d.txt")
fig, ax = plt.subplots()
bins = np.arange(d.min(), d.max(), (d.max() - d.min())/50)
ax.hist(d, edgecolor='white', bins=bins)
plt.show()

运行演示:

clang++ -std=c++11 -stdlib=libc++ main.cc -o main; ./main; python dist_plot.py

【讨论】:

  • 关于这段代码可以说几件事,但这确实属于代码审查。
  • @Walter 该帖子不要求评论。这是我如何创建自定义随机分布的答案,并回答了我自己的问题。老实说,我对否决票感到惊讶。
  • 您的代码远非最佳。首先,您至少应该测试 CDF 的单调性。其次,您可以实现一种更好的方法来反转它,例如使用样条或多项式插值。第三,如果您向用户请求 PDF 和 CDF,您可以使用 Newton-Raphson 反转后者,这可以收敛到机器精度。最后,这对于您最初的问题来说太过分了。
  • @Walter 感谢您的反馈。你所有的 cmets 都有优点——我在这个主题上的经验是有限的,我尽我所能来实现满足我的需求。关于它是矫枉过正:我试图遵循 jwimberley 的想法,我能做些什么呢?
  • @Walter 不需要测试单调性,valid CDF 始终是单调非递减的,因为任何有效的 PDF 都必须是非负数。不过,您必须修改离散分布的二进制搜索。
【解决方案3】:

正如其他地方所指出的,对任何 PDF 进行采样的标准方法是在从区间 [0,1] 中均匀随机选择的点处反转其 CDF。

对于您的特定问题,CDF 是一个简单的函数,但它的逆函数不是。在这种情况下,可以使用传统的数值工具(例如 Newton-Raphson 迭代)对其进行反演。不幸的是,您未能指定x 的范围或参数mk 的允许选择。我已经为一般mk 和范围 (and posted it on code review) 实现了这个以满足 C++ RandomNumberDistribution concept

【讨论】:

    【解决方案4】:

    我非常喜欢这里介绍的概念,从而产生了一个非常纤薄但功能强大的生成器。我刚刚做了一些嵌入 c++17 功能的清理工作,我打算编辑 pingul 的答案,但结果完全不同,所以我将它分开发布。

    #pragma once
    
    #include <algorithm>
    #include <vector>
    #include <random>
    #include <stdexcept>
    
    template <typename T = double, bool Interpolate = true>
    class SampledDistribution {
      struct Sample { 
        T prob, value; 
        Sample(const T p, const T v): prob(p), value(v) {}
        friend bool operator<(T p, const Sample& s) { return p < s.prob; }
      };
    
      std::vector<Sample> SampledCDF;
    
    public:
      struct InvalidBounds:   std::runtime_error { using std::runtime_error::runtime_error; };
      struct CDFNotMonotonic: std::runtime_error { using std::runtime_error::runtime_error; };
    
      template <typename F>
      SampledDistribution(F&& cdfFunc, const T low, const T high, const unsigned resolution = 256) {
        if (low >= high) throw InvalidBounds("");
        SampledCDF.reserve( resolution );
        const T cdfLow = cdfFunc(low);
        const T cdfHigh = cdfFunc(high);
        for (unsigned i = 0; i < resolution; ++i) {
          const T x = (high - low)*i/(resolution-1) + low;
          const T p = (cdfFunc(x) - cdfLow)/(cdfHigh - cdfLow); // normalising 
          if (p < SampledCDF.back()) throw CDFNotMonotonic("");
          SampledCDF.emplace_back(p, x);
        }
      }
    
      template <typename Engine>
      T operator()(Engine& g) {
        const T cdf = std::uniform_real_distribution<T>{0.,1.}(g);
        auto s = std::upper_bound(SampledCDF.begin(), SampledCDF.end(), cdf);
        if (Interpolate && s != SampledCDF.begin()) { 
          auto bs = s - 1;
          const T r = (cdf - bs->prob)/(s->prob - bs->prob);
          return r*bs->value + (1 - r)*s->value;
        }
        return s->value;
      }
    };
    

    这是一个测试主要内容:

    #include <iostream>
    #include "SampledDistribution.hpp"
    
    int main() {
      std::mt19937 gen;
      auto sinFunc = [](double x) { return x + std::cos(x); }; // PDF(x) = 1 - sin(x)
    
      unsigned resolution = 32;
      std::vector<int> v(resolution,0);
      SampledDistribution dist(sinFunc, 0.0, 6.28, resolution);
    
      for (int i = 0; i < 100000; i++) 
        ++v[ static_cast<size_t>(dist(gen)/(6.28) * resolution) ];
    
      for (auto i: v)
        std::cout << i << '\t' << std::string(i/100, '*') << std::endl;
    
      return 0;
    }
    

    样本输出:

    $ g++ -std=c++17 main.cpp && ./a.out
    2882    ****************************
    2217    **********************
    1725    *****************
    1134    ***********
    690     ******
    410     ****
    182     *
    37  
    34  
    162     *
    411     ****
    753     *******
    1163    ***********
    1649    ****************
    2157    *********************
    2796    ***************************
    3426    **********************************
    4048    ****************************************
    4643    **********************************************
    5193    ***************************************************
    5390    *****************************************************
    5796    *********************************************************
    5979    ***********************************************************
    6268    **************************************************************
    6251    **************************************************************
    6086    ************************************************************
    5783    *********************************************************
    5580    *******************************************************
    5111    ***************************************************
    4646    **********************************************
    3964    ***************************************
    3434    **********************************
    

    【讨论】:

      猜你喜欢
      • 2017-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-28
      • 2017-09-08
      • 2014-08-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多