Những lợi thế của một máy tạo ngẫu nhiên theo cấp số nhân sử dụng phương pháp của AhDR và ​​Dieter (1972) thay vì biến đổi nghịch đảo là gì?


11

Câu hỏi của tôi được lấy cảm hứng từ hàm tạo số ngẫu nhiên theo hàm mũ tích hợp của R , hàm rexp(). Khi cố gắng tạo các số ngẫu nhiên phân tán theo cấp số nhân, nhiều sách giáo khoa khuyên dùng phương pháp biến đổi nghịch đảo như được nêu trong trang Wikipedia này . Tôi biết rằng có những phương pháp khác để hoàn thành nhiệm vụ này. Cụ thể, mã nguồn của R sử dụng thuật toán được phác thảo trong một bài báo của Ahrens & Dieter (1972) .

Tôi đã thuyết phục bản thân mình rằng phương pháp AhDR-Dieter (AD) là chính xác. Tuy nhiên, tôi không thấy lợi ích của việc sử dụng phương pháp của họ so với phương pháp biến đổi nghịch đảo (IT). AD không chỉ phức tạp để thực hiện hơn CNTT. Dường như cũng không có lợi ích về tốc độ. Đây là mã R của tôi để điểm chuẩn cả hai phương pháp theo sau là kết quả.

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

Các kết quả:

[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

So sánh mã cho hai phương thức, AD rút ra ít nhất hai số ngẫu nhiên đồng nhất (với hàm Cunif_rand() ) để có được một số ngẫu nhiên theo cấp số nhân. CNTT chỉ cần một số ngẫu nhiên thống nhất. Có lẽ nhóm lõi R đã quyết định chống lại việc triển khai CNTT vì họ cho rằng việc lấy logarit có thể chậm hơn so với việc tạo ra các số ngẫu nhiên thống nhất hơn. Tôi hiểu rằng tốc độ lấy logarit có thể phụ thuộc vào máy, nhưng ít nhất với tôi thì điều ngược lại là đúng. Có lẽ có những vấn đề xung quanh độ chính xác số của CNTT phải làm với điểm kỳ dị của logarit ở 0? Nhưng sau đó, mã nguồn R sexp.ccho thấy việc thực hiện AD cũng làm mất một số độ chính xác bằng số vì phần sau của mã C sẽ loại bỏ các bit hàng đầu khỏi số ngẫu nhiên thống nhất 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 sau được tái chế như một số ngẫu nhiên thống nhất trong phần còn lại của sexp.c . Cho đến nay, nó xuất hiện như thể

  • CNTT dễ mã hóa hơn,
  • CNTT nhanh hơn, và
  • cả CNTT và AD đều có thể mất độ chính xác về số.

Tôi thực sự sẽ đánh giá cao nếu ai đó có thể giải thích tại sao R vẫn thực hiện AD là lựa chọn khả dụng duy nhất cho rexp().


4
Với các trình tạo số ngẫu nhiên, "mã dễ dàng hơn" không thực sự được xem xét trừ khi bạn là người làm việc đó! Tốc độ và độ chính xác là hai cân nhắc duy nhất. (Đối với máy phát điện đồng đều, cũng có thời kỳ của máy phát điện.) Ngày xưa, AD nhanh hơn. Trên hộp Linux của tôi, AD chạy trong khoảng 1/2 thời gian chức năng invTrans của bạn và trên máy tính xách tay của tôi trong khoảng 2/3 thời gian. Bạn cũng có thể muốn sử dụng microbenchmark cho thời gian toàn diện hơn.
jbowman

5
Tôi sẽ đề nghị chúng ta không di chuyển nó. Điều này có vẻ như trên chủ đề với tôi.
amip nói rằng Phục hồi lại

1
Cho rằng tôi không thể nghĩ ra một kịch bản duy nhất trong đó rexp(n)sẽ là nút cổ chai, sự khác biệt về tốc độ không phải là một lý lẽ mạnh mẽ để thay đổi (ít nhất là với tôi). Tôi có thể quan tâm nhiều hơn về độ chính xác của số, mặc dù tôi không rõ cái nào đáng tin cậy hơn về mặt số.
Vách đá AB

1
@amoeba Tôi nghĩ rằng "Điều gì sẽ là lợi thế của ..." sẽ là một sự chia sẻ lại rõ ràng về chủ đề ở đây, và sẽ không ảnh hưởng đến bất kỳ câu trả lời hiện có nào. Tôi cho rằng "Tại sao những người khiến R quyết định làm ..." thực sự là (a) một câu hỏi dành riêng cho phần mềm, (b) yêu cầu bằng chứng trong tài liệu hoặc thần giao cách cảm, vì vậy có thể được cho là lạc đề ở đây. Cá nhân tôi muốn thay vì câu hỏi được đăng lại để làm cho nó rõ ràng hơn trong phạm vi của trang web, nhưng tôi không thấy đây là một lý do đủ mạnh để đóng nó.
Cá bạc

1
@amoeba Tôi đã có một chuyến đi. Không thuyết phục tiêu đề mới được đề xuất của tôi đặc biệt là ngữ pháp, và có lẽ một vài phần khác của văn bản câu hỏi có thể làm với việc thay đổi. Nhưng tôi hy vọng điều này rõ ràng hơn về chủ đề, ít nhất, và tôi không nghĩ nó vô hiệu hoặc yêu cầu thay đổi để trả lời.
Cá bạc

Câu trả lời:


9

Trên máy tính của tôi (xin lỗi tiếng Pháp của tôi!):

> 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

biến đổi nghịch đảo làm tồi tệ hơn. Nhưng bạn nên coi chừng sự thay đổi. Giới thiệu một tham số tỷ lệ dẫn đến sự biến đổi thậm chí nhiều hơn cho biến đổi nghịch đảo:

> 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 

Dưới đây là các so sánh sử dụng 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

Vì vậy, số dặm vẫn thay đổi, tùy thuộc vào quy mô!


2
Trên máy tính xách tay của tôi, thời gian khớp với OP rất chặt chẽ đến nỗi tôi nghi ngờ chúng ta có cùng một máy (hoặc ít nhất là cùng một bộ xử lý). Nhưng tôi nghĩ rằng quan điểm của bạn ở đây là lợi thế về tốc độ được quan sát là phụ thuộc vào nền tảng và với sự khác biệt tối thiểu, không có lợi thế rõ ràng nào so với tốc độ khác.
Vách đá AB

4
Có lẽ bạn có thể thực hiện một microbenchmarkthay thế?
Bọ lửa

2
Thời gian hệ thống xuất hiện để đo chi phí biến đổi cao, có lẽ do gián đoạn và phân trang bộ nhớ. Thật thú vị, như @Cliff lưu ý, để thấy sự khác biệt tương đối lớn về hiệu suất giữa các hệ thống. Chẳng hạn, trên Xeon có nhiều RAM, tôi hầu như không mất thời gian hệ thống (0,05 đến 0,32 giây), thời gian người dùng dài hơn khoảng 12% rexp, thời gian người dùng ngắn hơn 3% -log(runif())và không thay đổi với thông số tốc độ ( tổng số giây). Tất cả chúng ta đều mặc nhiên cho rằng đang đạt được thời gian và có thể so sánh với những gì người ta sẽ nhận được với chương trình con Fortran. 5.27±0.02Rlogrunif
whuber

7

Đây chỉ là trích dẫn bài viết trong phần "Thuật toán LG: (Phương pháp logarit)":

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

Vì vậy, có vẻ như các tác giả đã chọn các phương pháp khác để tránh giới hạn "nhà sản xuất" này của logarit chậm. Có lẽ câu hỏi này sau đó được chuyển tốt nhất sang stackoverflow nơi ai đó có kiến ​​thức về can đảm của R có thể nhận xét.


6

Chỉ cần chạy này với microbenchmark; trên máy của tôi, cách tiếp cận riêng của R nhanh hơn:

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

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.