Tại sao tôi nhận được kết quả trải đều không đồng đều khi sử dụng $ RANDOM?


14

Tôi đã đọc về RNG trên Wikipedia$RANDOMhoạt động trên TLDP nhưng nó không thực sự giải thích kết quả này:

$ max=$((6*3600))
$ for f in {1..100000}; do echo $(($RANDOM%max/3600)); done | sort | uniq -c
  21787 0
  22114 1
  21933 2
  12157 3
  10938 4
  11071 5

Tại sao các giá trị ở trên cao hơn khoảng 2 lần có xu hướng là 0, 1, 2 hơn 3, 4, 5 nhưng khi tôi thay đổi modulo tối đa, chúng gần như trải đều trên tất cả 10 giá trị?

$ max=$((9*3600))
$ for f in {1..100000}; do echo $(($RANDOM%max/3600)); done | sort | uniq -c
  11940 0
  11199 1
  10898 2
  10945 3
  11239 4
  10928 5
  10875 6
  10759 7
  11217 8

9
Câu trả lời thông thường cho điều này là reroll (loại bỏ số bạn nhận được và chọn số khác) nếu bạn nằm giữa giá trị tối đa cho RANDOM và giá trị cao nhất có thể có thể chia đều cho modulo của bạn. Đó không phải là thông thường đối với RANDOM, đó là tên miền RNG thường dùng để sử dụng modulo để hạn chế trên tất cả các ngôn ngữ / công cụ / v.v. thực hiện RNG loại đó.
Charles Duffy

7
Xem bài viết năm 2013 của tôi về nguồn gốc của sự thiên vị này nếu bạn muốn một số biểu đồ đẹp về mức độ tệ hại của nó: ericlippert.com/2013/12/16/ mẹo
Eric Lippert

1
"Việc tạo ra các số ngẫu nhiên là quá quan trọng để có cơ hội." - Robert Coveyou. FYI mặc dù: hầu hết các chương trình không thể tạo ra các số thực sự ngẫu nhiên
Jesse_b

@Eric Lippert cảm ơn bạn, tôi sẽ đọc nó một cách vui vẻ!
cprn

1
Lưu ý rằng, mặc dù bạn đang thấy các vấn đề do sai lệch modulo, $RANDOMbiến không sử dụng PRNG tốt trong nội bộ.
rừng

Câu trả lời:


36

Để mở rộng về chủ đề thiên vị modulo, công thức của bạn là:

max=$((6*3600))
$(($RANDOM%max/3600))

Và trong công thức này, $RANDOMlà một giá trị ngẫu nhiên trong phạm vi 0-32767.

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.

Nó giúp hình dung làm thế nào bản đồ này đến các giá trị có thể:

0 = 0-3599
1 = 3600-7199
2 = 7200-10799
3 = 10800-14399
4 = 14400-17999
5 = 18000-21599
0 = 21600-25199
1 = 25200-28799
2 = 28800-32399
3 = 32400-32767

Vì vậy, trong công thức của bạn, xác suất cho 0, 1, 2 gấp đôi so với 4, 5. Và xác suất của 3 cũng cao hơn 4, 5 một chút. Do đó, kết quả của bạn với 0, 1, 2 là người chiến thắng và 4, 5 là người thua cuộc.

Khi đổi thành 9*3600, hóa ra là:

0 = 0-3599
1 = 3600-7199
2 = 7200-10799
3 = 10800-14399
4 = 14400-17999
5 = 18000-21599
6 = 21600-25199
7 = 25200-28799
8 = 28800-32399
0 = 32400-32767

1-8 có cùng xác suất, nhưng vẫn có sai lệch nhỏ cho 0 và do đó 0 vẫn là người chiến thắng trong bài kiểm tra của bạn với 100'000 lần lặp.

Để khắc phục sai lệch modulo, trước tiên bạn nên đơn giản hóa công thức (nếu bạn chỉ muốn 0-5 thì modulo là 6, không phải 3600 hoặc thậm chí là số điên hơn, không có ý nghĩa gì trong đó). Chỉ riêng việc đơn giản hóa này sẽ làm giảm rất nhiều sự thiên vị của bạn (32766 ánh xạ thành 0, 32767 thành 1 tạo ra sự thiên vị nhỏ cho hai số đó).

Để loại bỏ hoàn toàn sai lệch, bạn cần cuộn lại, (ví dụ) khi $RANDOMthấp hơn 32768 % 6(loại bỏ các trạng thái không ánh xạ hoàn hảo đến phạm vi ngẫu nhiên có sẵn).

max=6
for f in {1..100000}
do
    r=$RANDOM
    while [ $r -lt $((32768 % $max)) ]; do r=$RANDOM; done
    echo $(($r%max))
done | sort | uniq -c | sort -n

Kết quả kiểm tra:

  16425 5
  16515 1
  16720 0
  16769 2
  16776 4
  16795 3

Giải pháp thay thế sẽ là sử dụng một nguồn ngẫu nhiên khác nhau không có sai lệch đáng chú ý (các đơn đặt hàng có cường độ lớn hơn chỉ có 32768 giá trị có thể). Nhưng dù sao thì việc thực hiện logic cuộn lại cũng không gây hại gì (ngay cả khi nó có thể không bao giờ được thông qua).


Câu trả lời của bạn phần lớn là chính xác, ngoại trừ: "bạn cần cuộn lại khi $ RANDOM thấp hơn 32768% 6" nên thực sự "bằng hoặc lớn hơn sàn ((RANDMAX + 1) / 6) * 6" (tức là 32766 ) và sửa mã shell liên quan bên dưới đó.
Nayuki

@Nayuki nếu bạn có thể chỉ ra một lỗi cụ thể (áp dụng trong ngữ cảnh cụ thể) tôi sẽ vui lòng sửa nó. Giải pháp của tôi chỉ là một ví dụ, có nhiều cách khác nhau để làm điều đó. Bạn có thể xóa độ lệch khỏi phạm vi bắt đầu hoặc phạm vi kết thúc hoặc ở đâu đó ở giữa, điều này không tạo ra sự khác biệt. Bạn có thể tính toán nó tốt hơn (và không thực hiện một modulo trong mỗi lần lặp). Bạn có thể xử lý các trường hợp đặc biệt, chẳng hạn như các giá trị modul và randmax tùy ý, cũng xử lý RANDMAX = INTMAX trong đó RANDMAX + 1 không tồn tại, nhưng đó không phải là trọng tâm ở đây.
frostschutz

Trả lời của bạn là tồi tệ hơn đáng kể so với bài viết của bạn. Trước hết, tôi đã chỉ ra cụ thể cụm từ nào của bạn thực sự sai. Lưu ý rằng "32768% 6" == 2, vì vậy bạn muốn chạy lại mỗi lần $ RANDOM <2? Về xu hướng ở đầu / cuối / trung gian của phạm vi, toàn bộ bài viết của bạn là về việc loại bỏ thiên vị ở cuối phạm vi và phản hồi của tôi cũng đáp ứng chính xác điều đó. Thứ ba, bạn nói về việc xử lý RANDMAX = INTMAX, nhưng trong câu trả lời của bạn, bạn đã đề cập đến giá trị 32768 (= 32767 + 1) nhiều lần, điều đó ngụ ý rằng bạn cảm thấy thoải mái khi tính toán RANDMAX + 1.
Nayuki

1
@Nayuki mã của tôi xóa 0 và 1, mã của bạn xóa 32766 và 32767 và tôi muốn bạn giải thích: nó có gì khác biệt? Tôi chỉ là con người, tôi phạm sai lầm, nhưng tất cả những gì bạn nói cho đến nay là "nó sai" mà không giải thích hoặc cho thấy lý do tại sao. Cảm ơn bạn.
frostschutz

1
Đừng bận tâm, đã tìm ra nó. Xin lỗi về báo động sai.
Nayuki

23

Đây là sự thiên vị modulo. NếuRANDOM được xây dựng tốt, mỗi giá trị từ 0 đến 32767 được tạo ra với xác suất bằng nhau. Khi bạn sử dụng modulo, bạn thay đổi xác suất: xác suất của tất cả các giá trị trên modulo được thêm vào các giá trị mà chúng ánh xạ tới.

Trong ví dụ của bạn, 6 × 3600 xấp xỉ hai phần ba phạm vi giá trị. Do đó, xác suất của phần ba trên cùng được thêm vào phần ba dưới cùng, điều đó có nghĩa là các giá trị từ 0 đến 2 (xấp xỉ) có khả năng được tạo ra gấp đôi so với các giá trị từ 3 đến 5. 9 × 3600 là gần 32767, vì vậy độ lệch modulo nhỏ hơn nhiều và chỉ ảnh hưởng đến các giá trị từ 32400 đến 32767.

Để trả lời câu hỏi chính của bạn, ít nhất là trong Bash, chuỗi ngẫu nhiên hoàn toàn có thể dự đoán được nếu bạn biết hạt giống. Xem intrand32trong variables.c.

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.