Які переваги експоненціального генератора випадкових випадків використовують метод Аренса і Дітера (1972), а не шляхом зворотного перетворення?


11

Моє питання надихає вбудований генератор експоненціальних випадкових чисел R , функція rexp(). Намагаючись генерувати експоненціально розподілені випадкові числа, багато підручників рекомендують метод зворотного перетворення, як описано на цій сторінці Вікіпедії . Я усвідомлюю, що є інші методи для виконання цього завдання. Зокрема, у вихідному коді R використовується алгоритм, викладений у статті Ahrens & Dieter (1972) .

Я переконав себе, що метод Аренса-Дітера (AD) є правильним. Проте я не бачу вигоди від використання їх методу порівняно з методом зворотного перетворення (ІТ). AD не тільки складніший для впровадження, ніж ІТ. Зрозуміло, що також немає переваги швидкості. Ось мій код R для порівняння обох методів з результатами.

invTrans <- function(n)
    -log(runif(n))
print("For the inverse transform:")
print(system.time(invTrans(1e8)))
print("For the Ahrens-Dieter algorithm:")
print(system.time(rexp(1e8)))

Результати:

[1] "For the inverse transform:" 
user     system     elapsed
4.227    0.266      4.597 
[1] "For the Ahrens-Dieter algorithm:"
user     system     elapsed
4.919    0.265      5.213

Порівнюючи код для двох методів, AD малює щонайменше два рівномірних випадкових числа (з функцією Cunif_rand() ), щоб отримати одне експоненціальне випадкове число. ІТ потрібно лише одне єдине випадкове число. Імовірно, основна команда R вирішила проти впровадження ІТ, оскільки передбачала, що прийняття логарифму може бути повільніше, ніж генерування більш рівномірних випадкових чисел. Я розумію, що швидкість прийому логарифмів може бути машинно залежною, але принаймні для мене це навпаки. Можливо, є проблеми навколо чисельної точності ІТ, пов'язаних із особливістю логарифму при 0? Але тоді, вихідний код R sexp.cпоказує, що реалізація AD також втрачає деяку числову точність, оскільки наступна частина коду С видаляє провідні біти з рівномірного випадкового числа u .

double u = unif_rand();
while(u <= 0. || u >= 1.) u = unif_rand();
for (;;) {
    u += u;
    if (u > 1.)
        break;
    a += q[0];
}
u -= 1.;

U пізніше перероблені в вигляді однорідного випадкового числа в іншій частині sexp.c . Поки що, здається, ніби

  • ІТ простіше кодувати,
  • ІТ швидше, і
  • І IT, і AD можливо втрачають числову точність.

Я дуже вдячний, якби хтось міг пояснити, чому R все ще реалізує AD як єдиний доступний варіант для rexp().


4
З генераторами випадкових чисел "простіше кодувати" насправді не враховується, якщо ви не це зробите! Швидкість і точність - це лише два міркування. (Для рівномірних генераторів також існує період генератора.) У старі часи AD був швидшим. У моєму вікні Linux AD працює приблизно в 1/2 часу, коли працює ваша функція invTrans, а на моєму ноутбуці - приблизно в 2/3 часу. Можливо, ви хочете використовувати мікро-орієнтир і для більш комплексних термінів.
jbowman

5
Я б запропонував, щоб ми не мігрували. Це здається мені темою.
амеба

1
Зважаючи на те, що я не в змозі придумати єдиний сценарій, в якому rexp(n)було б вузьке місце, різниця в швидкості не є сильним аргументом для змін (принаймні для мене). Мене, можливо, більше турбує числова точність, хоча мені не зрозуміло, який із них був би більш чисельно надійним.
Кліф АВ

1
@amoeba Я думаю, що "Які б були переваги ..." було б перефразовуванням, яке тут було б чітко тематичним і не впливало б на будь-які існуючі відповіді. Я гадаю, "Чому люди, які зробили R, вирішили зробити ..." це насправді (а) специфічне для програмного забезпечення питання; (b) вимагає доказів у документації або телепатії, тому, можливо, це може бути поза темою. Особисто я вважаю за краще, щоб питання було переосмислено, щоб зробити його більш чітким у межах сайту, але я не вважаю це достатньо вагомим приводом для його закриття.
Срібна рибка

1
@amoeba У мене був хід. Не переконаний, що моя запропонована нова назва є особливо граматичною, і, можливо, деякі інші частини тексту запитання можуть змінитись. Але я сподіваюся, що це є більш чітким на тему, принаймні, і я не думаю, що це визнає недійсним або вимагає змін у будь-якій відповіді.
Срібна рибка

Відповіді:


9

На моєму комп’ютері (пробачте мою французьку!):

> print(system.time(rexp(1e8)))
utilisateur     système      écoulé 
      4.617       0.320       4.935 
> print(system.time(rexp(1e8)))
utilisateur     système      écoulé 
      4.589       2.045       6.629 
> print(system.time(-log(runif(1e8))))
utilisateur     système      écoulé 
      7.455       1.080       8.528 
> print(system.time(-log(runif(1e8))))
utilisateur     système      écoulé 
      9.140       1.489      10.623

обернене перетворення робить гірше. Але слід стежити за мінливістю. Введення параметра швидкості призводить до ще більшої мінливості для зворотного перетворення:

> print(system.time(rexp(1e8,rate=.01)))
utilisateur     système      écoulé 
      4.594       0.456       5.047 
> print(system.time(rexp(1e8,rate=.01)))
utilisateur     système      écoulé 
      4.661       1.319       5.976 
> print(system.time(-log(runif(1e8))/.01))
utilisateur     système      écoulé 
     15.675       2.139      17.803 
> print(system.time(-log(runif(1e8))/.01))
utilisateur     système      écoulé 
      7.863       1.122       8.977 
> print(system.time(rexp(1e8,rate=101.01)))
utilisateur     système      écoulé 
      4.610       0.220       4.826 
> print(system.time(rexp(1e8,rate=101.01)))
utilisateur     système      écoulé 
      4.621       0.156       4.774 
> print(system.time(-log(runif(1e8))/101.01))
utilisateur     système      écoulé 
      7.858       0.965       8.819 > 
> print(system.time(-log(runif(1e8))/101.01))
utilisateur     système      écoulé 
     13.924       1.345      15.262 

Ось порівняння з використанням rbenchmark:

> benchmark(x=rexp(1e6,rate=101.01))
  elapsed user.self sys.self
  4.617     4.564    0.056
> benchmark(x=-log(runif(1e6))/101.01)
  elapsed user.self sys.self
  14.749   14.571    0.184
> benchmark(x=rgamma(1e6,shape=1,rate=101.01))
  elapsed user.self sys.self
  14.421   14.362    0.063
> benchmark(x=rexp(1e6,rate=.01))
  elapsed user.self sys.self
  9.414     9.281    0.136
> benchmark(x=-log(runif(1e6))/.01)
  elapsed user.self sys.self
  7.953     7.866    0.092
> benchmark(x=rgamma(1e6,shape=1,rate=.01))
  elapsed user.self sys.self
  26.69    26.649    0.056

Тож пробіг все ще змінюється, залежно від масштабу!


2
На моєму ноутбуці час збігається з ОП так близько, що я підозрюю, що у нас однакова машина (або принаймні той самий процесор). Але я думаю, що Ваша думка полягає в тому, що перевага швидкості спостерігається залежно від платформи, і зважаючи на мінімальну різницю, немає явної переваги у порівнянні з іншою щодо швидкості.
Cliff AB

4
Чи можете ви microbenchmarkзамість цього виконати ?
Firebug

2
rexp-log(runif())5.27±0.02Rlogrunif

7

Це лише цитування статті в розділі "Алгоритм LG: (метод логарифму)":

X=ALOG(REGOL(IR))μμμu

Так виглядає, що автори обрали інші методи, щоб уникнути цього «виробника» обмеження повільних логарифмів. Можливо, тоді це питання найкраще перенести на stackoverflow, де хтось, хто має знання про кишки R, може прокоментувати.


6

Просто запустивши це microbenchmark; На моїй машині рідний підхід R рівномірно швидший:

library(microbenchmark)
microbenchmark(times = 10L,
               R_native = rexp(1e8),
               dir_inv = -log(runif(1e8)))
# Unit: seconds
#      expr      min       lq     mean   median       uq      max neval
#  R_native 3.643980 3.655015 3.687062 3.677351 3.699971 3.783529    10
#   dir_inv 5.780103 5.783707 5.888088 5.912384 5.946964 6.050098    10

λ=1

lambdas = seq(0, 10, length.out = 25L)[-1L]
png("~/Desktop/micro.png")
matplot(lambdas, 
        ts <- 
          t(sapply(lambdas, function(ll)
            print(microbenchmark(times = 50L,
                                 R_native = rexp(5e5, rate = ll),
                                 dir_inv = -log(runif(5e5))/ll),
                  unit = "relative")[ , "median"])),
        type = "l", lwd = 3L, xlab = expression(lambda),
        ylab = "Relative Timing", lty = 1L,
        col = c("black", "red"), las = 1L,
        main = paste0("Direct Computation of Exponential Variates\n",
                      "vs. R Native Generator (Ahrens-Dieter)"))
text(lambdas[1L], ts[1L, ], c("A-D", "Direct"), pos = 3L)
dev.off()

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.