【问题标题】:Weighted random numbers加权随机数
【发布时间】:2010-12-18 05:05:00
【问题描述】:

我正在尝试实现加权随机数。我目前只是把头撞在墙上,无法弄清楚。

在我的项目(Hold'em 手牌范围,主观全押权益分析)中,我使用了 Boost 的随机函数。所以,假设我想选择一个介于 1 和 3 之间的随机数(所以是 1、2 或 3)。 Boost 的 mersenne twister 发生器对此很有魅力。但是,我希望选择权重,例如:

1 (weight: 90)
2 (weight: 56)
3 (weight:  4)

Boost 是否为此提供了某种功能?

【问题讨论】:

    标签: c++ boost random


    【解决方案1】:

    有一种简单的随机挑选物品的算法,其中物品具有单独的权重:

    1) 计算所有权重的总和

    2) 选择一个大于等于 0 且小于权重总和的随机数

    3) 一次检查一个项目,从你的随机数中减去它们的重量,直到你得到随机数小于该项目重量的项目

    说明这一点的伪代码:

    int sum_of_weight = 0;
    for(int i=0; i<num_choices; i++) {
       sum_of_weight += choice_weight[i];
    }
    int rnd = random(sum_of_weight);
    for(int i=0; i<num_choices; i++) {
      if(rnd < choice_weight[i])
        return i;
      rnd -= choice_weight[i];
    }
    assert(!"should never get here");
    

    这应该很容易适应您的 boost 容器等。


    如果您的权重很少更改,但您经常随机选择一个,并且只要您的容器存储指向对象的指针或长度超过几十个项目(基本上,您必须进行分析以了解这是否有帮助或阻碍),然后有一个优化:

    通过将累积重量总和存储在每个项目中,您可以使用binary search 来选择与选择重量对应的项目。


    如果您不知道列表中的项目数,那么有一个非常简洁的算法,称为reservoir sampling,可以调整为加权。

    【讨论】:

    • 作为优化,您可以使用累积权重并使用二进制搜索。但是对于只有三个不同的值,这可能是矫枉过正。
    • 我假设当您说“按顺序”时,您是故意省略了对choice_weight 数组的预排序步骤,是吗?
    • @Aureis,不需要对数组进行排序。我试图澄清我的语言。
    • 这是一个很棒的答案,我在游戏中使用了算法来定义不同类型角色的出现频率
    • 未来读者注意:从你的随机数中减去它们的权重部分很容易被忽略,但对算法至关重要(我陷入了与@kobik 相同的陷阱他们的评论)。
    【解决方案2】:

    更新了一个老问题的答案。您可以在 C++11 中使用 std::lib 轻松做到这一点:

    #include <iostream>
    #include <random>
    #include <iterator>
    #include <ctime>
    #include <type_traits>
    #include <cassert>
    
    int main()
    {
        // Set up distribution
        double interval[] = {1,   2,   3,   4};
        double weights[] =  {  .90, .56, .04};
        std::piecewise_constant_distribution<> dist(std::begin(interval),
                                                    std::end(interval),
                                                    std::begin(weights));
        // Choose generator
        std::mt19937 gen(std::time(0));  // seed as wanted
        // Demonstrate with N randomly generated numbers
        const unsigned N = 1000000;
        // Collect number of times each random number is generated
        double avg[std::extent<decltype(weights)>::value] = {0};
        for (unsigned i = 0; i < N; ++i)
        {
            // Generate random number using gen, distributed according to dist
            unsigned r = static_cast<unsigned>(dist(gen));
            // Sanity check
            assert(interval[0] <= r && r <= *(std::end(interval)-2));
            // Save r for statistical test of distribution
            avg[r - 1]++;
        }
        // Compute averages for distribution
        for (double* i = std::begin(avg); i < std::end(avg); ++i)
            *i /= N;
        // Display distribution
        for (unsigned i = 1; i <= std::extent<decltype(avg)>::value; ++i)
            std::cout << "avg[" << i << "] = " << avg[i-1] << '\n';
    }
    

    我的系统上的输出:

    avg[1] = 0.600115
    avg[2] = 0.373341
    avg[3] = 0.026544
    

    请注意,上面的大部分代码仅用于显示和分析输出。实际的生成只是几行代码。输出表明已获得请求的“概率”。您必须将请求的输出除以 1.5,因为这是请求的总和。

    【讨论】:

    • 只是一个关于编译这个例子的提示:需要 C++ 11 即。使用 -std=c++0x 编译器标志,从 gcc 4.6 开始可用。
    • 只是挑选出解决问题的必要部分?
    • 这是最好的答案,但我认为std::discrete_distribution 而不是std::piecewise_constant_distribution 会更好。
    • @Dan,是的,这将是另一种很好的方法。如果您将其编码并回答,我将投票赞成。我认为代码可能与我上面的代码非常相似。您只需要在生成的输出中添加一个。并且分配的输入会更简单。这方面的一组比较/对比答案可能对读者很有价值。
    【解决方案3】:

    如果你的权重变化比绘制速度慢,C++11 discrete_distribution 将是最简单的:

    #include <random>
    #include <vector>
    std::vector<double> weights{90,56,4};
    std::discrete_distribution<int> dist(std::begin(weights), std::end(weights));
    std::mt19937 gen;
    gen.seed(time(0));//if you want different results from different runs
    int N = 100000;
    std::vector<int> samples(N);
    for(auto & i: samples)
        i = dist(gen);
    //do something with your samples...
    

    但是请注意,c++11 discrete_distribution 会在初始化时计算所有累积和。通常,您希望这样做,因为它以一次性 O(N) 成本加快了采样时间。但是对于快速变化的分布,它会产生大量的计算(和内存)成本。例如,如果权重表示有多少项目,并且每次绘制一个,将其删除,您可能需要自定义算法。

    Will 的回答 https://stackoverflow.com/a/1761646/837451 避免了这种开销,但会比 C++11 更慢,因为它不能使用二进制搜索。

    要查看它是否执行此操作,您可以查看相关行(我的 Ubuntu 16.04 + GCC 5.3 安装上的/usr/include/c++/5/bits/random.tcc):

      template<typename _IntType>
        void
        discrete_distribution<_IntType>::param_type::
        _M_initialize()
        {
          if (_M_prob.size() < 2)
            {
              _M_prob.clear();
              return;
            }
    
          const double __sum = std::accumulate(_M_prob.begin(),
                                               _M_prob.end(), 0.0);
          // Now normalize the probabilites.
          __detail::__normalize(_M_prob.begin(), _M_prob.end(), _M_prob.begin(),
                                __sum);
          // Accumulate partial sums.
          _M_cp.reserve(_M_prob.size());
          std::partial_sum(_M_prob.begin(), _M_prob.end(),
                           std::back_inserter(_M_cp));
          // Make sure the last cumulative probability is one.
          _M_cp[_M_cp.size() - 1] = 1.0;
        }
    

    【讨论】:

      【解决方案4】:

      当我需要对数字进行加权时,我会使用随机数作为权重。

      例如:我需要生成具有以下权重的从 1 到 3 的随机数:

      • 10% 的随机数可能是 1
      • 30% 的随机数可能是 2
      • 60% 的随机数可能是 3

      然后我使用:

      weight = rand() % 10;
      
      switch( weight ) {
      
          case 0:
              randomNumber = 1;
              break;
          case 1:
          case 2:
          case 3:
              randomNumber = 2;
              break;
          case 4:
          case 5:
          case 6:
          case 7:
          case 8:
          case 9:
              randomNumber = 3;
              break;
      }
      

      这样,随机有 10% 的概率为 1,30% 的概率为 2,60% 的概率为 3。

      您可以根据需要使用它。

      希望我能帮助你,祝你好运!

      【讨论】:

      • 这排除了动态调整分布。
      • Hacky 但我喜欢它。非常适合需要粗略加权的快速原型。
      • 它只适用于有理权重。你很难用 1/pi 的重量来做这件事;)
      • @JosephBudin 再说一次,你永远不可能拥有不合理的体重。一个约 43 亿箱的开关应该可以很好地用于浮动重量。 :D
      • 对@JasonC,问题现在无限小,但仍然是一个问题;)
      【解决方案5】:

      为所有可以挑选的物品构建一个袋子(或 std::vector)。
      确保每个项目的数量与您的权重成正比。

      例子:

      • 1 60%
      • 2 35%
      • 3 5%

      所以有一个包含 100 件物品的袋子,其中 60 个 1、35 个 2 和 5 个 3。
      现在对包进行随机排序(std::random_shuffle)

      按顺序从包中挑选元素,直到它为空。
      一旦空了重新随机袋子并重新开始。

      【讨论】:

      • 如果你有一袋红色和蓝色的弹珠,你从中选择了一个红色弹珠并且替换它是选择另一个红色弹珠的概率仍然相同?同样,您的语句“按顺序从袋子中挑选元素,直到它为空”会产生与预期完全不同的分布。
      • @ldog:我理解你的论点,但我们不是在寻找真正的随机性,而是在寻找特定的分布。这种技术保证了正确的分布。
      • 我的意思是,根据我之前的论点,您没有正确地产生分布。考虑一个简单的反例,假设您将数组 3 作为1,2,2 产生 1 1/3 的时间和 2 2/3 的时间。随机化数组,选择第一个,假设是 2,现在您选择的下一个元素遵循 1 1/2 时间和 2 1/2 时间的分布。精明吗?
      【解决方案6】:

      在 [0,1) 上选择一个随机数,这应该是 boost RNG 的默认 operator()。选择累积概率密度函数>=那个数的项目:

      template <class It,class P>
      It choose_p(It begin,It end,P const& p)
      {
          if (begin==end) return end;
          double sum=0.;
          for (It i=begin;i!=end;++i)
              sum+=p(*i);
          double choice=sum*random01();
          for (It i=begin;;) {
              choice -= p(*i);
              It r=i;
              ++i;
              if (choice<0 || i==end) return r;
          }
          return begin; //unreachable
      }
      

      其中 random01() 返回一个双精度 >=0 和

      p 只是一个函数,它为集合 [begin,end) 中的项目分配概率。如果您只有一系列概率,则可以省略它(或使用标识)。

      【讨论】:

        【解决方案7】:

        这是我对“加权随机”的理解,我最近一直在使用它。 (代码在 Python 中,但可以用其他语言实现)

        假设您想随机选择一个人,但他们被选中的机会并不相同 您可以给每个人一个“权重”或“机会”值:

        choices = [("Ade", 60), ("Tope", 50), ("Maryamu", 30)]
        

        您使用他们的权重计算每个分数,然后找到得分最高的选项

        highest = [None, 0]
        for p in choices:
            score = math.floor(random.random() * p[1])
            if score > highest[1]:
                highest[0] = p
                highest[1] = score
        
        print(highest)
        

        对于 Ade,他们可以获得的最高分是 60,Tope 50 等等,这意味着 Ade 比其他人更有可能获得最高分。

        您可以使用任何范围的权重,差异越大,分布越偏斜。 例如,如果 Ade 的权重为 1000,那么他们几乎总是会被选中。

        测试

        votes = [{"name": "Ade", "votes": 0}, {"name": "Tope", "votes": 0}, {"name": "Maryamu", "votes": 0]
        for v in range(100):
                
                highest = [None, 0]
                for p in choices:
                    score = math.floor(random.random() * p[1])
                    
                    if score > highest[1]:
                        highest[0] = p
                        highest[1] = score
        
                candidate = choices(index(highest[0])) # get index of person
                votes[candidate]["count"] += 1 # increase vote count
        print(votes)
        
        // votes printed at the end. your results might be different
        [{"name": "Ade", "votes": 45}, {"name": "Tope", "votes": 30}, {"name": "Maryamu", "votes": 25}]
        

        问题

        看起来选民越多,结果就越容易预测。哎呀

        希望这能给某人一个想法......

        【讨论】:

        • 为什么会有 math.floor?
        猜你喜欢
        • 2018-03-15
        • 1970-01-01
        • 1970-01-01
        • 2013-11-21
        • 1970-01-01
        • 1970-01-01
        • 2011-02-27
        • 2015-04-23
        • 1970-01-01
        相关资源
        最近更新 更多