Индексный метод генерации конечных дискретных распределений

в 20:23, , рубрики: Песочница

Иногда очень интересно провести имитацию броска кости. Для этого существует эффективный алгоритм, который позволяет сгенерировать значение выпавшее на верхней грани, используя псевдослучайное число alpha из равномерного распределения на [0,1]. А именно: image, где image — взятие целой части у аргумента.

Но предположим, что у нас «нечестная» кость и грани выпадают неравномерно. Пусть наша кость имеет K граней, и p_i вероятность выпадения грани image. При этом выполняется естественное ограничение image. Постараюсь ответить на вопрос: как смоделировать псевдослучайную последовательность с таким распределением?

#Naive approach
len<-10
ps<-seq(len,2, by=-1)
ps<- 1/ps^2
ps<-ps/sum(ps)
ss<-cumsum(ps)
gen_naiv <- function() {
  alpha<-runif(1)
  return (min(which(alpha<ss)))
}

#sample
val<-c()
len<-10000
for(i in 1:len) {
  val<-append(val, gen_naiv())
}
vals<-factor(val)
plot(vals)

image

Рассмотрим «наивный» вариант генерации такого распределения. Введем понятие частичных сумм image, можно выписать равенство image. Осталось по известным image и image найти конкретное i. Если просто перебирать по всем i начиная с 1 и заканчивая K, то в среднем требуется image операций сравнения. В худшем случае, который, кстати, встречается с вероятностью image, нам потребуется K сравнений, для примера с графика выше, он возникает с вероятностью 0.45473749. В среднем же требуется 7.5 сравнений и генерация одной случайной величины. Количество операций заставляет грустить особенно, если требуется смоделировать большое количество бросков неправильной костью.

Одна из идей как решить незадачу состоит в том, чтобы подобрать такую правильную кость, посмотрев на которую можно грубо приблизить нашу неправильную кость и сделать это очень быстро. Этот метод называется методом Чена, так же его можно найти под названиями «cutpoint method» или методом «индексного поиска».

Суть метода очень проста, разделим отрезок [0,1] на M равных частей. Введем дополнительный массив r длины M. В элементе r_j массива будет хранится i, такая что image:

#Preprocessing
ss<-cumsum(ps)
ss<-append(ss, 0, after=0)
M<-length(ss)
rs<-c()
for(k in 0:(M-1)) rs<-append(rs, min(which(ss>=k/M)))

Алгоритм генерации нового значения в таком случае принимает следующий вид:

#chen's method finite discrete distribution generator
gen_dfd <- function() {
  M<-10
  alpha<-runif(1)
  idx<-rs[floor(alpha*M)+1]
  return (idx-1+min(which(alpha<ss[idx:(M+1)]))-1)
}
#sample code
val<-c()
len<-10000
for(i in 1:len) {
  val<-append(val, gen_dfd())
}
vals<-factor(val)
plot(vals)

В худшем случае для примера, приведенного выше, нам потребуется (при M равном 9) 4 сравнения с вероятностью 0.03712143. В среднем же понадобится генерация одной случайной величины и 1.2 сравнения. Если памяти очень много и этап подготовки данных (задание p_i) относительно редок, M можно выбирать сколь угодно большим.

Метод просто в реализации и дает значительный выигрыш в скорости генерации большого количества реализаций одной и той же случайной величины, особенно в случае большого числа почти одинаковых состояний. На всякий случай приблизительная реализация на c++ выложена на pastebin.

Автор: kokorins

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js