Quali sono i vantaggi di un generatore casuale esponenziale che usa il metodo di Ahrens e Dieter (1972) piuttosto che dalla trasformazione inversa?


11

La mia domanda è ispirata al generatore di numeri casuali esponenziale incorporato di R , la funzione rexp(). Quando si tenta di generare numeri casuali distribuiti in modo esponenziale, molti libri di testo raccomandano il metodo di trasformazione inversa come indicato in questa pagina di Wikipedia . Sono consapevole che esistono altri metodi per svolgere questo compito. In particolare, il codice sorgente di R utilizza l'algoritmo descritto in un documento di Ahrens & Dieter (1972) .

Mi sono convinto che il metodo Ahrens-Dieter (AD) sia corretto. Tuttavia, non vedo il vantaggio di usare il loro metodo rispetto al metodo di trasformazione inversa (IT). AD non è solo più complesso da implementare rispetto all'IT. Nemmeno sembra esserci un vantaggio in termini di velocità. Ecco il mio codice R per confrontare entrambi i metodi seguiti dai risultati.

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

risultati:

[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

Confrontando il codice per i due metodi, AD disegna almeno due numeri casuali uniformi (con la funzione Cunif_rand() ) per ottenere un numero casuale esponenziale. L'IT ha bisogno solo di un numero casuale uniforme. Presumibilmente il team di core R ha deciso di non implementare l'IT perché ha assunto che il logaritmo potrebbe essere più lento della generazione di numeri casuali più uniformi. Capisco che la velocità di acquisizione dei logaritmi può dipendere dalla macchina, ma almeno per me è vero il contrario. Forse ci sono problemi nella precisione numerica dell'IT che hanno a che fare con la singolarità del logaritmo a 0? Ma poi, il codice sorgente R sexp.crivela che l'implementazione di AD perde anche una certa precisione numerica perché la seguente porzione di codice C rimuove i bit iniziali dal numero casuale uniforme 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 è successivamente riciclato come un numero casuale uniforme nella restante sexp.c . Finora sembra che

  • L'IT è più facile da codificare,
  • L'IT è più veloce e
  • sia l'IT che l'AD probabilmente perdono la precisione numerica.

Gradirei davvero se qualcuno potesse spiegare perché R implementa ancora AD come l'unica opzione disponibile per rexp().


4
Con generatori di numeri casuali, "più facile da programmare" non è in realtà una considerazione a meno che tu non sia quello che lo fa! Velocità e precisione sono le uniche due considerazioni. (Per i generatori uniformi, c'è anche il periodo del generatore.) Ai vecchi tempi, AD era più veloce. Sulla mia macchina Linux, AD viene eseguito in circa la metà del tempo della funzione invTrans e sul mio laptop in circa i 2/3 del tempo. Potresti voler usare il microbenchmark anche per tempistiche più complete.
jbowman,

5
Suggerirei di non migrarlo. Questo mi sembra in tema.
ameba dice di reintegrare Monica il

1
Dato che non sono in grado di escogitare un singolo scenario in cui si rexp(n)verifichino i colli di bottiglia, la differenza di velocità non è un argomento forte per il cambiamento (almeno per me). Potrei essere più preoccupato per l'accuratezza numerica, anche se non mi è chiaro quale sarebbe più affidabile dal punto di vista numerico.
Cliff AB,

1
@amoeba Penso che "Quali sarebbero i vantaggi di ..." sarebbe una riformulazione che sarebbe chiaramente sul tema qui, e non influirebbe sulle risposte esistenti. Suppongo che "Perché le persone che hanno fatto decidere a R di fare ..." sia davvero (a) una domanda specifica del software, (b) richiede prove nella documentazione o telepatia, quindi potrebbe essere fuori tema. Personalmente preferirei riformulare la domanda per renderla più chiara nell'ambito del sito, ma non vedo questo come un motivo abbastanza forte per chiuderlo.
Silverfish

1
@amoeba Ci ho provato. Non sono convinto che il mio nuovo titolo suggerito sia particolarmente grammaticale, e forse alcune altre parti del testo della domanda potrebbero cambiare. Ma spero che questo sia più chiaramente in tema, almeno, e non penso che invalidi o richieda modifiche a entrambe le risposte.
Silverfish

Risposte:


9

Sul mio computer (scusate il mio francese!):

> 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

la trasformazione inversa fa peggio. Ma dovresti stare attento alla variabilità. L'introduzione di un parametro rate determina una variabilità ancora maggiore per la trasformata inversa:

> 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 

Ecco i confronti usando 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

Quindi il chilometraggio varia ancora, a seconda della scala!


2
Sul mio laptop, i tempi coincidono con gli OP così da vicino che sospetto che abbiamo la stessa macchina (o almeno lo stesso processore). Ma penso che il tuo punto qui sia il vantaggio di velocità osservato dipende dalla piattaforma e, data la minima differenza, non c'è un chiaro vantaggio l'uno rispetto all'altro in termini di velocità.
Cliff AB,

4
Potresti forse eseguire un microbenchmarkinvece?
Firebug,

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

7

Questo è solo citando l'articolo nella sezione "Algoritmo LG: (metodo Logarithm)":

X=-UNLOsol(REsolOL(ioR))μμμu

Quindi sembra che gli autori abbiano optato per altri metodi per evitare questa limitazione "di fabbricazione" dei logaritmi lenti. Forse questa domanda viene quindi spostata al meglio nello stackoverflow in cui qualcuno con conoscenza sulle viscere di R può commentare.


6

Sto solo eseguendo questo microbenchmark; sulla mia macchina, l'approccio nativo di R è uniformemente più veloce:

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()

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.