web-dev-qa-db-ja.com

重み付き乱数

重み付き乱数を実装しようとしています。私は現在、頭を壁にぶつけているだけで、これを理解できません。

私のプロジェクト(ホールデムハンドレンジ、主観的オールインエクイティ分析)では、Boostのランダム関数を使用しています。したがって、1〜3の間の乱数(1、2、3のいずれか)を選択するとします。 Boostのメルセンヌツイスタージェネレーターは、このための魅力のように機能します。ただし、次のようにピックに重みを付けたいと思います。

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

Boostにはこのための何らかの機能がありますか?

85
nhaa123

アイテムをランダムに選択するための簡単なアルゴリズムがあります。アイテムには個別の重みがあります。

1)すべての重みの合計を計算する

2)0以上で重みの合計より小さい乱数を選択する

3)乱数がそのアイテムの重量よりも小さいアイテムが得られるまで、アイテムを一度に1つずつ調べて、乱数からその重量を引きます

これを示す擬似コード:

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");

これは、ブーストコンテナーなどに適応するために簡単です。


重みがめったに変更されないが、ランダムに1つを選ぶことが多く、コンテナーがオブジェクトへのポインターを格納している場合、または数十個以上のアイテムの長さがある場合(基本的に、これが役立つか妨げるかを知るためにプロファイルする必要があります) 、最適化があります:

各アイテムに累積重量の合計を保存することにより、 バイナリ検索 を使用して、ピック重量に対応するアイテムをピックできます。


リスト内のアイテムの数がわからない場合は、 リザーバーサンプリング と呼ばれる非常にきちんとしたアルゴリズムがあり、重み付けに適応させることができます。

146
Will

古い質問に対する回答を更新しました。 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で除算する必要があります。

47
Howard Hinnant

重みが描かれるよりもゆっくりと変化する場合、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は、初期化時にすべての累積合計を計算します。通常、1回のサンプリング時間を高速化するためO(N)コスト。たとえば、重みが存在するアイテムの数を表し、1つを描画するたびにそれを削除する場合、おそらくカスタムアルゴリズムが必要になります。

Willの答え https://stackoverflow.com/a/1761646/837451 はこのオーバーヘッドを回避しますが、バイナリ検索を使用できないため、C++ 11よりも描画が遅くなります。

これが行われていることを確認するには、関連する行(/usr/include/c++/5/bits/random.tcc私のUbuntu 16.04 + GCC 5.3のインストール):

  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;
    }
13
mmdanziger

数字に重みを付ける必要があるときに行うことは、重みに乱数を使用することです。

たとえば、次の重みで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になります。

必要に応じて使用できます。

幸運を祈ります!

10
Chirry

選択できるすべてのアイテムのバッグ(またはstd :: vector)を作成します。
各アイテムの数が重みに比例することを確認してください。

例:

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

したがって、1個が60個、2個が35個、3個が5個の100個のアイテムが入ったバッグがあります。
今、バッグをランダムにソートします(std :: random_shuffle)

空になるまで、バッグから要素を順番に選択します。
空になったら、バッグを再度ランダム化し、やり直します。

3
Martin York

[0,1)の乱数を選択します。これは、ブースト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()がdouble> = 0および<1を返す場合。上記では、合計が1になる確率は必要ありません。それはあなたのためにそれらを正規化します。

pは、コレクション内のアイテムに確率を割り当てる関数です[begin、end)。単に確率のシーケンスがある場合は、これを省略する(またはIDを使用する)ことができます。

0
Jonathan Graehl