генерация пуассоновских переменных в С++

Я реализовал эту функцию для генерации случайной величины Пуассона

typedef long unsigned int luint;
luint poisson(luint lambda) {
    double L = exp(-double(lambda));
    luint k = 0;
    double p = 1;
    do {
        k++;
        p *= mrand.rand();
    } while( p > L);
    return (k-1);
}

где mrand — генератор случайных чисел MersenneTwister. Я обнаружил, что по мере увеличения лямбда ожидаемое распределение будет неправильным со средним значением, которое достигает насыщения около 750. Это связано с числовыми приближениями или я сделал какие-то ошибки?


person Bob    schedule 14.04.2011    source источник
comment
IIRC, переменная Пуассона имеет экспоненциальное распределение. Следовательно, это точная копия stackoverflow.com/questions/ 2106503/. Но даже если я ошибаюсь, указанный там метод должен работать.   -  person MSalters    schedule 14.04.2011
comment
@MSalters: распределение Пуассона является дискретным - оно принимает только целые значения. Экспоненциальное распределение непрерывно. Так что они не одинаковы (хотя и родственны).   -  person TonyK    schedule 14.04.2011
comment
Справа, из Википедии: если количество прибытий в заданный интервал времени [0,t] следует распределению Пуассона со средним значением = λt, то длины интервалов между прибытиями следуют экспоненциальному распределению со средним значением 1/λ. Это эффективное преобразование между ними, структурно похожее на алгоритм, который я предложил ниже.   -  person MSalters    schedule 14.04.2011


Ответы (5)


Из другой вопрос I спрашивали ранее, кажется, вы также можете приблизить poisson(750) к poisson(375) + poisson(375).

person MSalters    schedule 14.04.2011

Если вы пойдете по пути "существующей библиотеки", ваш компилятор может уже поддерживать пакет C++11 std::random. Вот как вы его используете:

#include <random>
#include <ctime>
#include <iostream>

std::mt19937 mrand(std::time(0));  // seed however you want

typedef long unsigned int luint;

luint poisson(luint lambda)
{
    std::poisson_distribution<luint> d(lambda);
    return d(mrand);
}

int main()
{
    std::cout << poisson(750) << '\n';
    std::poisson_distribution<luint> d(750);
    std::cout << d(mrand) << '\n';
    std::cout << d(mrand) << '\n';
}

Я использовал его двумя способами выше:

  1. Я пытался имитировать ваш существующий интерфейс.

  2. Если вы создаете std::poisson_distribution со средним значением, более эффективно использовать это распределение снова и снова для одного и того же среднего значения (как это делается в main()).

Вот пример вывода для меня:

751
730
779
person Howard Hinnant    schedule 14.04.2011

exp(-750) - очень маленькое число, очень близкое к наименьшему возможному двойному значению, поэтому ваша проблема числовая. В любом случае ваша сложность будет линейной по лямбда, поэтому алгоритм не очень эффективен для высоких лямбда. Если у вас нет веских причин кодировать это самостоятельно, использование существующей реализации библиотеки, вероятно, имеет смысл, поскольку эти числовые алгоритмы, как правило, чувствительны именно к проблемам точности, с которыми вы сталкиваетесь.

person bsdfish    schedule 14.04.2011
comment
Думаю, я буду использовать нормальное приближение, так как в моем случае лямбда всегда большое число. - person Bob; 14.04.2011

Поскольку вы используете только L в выражении (p>L), вы, по сути, тестируете (log(p) > -lambda). Это не очень полезное преобразование. Конечно, вам больше не нужен exp(-750), но вместо этого вы просто переполнитесь p.

Теперь p — это просто Π(mrand.rand()), а log(p) — это log(Π(mrand.rand())) есть Σ(log(mrand.rand()). Это дает вам необходимое преобразование:

double logp = 0;
do {
    k++;
    logp += log(mrand.rand());
} while( logp > -lambda);

double имеет только 11-битную экспоненту, но 52-битную мантиссу. Следовательно, это значительное увеличение численной стабильности. Плата за это заключается в том, что вам нужно log на каждой итерации вместо одной exp заранее.

person MSalters    schedule 14.04.2011

В подобных ситуациях вам не нужно вызывать генератор случайных чисел более одного раза. Все, что вам нужно, это таблица кумулятивных вероятностей:

double c[k] = // the probability that X <= k (k = 0,...)

Затем сгенерируйте случайное число 0 <= r < 1 и возьмите первое целое число X такое, что c[X] > r. Вы можете найти этот X с помощью бинарного поиска.

Чтобы сгенерировать эту таблицу, нам нужны отдельные вероятности

p[k] = lambda^k / (k! e^lambda) // // the probability that X = k

Если lambda велико, это становится крайне неточным, как вы обнаружили. Но здесь мы можем использовать хитрость: начать с (или близкого) к наибольшему значению, с k = floor[lambda], и представить на данный момент, что p[k] равно 1. Затем вычислите p[i] для i > k, используя рекуррентное соотношение

p[i+1] = (p[i]*lambda) / (i+1)

и для i < k использования

p[i-1] = (p[i]*i)/lambda

Это гарантирует, что наибольшие вероятности имеют максимально возможную точность.

Теперь просто вычислите c[i], используя c[i+1] = c[i] + p[i+1], до точки, где c[i+1] совпадает с c[i]. Затем вы можете нормализовать массив, разделив на это предельное значение c[i]; или вы можете оставить массив как есть и использовать случайное число 0 <= r < c[i].

См.: http://en.wikipedia.org/wiki/Inverse_transform_sampling.

person TonyK    schedule 14.04.2011
comment
Не могли бы вы вместо этого сохранить log(p[k])? Это всего лишь (k log(λ)) / (λ * log(k!)), и вычислить это несложно (см. en.wikipedia.org/wiki/Factorial #Rate_of_growth для log(k!)) - person MSalters; 14.04.2011
comment
Это шаг назад. Точность log(k!) ухудшается по мере увеличения k, тогда как мы хотим, чтобы наиболее точные значения были около среднего значения, где k ~ лямбда. Кроме того, здесь вообще нет необходимости в журнале или опыте. - person TonyK; 14.04.2011