加重乱数


101

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

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

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

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

回答:


179

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

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

これは、Boostコンテナなどに適応するのは簡単です。


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

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


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


3
最適化として、累積的な重みを使用し、バイナリ検索を使用できます。しかし、3つの異なる値については、これはおそらくやり過ぎです。
sellibitze

2
私はあなたが「順番に」と言うとき、choice_weight配列の事前ソートのステップを意図的に省略していると思いますか?
SilentDirge 2010年

2
@Aureis、配列をソートする必要はありません。私は自分の言語を明確にしようとしました。
ウィル

1
@Will:はい、しかし同じ名前のアルゴリズムがあります。sirkan.iit.bme.hu/~szirmay/c29.pdfおよびen.wikipedia.org/wiki/Photon_mapping 探しているA Monte Carlo method called Russian roulette is used to choose one of these actionsときにバケットに表示されます。「ロシアンルーレットアルゴリズム」。あなたはこれらの人々全員が間違った名前を持っていると主張するかもしれません。
v.oddou 2014年

3
今後の読者への注意:乱数から重みを差し引く部分は見過ごされがちですが、アルゴリズムにとって重要です(コメントで@kobikと同じ罠に陥りました)。
フランクシュミット

48

古い質問への回答を更新しました。std :: libだけで、C ++ 11でこれを簡単に行うことができます。

#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になるため、要求された出力を1.5で除算する必要があります。


この例のコンパイルに関する注意事項:C ++ 11 ieが必要です。gcc 4.6以降で利用可能な-std = c ++ 0xコンパイラフラグを使用します。
Pete855217 2012年

3
問題を解決するために必要な部品だけを選びたいですか?
Jonny、2015年

2
これが最良の答えですが、std::discrete_distribution代わりにstd::piecewise_constant_distributionさらに優れていると思います。
Dan

1
@ダン、はい、それはそれを行う別の優れた方法でしょう。あなたがそれをコード化してそれに答えるなら、私はそれに投票します。コードは上記のものとかなり似ていると思います。生成された出力に1つ追加するだけです。そして、分布への入力はより単純になります。この分野の回答の比較/対照セットは、読者にとって価値があるかもしれません。
ハワードヒナント2018年

15

重みが描画よりもゆっくり変化する場合は、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つを描画するたびにそれを削除する場合、おそらくカスタムアルゴリズムが必要になります。

ウィルの回答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;
    }

10

数字に重みを付ける必要がある場合、重みに乱数を使用します。

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

あなたはそれをあなたのニーズとして遊ぶことができます。

お役に立てれば幸いです。


これにより、動的に分布を調整できなくなります。
Josh C

2
ハッキーですが、私はそれが好きです。大まかな重み付けが必要なクイックプロトタイプに最適です。
2015年

1
これは有理重みに対してのみ機能します。1 / piの重みでそれを行うのは難しいでしょう;)
Joseph Budin

1
@JosephBudin次に、再び、不合理な重みを付けることはできません。フロートの重量に対しては、約43億のケース切り替えで問題ありません。:D
ジェイソンC

1
右@JasonC、問題は今では無限に小さくなっていますが、依然として問題です;)
Joseph Budin

3

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

例:

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

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

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


6
赤と青の大理石のバッグがあり、そこから赤い大理石を選択し、それを置き換えない場合、同じである別の赤い大理石を選択する可能性はありますか?同様に、「バッグから空になるまで順番に要素を選択する」というステートメントは、意図したものとはまったく異なる分布を生成します。
ldog 09/10/23

@ldog:私はあなたの議論を理解していますが、私たちは特定の分布を探している真のランダム性を探していません。この手法により、正しい配布が保証されます。
マーティンヨーク

4
私の要点は、私の以前の議論では、あなたが正しく分布を生み出さないということです。単純なカウンターの例を考えてみましょう。3の配列があると1,2,2すると、時間の1 1/3と2 2/3が生成されます。配列をランダム化し、最初に選択し、2としましょう。次に選択する要素は、時間の1 1/2と時間の2 1/2の分布に従います。精通?
ldog

0

[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()はdouble> = 0および<1を返します。上記では、確率が1になる必要がないことに注意してください。それはあなたのためにそれらを正規化します。

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.