如何创建自定义随机分布函数?

时间:2017-04-26 12:28:51

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

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

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); } 。理想情况下,我希望它能够用于当前内置发行版的工作方式:

{{1}}

4 个答案:

答案 0 :(得分: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);
     }
}

答案 1 :(得分:1)

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

  1. 构造函数参数:
    • CDF(规范化或非规范化),即 PDF
    • 整数
    • 分布的下限和上限
    • (可选)分辨率,表示我们应采取的CDF样本点数。
  2. 计算CDF的映射 - &gt;随机数 x 。这是我们的逆CDF功能。
  3. 通过以下方式生成随机点:
    • 使用(0, 1]std::random之间生成随机概率 p
    • 在我们的映射中二进制搜索与 p 对应的CDF值。返回与CDF一起计算的 x 。提供了附近“桶”之间的可选线性集成,否则我们将得到 n ==分辨率离散步骤。
  4. 代码:

    // 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。

    <强>数线性 Log-linear distribution

    <强>正弦 Sinusoidal distribution

    您可以使用以下演示自行尝试:

    // 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
    

答案 2 :(得分:1)

我非常喜欢这里介绍的概念,这导致了非常纤巧但功能强大的生成器。我只是做了一些嵌入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    **********************************

答案 3 :(得分:0)

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

如果您遇到特殊问题,CDF是一个简单的函数,但它的反函数却不是。在这种情况下,可以使用传统的数值工具将其反转,例如Newton-Raphson迭代。很遗憾,您未能指定x的范围或参数mk的允许选项。我已针对常规mk和范围(and posted it on code review)实现了此功能,以满足C ++ RandomNumberDistribution concept