¿Por qué obtengo resultados desiguales cuando uso $ RANDOM?


14

Leí acerca de los RNG en Wikipedia y $RANDOMfuncionó en TLDP, pero en realidad no explica este resultado:

$ 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

¿Por qué los valores anteriores son aproximadamente 2 veces más propensos a ser 0, 1, 2 que 3, 4, 5, pero cuando cambio el módulo máximo, se distribuyen casi por igual en los 10 valores?

$ 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

99
La respuesta habitual a esto es volver a desplazarse (descartar el número que recibió y elegir otro) si se encuentra entre el valor máximo para ALEATORIO y el valor más alto posible que puede dividirse equitativamente en su módulo. Eso no es habitual para ALEATORIO, es habitual para usar el módulo-para-restringir-dominio-RNG en todos los idiomas / herramientas / etc. implementando RNG de ese tipo.
Charles Duffy el

77
Vea mi artículo de 2013 sobre el origen de este sesgo si desea algunos gráficos agradables de lo mal que se pone: ericlippert.com/2013/12/16/…
Eric Lippert

1
"La generación de números aleatorios es demasiado importante para dejarla al azar". - Robert Coveyou. Para su información: la mayoría de los programas no pueden generar números verdaderamente aleatorios
jesse_b

@ Eric Lippert gracias, ¡lo leeré con mucho gusto!
cprn

1
Tenga en cuenta que, aunque esté viendo problemas debido al sesgo de módulo, la $RANDOMvariable no utiliza un buen PRNG internamente.
bosque

Respuestas:


36

Para ampliar el tema del sesgo de módulo, su fórmula es:

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

Y en esta fórmula, $RANDOMes un valor aleatorio en el rango 0-32767.

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

Ayuda a visualizar cómo esto se asigna a los posibles valores:

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

Entonces, en su fórmula, la probabilidad de 0, 1, 2 es dos veces mayor que 4, 5. Y la probabilidad de 3 también es ligeramente mayor que 4, 5. De ahí su resultado con 0, 1, 2 como ganadores y 4, 5 como perdedores.

Al cambiar a 9*3600, resulta que:

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 tienen la misma probabilidad, pero todavía hay un ligero sesgo para 0 y, por lo tanto, 0 seguía siendo el ganador en su prueba con 100'000 iteraciones.

Para corregir el sesgo del módulo, primero debe simplificar la fórmula (si solo desea 0-5, entonces el módulo es 6, no 3600 o incluso un número más loco, no tiene sentido). Esta simplificación por sí sola reducirá su sesgo en gran medida (32766 se asigna a 0, 32767 a 1 dando un pequeño sesgo a esos dos números).

Para deshacerse del sesgo por completo, debe volver a tirar, (por ejemplo) cuando $RANDOMes inferior a 32768 % 6(eliminar los estados que no se asignan perfectamente al rango aleatorio disponible).

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

Resultado de la prueba:

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

La alternativa sería usar una fuente aleatoria diferente que no tenga un sesgo notable (órdenes de magnitud mayores que solo 32768 valores posibles). Pero implementar una lógica de repetición de todos modos no hace daño (incluso si es probable que nunca se cumpla).


Su respuesta es en gran medida correcta, excepto: "necesita volver a tirar cuando $ RANDOM es menor que 32768% 6" en realidad debería ser "igual o mayor que el piso ((RANDMAX + 1) / 6) * 6" (es decir, 32766 ) y corrija el código de shell asociado debajo de eso.
Nayuki

@Nayuki si puede señalar un error específico (que se aplica dentro del contexto dado), estaré encantado de corregirlo. Mi solución es solo un ejemplo, hay diferentes formas de hacerlo. Puede eliminar el sesgo del rango inicial o final, o en algún lugar en el medio, no hay diferencia. Puede calcularlo mejor (y no hacer un módulo en cada iteración). Puede manejar casos especiales como módulos arbitrarios y valores randmax, también manejar RANDMAX = INTMAX donde RANDMAX + 1 no existe, pero ese no era el enfoque aquí.
frostschutz

Tu respuesta es significativamente peor que tu publicación. En primer lugar, señalé específicamente qué frase tuya es realmente incorrecta. Tenga en cuenta que "32768% 6" == 2, por lo que desea volver a ejecutar cada vez $ RANDOM <2? Con respecto al sesgo al inicio / final / medio del rango, toda su publicación se trata de eliminar el sesgo al final del rango, y mi respuesta también responde exactamente a eso. En tercer lugar, habla sobre el manejo de RANDMAX = INTMAX, pero en su respuesta mencionó el valor 32768 (= 32767 + 1) varias veces, lo que implica que se siente cómodo con el cálculo de RANDMAX + 1.
Nayuki el

1
@Nayuki mi código elimina 0 y 1, el tuyo elimina 32766 y 32767 y me gustaría que explicaras: ¿qué diferencia hay? Solo soy humano, cometo errores, pero todo lo que has dicho hasta ahora es "está mal" sin explicar o mostrar por qué. Gracias.
frostschutz

1
No te preocupes, ya lo resolví. Perdón por la falsa alarma.
Nayuki el

23

Esto es sesgo de módulo. Si RANDOMestá bien construido, cada valor entre 0 y 32767 se produce con igual probabilidad. Cuando usa el módulo, cambia las probabilidades: las probabilidades de todos los valores por encima del módulo se agregan a los valores a los que se asignan.

En su ejemplo, 6 × 3600 es aproximadamente dos tercios del rango de valores. Por lo tanto, las probabilidades del tercio superior se suman a las del tercio inferior, lo que significa que los valores de 0 a 2 (aproximadamente) tienen el doble de probabilidades de producirse que los valores de 3 a 5. 9 × 3600 es casi 32767, por lo que el sesgo de módulo es mucho más pequeño y solo afecta valores de 32400 a 32767.

Para responder a su pregunta principal, al menos en Bash la secuencia aleatoria es completamente predecible si conoce la semilla. Ver intrand32en variables.c.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.