逆変換ではなく、アーレンスとディーター(1972)の方法を使用する指数ランダムジェネレーターの利点は何ですか?


11

私の質問は、Rの組み込みの指数乱数ジェネレーターである関数に触発されましたrexp()。指数分布の乱数を生成しようとする場合、多くの教科書では、このWikipediaページで概説されている逆変換方法を推奨しています。このタスクを実行する他の方法があることを知っています。特に、Rのソースコードは、Ahrens&Dieter(1972)の論文で概説されているアルゴリズムを使用しています。

アーレンスディーター(AD)法が正しいことを確信しました。それでも、逆変換(IT)メソッドと比較して、これらのメソッドを使用する利点はわかりません。ADは、ITよりも実装が複雑なだけではありません。スピードメリットもないようです。以下に、両方の方法のベンチマークを行うための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

2つの方法のコードを比較すると、ADは少なくとも2つの均一な乱数(C関数を使用unif_rand())を描画して、1つの指数乱数を取得します。ITは1つの均一な乱数のみを必要とします。対数を取るのがより均一な乱数を生成するよりも遅いかもしれないと想定していたため、おそらくRコアチームはITの実装に反対しました。対数を取る速度はマシンに依存する可能性があることを理解していますが、少なくとも私にとってはその逆です。おそらく、ITの数値精度に関連して、0の対数の特異性に関係する問題があるのでしょうか。しかし、その後、R ソースコードsexp.cは、Cコードの次の部分が一様乱数uから先頭ビットを削除するため、ADの実装も数値精度を失うことを示しています。

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はコーディングが簡単です
  • ITはより高速であり、
  • ITとADの両方が数値精度を失う可能性があります。

RがADを唯一の利用可能なオプションとして実装している理由を誰かが説明していただければ幸いですrexp()


4
乱数ジェネレーターを使用する場合、「コーディングが簡単」であることは、あなたがそれを実行している人でない限り、実際には考慮に入れません。速度と精度だけが2つの考慮事項です。(均一な発電機の場合、発電機の周期もあります。)昔は、ADの方が高速でした。私のLinuxボックスでは、ADはinvTrans関数の約半分の時間で実行され、私のラップトップでは約2/3の時間で実行されます。また、より包括的なタイミングのためにマイクロベンチマークを使用することもできます。
jbowman

5
移行しないことをお勧めします。これは私には話題のようです。
アメーバは、モニカを

1
rexp(n)ボトルネックになる単一のシナリオを考えることができないことを考えると、速度の違いは、変更を強く主張するものではありません(少なくとも私にとって)。どちらがより数値的に信頼できるかははっきりしませんが、数値の精度についてもっと心配するかもしれません。
クリフAB

1
@amoeba「...の利点は何でしょうか」は、ここで明らかに話題になっている言い換えであり、既存の回答には影響を与えないと思います。「なぜRに決定を下したのか...」は本当に(a)ソフトウェア固有の質問であり、(b)ドキュメントまたはテレパシーの証拠が必要であるため、おそらくここでは話題から外れている可能性があります。個人的には、質問をサイトの範囲内でより明確にするために言い換えた方がいいですが、これを閉じる十分な理由とは思いません。
Silverfish

1
@amoebaやってみました。私が提案した新しいタイトルが特に文法的であるとは確信していません。おそらく、質問テキストの他のいくつかの部分が変更に関連している可能性があります。しかし、私はこれが少なくともトピックについてより明確になっているといいのですが、どちらの回答も無効にしたり、変更を要求したりするとは思いません。
Silverfish

回答:


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
私のラップトップでは、時間はOPと非常によく一致しているため、同じマシン(または少なくとも同じプロセッサ)を使用していると思います。しかし、ここでのあなたの要点は、観察された速度の利点はプラットフォームに依存することであり、最小限の違いを考えると、速度に関して他のものと比べて明確な利点はありません。
クリフAB

4
microbenchmark代わりに実行することはできますか?
Firebug

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

7

これは、「アルゴリズムLG:(対数法)」セクションの記事を引用しているだけです。

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

したがって、作成者は、遅い「対数」のこの「製造元」の制限を回避するために他の方法を選択したように見えます。おそらく、この質問は、Rの根性についての知識を持つ誰かがコメントできるstackoverflowに移動するのが最適です。


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

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.