가중 난수


101

가중치가있는 난수를 구현하려고합니다. 나는 현재 벽에 머리를 부딪 히고 있으며 이것을 알아낼 수 없습니다.

내 프로젝트 (Hold'em hand-ranges, 주관적인 올인 에퀴티 분석)에서는 Boost의 랜덤 함수를 사용하고 있습니다. 따라서 1과 3 사이의 임의의 숫자를 선택한다고 가정 해 봅시다 (1, 2 또는 3). Boost의 메르 센 트위스터 생성기는 이것에 대한 매력처럼 작동합니다. 그러나 다음과 같이 선택에 가중치를 부여하고 싶습니다.

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

Boost에 이것에 대한 일종의 기능이 있습니까?

답변:


179

무작위로 항목을 선택하는 간단한 알고리즘이 있으며 항목에는 개별 가중치가 있습니다.

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

이것은 부스트 ​​컨테이너 등에 쉽게 적용 할 수 있어야합니다.


가중치가 거의 변경되지 않지만 종종 무작위로 하나를 선택하고 컨테이너가 객체에 대한 포인터를 저장하거나 수십 개의 항목을 초과하는 한 (기본적으로 이것이 도움이되는지 방해 하는지를 알기 위해 프로파일 링해야합니다) , 최적화가 있습니다.

각 항목에 누적 가중치 합계를 저장하면 이진 검색 을 사용 하여 선택 가중치에 해당하는 항목을 선택할 수 있습니다 .


목록의 항목 수를 모르는 경우 가중치를 적용 할 수있는 저수지 샘플링 이라는 매우 깔끔한 알고리즘 이 있습니다.


3
최적화로 누적 가중치를 사용하고 이진 검색을 사용할 수 있습니다. 그러나 세 가지 다른 값에 대해서는 아마도 과잉 일 것입니다.
sellibitze

2
"순서대로"라고 말할 때 의도적으로 choice_weight 배열에서 사전 정렬 단계를 생략하고 있다고 생각합니다. 그렇죠?
SilentDirge 2011 년

2
@Aureis, 배열을 정렬 할 필요가 없습니다. 내 언어를 명확히하려고 노력했습니다.

1
@Will : 예,하지만 같은 이름의 알고리즘이 있습니다. sirkan.iit.bme.hu/~szirmay/c29.pdfen.wikipedia.org/wiki/Photon_mapping A Monte Carlo method called Russian roulette is used to choose one of these actions 검색 할 때 버킷에 표시됩니다. "러시아어 룰렛 알고리즘". 이 모든 사람들의 이름이 틀렸다고 주장 할 수 있습니다.
v.oddou

3
미래 독자를위한 참고 사항 : 난수에서 가중치를 빼는 부분 은 간과하기 쉽지만 알고리즘에 중요합니다 (댓글에서 @kobik과 동일한 함정에 빠졌습니다).
Frank Schmitt 2011 년

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로 나누어야합니다.


이 예제의 컴파일에 대한 참고 사항 : C ++ 11이 필요합니다. gcc 4.6 이상에서 사용 가능한 -std = c ++ 0x 컴파일러 플래그를 사용하십시오.
Pete855217

3
문제를 해결하는 데 필요한 부분 만 골라 주시겠습니까?
Jonny

2
이것이 최선의 대답이지만 std::discrete_distribution대신에 std::piecewise_constant_distribution더 좋았을 것이라고 생각 합니다 .
Dan

1
@Dan, 예, 그것은 그것을하는 또 다른 훌륭한 방법입니다. 당신이 그것을 코딩하고 그것에 대답한다면, 나는 그것에 투표 할 것입니다. 코드가 위에있는 것과 매우 유사 할 수 있다고 생각합니다. 생성 된 출력에 하나를 추가하기 만하면됩니다. 그리고 분포에 대한 입력은 더 간단 할 것입니다. 이 영역의 비교 / 대비 답변 세트는 독자에게 유용 할 수 있습니다.
Howard Hinnant

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은 초기화시 모든 누적 합계를 계산합니다. 일반적으로 한 번의 O (N) 비용으로 샘플링 시간을 단축하기 때문에이를 원합니다. 그러나 급변하는 배포의 경우 계산 (및 메모리) 비용이 많이 듭니다. 예를 들어 가중치가 항목 수를 나타내며 그릴 때마다 항목을 제거하면 사용자 지정 알고리즘이 필요할 것입니다.

Will의 답변 https://stackoverflow.com/a/1761646/837451 은이 오버 헤드를 피하지만 이진 검색을 사용할 수 없기 때문에 C ++ 11보다 가져 오는 속도가 느립니다.

이 작업을 수행하는지 확인하려면 관련 줄을 볼 수 있습니다 ( /usr/include/c++/5/bits/random.tccUbuntu 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;
}

이것으로 무작위로 1이 될 확률의 10 %, 2가 될 30 %, 3이 될 확률이 60 %입니다.

필요에 따라 사용할 수 있습니다.

내가 당신을 도울 수 있기를 바랍니다, 행운을 빕니다!


이것은 분포를 동적으로 조정하는 것을 배제합니다.
Josh C

2
Hacky하지만 나는 그것을 좋아한다. 대략적인 가중치를 원하는 빠른 프로토 타입에 적합합니다.
drewish

1
합리적인 가중치에 대해서만 작동합니다. ) 당신은 힘든 시간을 1 / 파이 무게로 그 일을해야합니다
조셉 Budin

1
@JosephBudin 그렇다면 다시 말하지만, 당신은 결코 비합리적인 무게를 가질 수 없을 것입니다. ~ 43 억 케이스 스위치는 플로트 웨이트에 적합합니다. : D
Jason C

1
오른쪽 @JasonC, 문제는 지금 무한히 작은하지만 여전히 문제)
조셉 Budin

3

집을 수있는 모든 항목의 가방 (또는 std :: vector)을 만드십시오.
각 항목의 수가 가중치에 비례하는지 확인하십시오.

예:

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

그러니 1이 60 개, 2가 35 개, 3이 5 개가 들어있는 100 개의 항목이있는 가방을 만드세요.
이제 무작위로 가방을 정렬합니다 (std :: random_shuffle).

가방이 비워 질 때까지 순차적으로 요소를 선택합니다.
비워지면 가방을 다시 무작위로 추출하고 다시 시작하십시오.


6
빨간색과 파란색 구슬이 들어있는 가방을 가지고 있고 그 중에서 빨간색 구슬을 선택하고 교체 하지 않으면 다른 빨간색 구슬을 선택할 확률은 여전히 ​​동일합니까? 같은 방식으로 "백에서 요소가 비어있을 때까지 순차적으로 선택"이라는 문장은 의도 한 것과 완전히 다른 분포를 생성합니다.
ldog

@ldog : 나는 당신의 주장을 이해하지만 우리는 특정 분포를 찾고있는 진정한 무작위성을 찾고 있지 않습니다. 이 기술은 올바른 분포를 보장합니다.
Martin York

4
내 요점은 정확히 당신이 내 이전 주장에 의해 분배를 올바르게 생성하지 않는다는 것입니다. 간단한 카운터 예제를 고려해보십시오. 3의 배열이 1,2,2시간의 1 1/3과 2 2/3 를 생성 한다고 가정합니다 . 배열을 무작위 화하고, 첫 번째를 선택하고, 2를 가정 해 보겠습니다. 이제 선택한 다음 요소는 시간의 1 1/2 및 2 1/2의 분포를 따릅니다. 알다?
ldog

0

부스트 RNG의 기본 operator () 여야하는 [0,1)에서 임의의 숫자를 선택합니다. 누적 확률 밀도 함수> = 해당 숫자가있는 항목을 선택합니다.

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.