¿Cómo debo probar la aleatoriedad?


127

Considere un método para barajar elementos aleatoriamente en una matriz. ¿Cómo escribiría una prueba unitaria simple pero robusta para asegurarse de que esto funciona?

Se me ocurrieron dos ideas, las cuales tienen defectos notables:

  • Mezcle la matriz y luego asegúrese de que su orden sea diferente al anterior. Esto suena bien, pero falla si el aleatorio se baraja en el mismo orden. (Improbable, pero posible).
  • Mezcle la matriz con una semilla constante y compárela con la salida predeterminada. Esto se basa en la función aleatoria que siempre devuelve los mismos valores dados la misma semilla. Sin embargo, esto a veces es una suposición no válida .

Considere una segunda función que simula tiradas de dados y devuelve un número aleatorio. ¿Cómo probarías esta función? ¿Cómo probarías que la función ...

  • nunca devuelve un número fuera de los límites dados?
  • devuelve números en una distribución válida? (Uniforme para un dado, normal para grandes cantidades de dados).

Estoy buscando respuestas que ofrezcan información para probar no solo estos ejemplos, sino elementos aleatorios de código en general. ¿Son las pruebas unitarias incluso la solución correcta aquí? Si no, ¿qué tipo de pruebas son?


Sólo para aliviar la mente de todos que estoy no escribir mi propio generador de números aleatorios.


35
El acoplamiento apretado muestra su cabeza. Pase el objeto que genera los números aleatorios. Luego, durante la prueba, puede pasar un objeto que genera un conjunto específico de números para los que sabe cómo se ve el mazo después de la barajadura. Puede probar la aleatoriedad de su generador de números aleatorios por separado.
Martin York

1
Consideraría usar una rutina de biblioteca existente para shuffle (java Collections.shuffle () o similar). Hay una historia de advertencia para leer en developer.com/tech/article.php/616221/... sobre cómo escribir un algoritmo aleatorio defectuoso. Para escribir una función d6 (), uno lo probaría lo suficiente como para estar seguro de que no generará un número fuera de rango y luego realizará una prueba de chi cuadrado en la distribución (chi cuadrado es bastante sensible a secuencias pseudoaleatorias). Mire también el coeficiente de correlación serial.

"Esto se basa en la función aleatoria que siempre devuelve los mismos valores dados la misma semilla. Sin embargo, esto a veces es una suposición no válida". Seguí el enlace y no veo la suposición no válida. Dice claramente: "Si se usa la misma semilla repetidamente, se genera la misma serie de números".
Kyralessa

@ Kyralessa "No se garantiza que la implementación del generador de números aleatorios en la clase Random sea la misma en las principales versiones de .NET Framework". Así que no es una gran preocupación, pero aún es algo a considerar.
dlras2

44
@ Kyralessa Me perdí la mitad importante de esa cita: "Como resultado, el código de su aplicación no debe suponer que la misma semilla dará como resultado la misma secuencia pseudoaleatoria en diferentes versiones de .NET Framework".
dlras2

Respuestas:


102

No creo que las pruebas unitarias sean la herramienta adecuada para probar la aleatoriedad. Una prueba unitaria debe llamar a un método y probar el valor devuelto (o el estado del objeto) contra un valor esperado. El problema con la prueba de aleatoriedad es que no hay un valor esperado para la mayoría de las cosas que le gustaría probar. Puede probar con una semilla determinada, pero eso solo prueba la repetibilidad . No le da ninguna forma de medir qué tan aleatoria es la distribución, o si es siquiera aleatoria.

Afortunadamente, hay muchas pruebas estadísticas que puedes ejecutar, como la batería de pruebas de aleatoriedad Diehard . Ver también:

  1. ¿Cómo probar un generador de números pseudoaleatorios?

    • Steve Jessop recomienda que encuentre una implementación probada del mismo algoritmo RNG que está utilizando y compare su salida con las semillas seleccionadas con su propia implementación.
    • Greg Hewgill recomienda el conjunto de pruebas estadísticas ENT .
    • John D. Cook remite a los lectores a su artículo de CodeProject Simple Random Number Generation , que incluye una implementación de la prueba de Kolmogorov-Smirnov mencionada en el volumen 2 de Algoritmos Seminuméricos de Donald Knuth.
    • Varias personas recomiendan probar que la distribución de los números generados es uniforme, la prueba de Chi-cuadrado y probar que la media y la desviación estándar están dentro del rango esperado. (Tenga en cuenta que probar la distribución por sí sola no es suficiente. [1,2,3,4,5,6,7,8] es una distribución uniforme, pero ciertamente no es aleatoria).
  2. Prueba unitaria con funciones que devuelven resultados aleatorios

    • Brian Genisio señala que burlarse de su RNG es una opción para hacer que sus pruebas sean repetibles y proporciona un código de muestra C #.
    • Una vez más, varias personas más señalan el uso de valores de semilla fijos para la repetibilidad y pruebas simples de distribución uniforme, Chi-cuadrado, etc.
  3. Unit Testing Randomness es un artículo de wiki que habla sobre muchos de los desafíos que ya se mencionaron al tratar de probar lo que, por su naturaleza, no es repetible. Una parte interesante que deduje fue la siguiente:

    He visto a winzip usado como una herramienta para medir la aleatoriedad de un archivo de valores antes (obviamente, cuanto más pequeño puede comprimir el archivo, menos aleatorio es).


Otro buen conjunto de pruebas para la aleatoriedad estadística se encuentra 'ent' en fourmilab.ch/random .

1
¿Puedes resumir algunos de los enlaces que publicaste para completar la respuesta?
dlras2

@DanRasmussen Claro, tendré tiempo para hacerlo durante el fin de semana.
Bill the Lizard

44
"El problema con ... la aleatoriedad es que no hay un valor esperado ..." - qué irónico, dado que el "valor esperado" es un término bien definido en estadística. Y aunque esto no es lo que quiso decir, sugiere la solución correcta: usar propiedades conocidas de distribuciones estadísticas, junto con muestreo aleatorio y pruebas estadísticas , para determinar si un algoritmo funciona con una probabilidad muy alta. Sí, esa no es una prueba unitaria clásica, pero quería mencionarla ya que en el caso más sencillo solo se analiza la distribución de ... el valor esperado .
Konrad Rudolph

2
Hay una versión actualizada de la famosa Batería Diehard de Pruebas de Aleatoriedad en Dieharder, que incluye el Statistical Test Suite (STS) desarrollado por el Instituto Nacional de Estándares y Tecnología (NIST). Está disponible listo para ejecutarse en Ubuntu y presumiblemente en otras distribuciones: phy.duke.edu/~rgb/General/dieharder.php
nealmcb

21

1. Unidad prueba tu algoritmo

Para la primera pregunta, construiría una clase falsa en la que alimentaría una secuencia de números aleatorios para la cual conoce el resultado de su algoritmo. De esa manera, se asegura de que el algoritmo que construye sobre su función aleatoria funcione. Entonces algo en la línea de:

Random r = new RandomStub([1,3,5,3,1,2]);
r.random(); //returns 1
r.random(); //returns 3
...

2. Vea si su función aleatoria tiene sentido

A la prueba unitaria, debe agregar una prueba que se ejecute varias veces y afirme que los resultados

  • están dentro de los límites establecidos (por lo tanto, una tirada de dados es entre 1 y 6) y
  • muestre una distribución sensata (realice varias ejecuciones de prueba y vea si la distribución está dentro del x% de lo que esperaba, por ejemplo, para la tirada de dados debería ver un 2aumento entre el 10% y el 20% (1/6 = 16.67%) del tiempo dado que lo rodaste 1000 veces).

3. Prueba de integración para el algoritmo y la función aleatoria.

¿Con qué frecuencia esperaría que su matriz se clasifique en la clasificación original? Ordene un par de cientos de veces y afirme que solo el x% de las veces la clasificación no cambia.

Esto ya es una prueba de integración, está probando el algoritmo junto con la función aleatoria. Una vez que está utilizando la función aleatoria real, ya no puede salirse con una sola prueba.

Por experiencia (escribí un algoritmo genético), diría que combinar la prueba unitaria de su algoritmo, la prueba de distribución de su función aleatoria y la prueba de integración es el camino a seguir.


14

Un aspecto de los PRNG que parece olvidado es que todas sus propiedades son de naturaleza estadística: no se puede esperar que mezclar una matriz resulte en una permutación diferente de la que comenzó. Básicamente, si está utilizando un PRNG normal, lo único que tiene garantizado es que no utiliza un patrón simple (con suerte) y que incluso tiene una distribución entre el conjunto de números que devuelve.

Una prueba adecuada para un PRNG implicará ejecutarlo al menos 100 veces y luego verificar la distribución de la salida (que es una respuesta directa a la segunda parte de la pregunta).

Una respuesta a la primera pregunta es casi la misma: ejecute la prueba unas 100 veces con {1, 2, ..., n} y cuente la cantidad de veces que cada elemento ha estado en cada posición. Deben ser todos aproximadamente iguales si el método aleatorio es bueno.

Una cuestión completamente diferente es cómo probar PRNG de grado criptográfico. Este es un asunto en el que probablemente no debería detenerse, a menos que realmente sepa lo que está haciendo. Se sabe que las personas destruyen (léase: abren agujeros catastróficos) buenos criptosistemas con solo unas pocas 'optimizaciones' o ediciones triviales.

EDITAR: He releído completamente la pregunta, la respuesta principal y la mía. Si bien los puntos que planteo siguen en pie, respaldaría la respuesta de Bill The Lizard. Las pruebas unitarias son de naturaleza booleana: fallan o tienen éxito y, por lo tanto, no son adecuadas para probar "cuán buenas" son las propiedades de un PRNG (o un método que usa un PRNG), ya que cualquier respuesta a esta pregunta sería cuantitativa , en lugar de polar.


1
Creo que quiere decir que el número de veces que cada elemento está en cada posición debería ser aproximadamente igual. Si son consistentemente exactamente iguales, algo está muy mal.
octern

@octern Gracias, no sé cómo podría haber escrito eso ... estaba completamente mal hasta ahora ...
K.Steff

6

Hay dos partes en esto: probar la aleatorización y probar cosas que usan la aleatorización.

Probar la aleatorización es relativamente sencillo. Comprueba que el período del generador de números aleatorios es el que espera que sea (para algunas muestras que utilizan algunas semillas un poco aleatorias, dentro de cierto umbral) y que la distribución de la salida en un tamaño de muestra grande es la esperada que sea (dentro de cierto umbral).

Probar cosas que usan aleatorización se realiza mejor con un generador de números psuedo-aleatorio determinista. Dado que el resultado de la aleatorización se conoce en función de la semilla (sus entradas), puede realizar pruebas unitarias de forma normal en función de las entradas frente a las salidas esperadas. Si su RNG no es determinista, simule con uno que sea determinista (o simplemente no aleatorio). Pruebe la aleatorización de forma aislada del código que lo consume.


6

Deje que se ejecute varias veces y visualice sus datos .

Aquí hay un ejemplo de un shuffle de Coding Horror , puedes ver que el algoritmo está bien o no:

ingrese la descripción de la imagen aquí

Es fácil ver que cada elemento posible se devuelve al menos una vez (los límites están bien) y que la distribución está bien.


1
La visualización de +1 es la clave. Siempre me gustó el ejemplo con una imagen de un pingüino en la sección BCE del artículo de cifrado de bloque ). Un software automatizado rara vez puede detectar tales regularidades
Maksee

Eh? El objetivo de esa visualización es mostrar que la distribución no está bien. El ingenuo algoritmo aleatorio hace que ciertas órdenes sean mucho más probables que otras. Observe cuánto más a la derecha se extienden las barras 2341, 2314, 2143 y 1342?
hvd

4

Punteros generales que he encontrado útiles cuando se trata de código que toma entradas aleatorias: Verifique los casos límite de aleatoriedad esperada (valores máximo y mínimo, y los valores máximo + 1 y min-1 si corresponde). Verifique los lugares (en, arriba y abajo) donde los números tienen puntos de inflexión (es decir, -1, 0, 1 o mayor que 1, menor que 1 y no negativo para los casos en los que un valor fraccional podría estropear la función). Verifique algunos lugares completamente fuera de la entrada permitida. Verifique algunos casos típicos. También puede agregar una entrada aleatoria, pero para una prueba unitaria que tiene el efecto secundario indeseable de que el mismo valor no está bajo prueba cada vez que se ejecuta la prueba (un enfoque de semilla puede funcionar, pruebe los primeros 1,000 números aleatorios de semilla S o somesuch).

Para probar el resultado de una función aleatoria, es importante identificar el objetivo. En el caso de las cartas, ¿el objetivo es probar la uniformidad del generador aleatorio 0-1, para determinar si las 52 cartas aparecen en el resultado, o algún otro objetivo (tal vez toda esta lista y más)?

En el ejemplo específico, debe suponer que su generador de números aleatorios es opaco (al igual que no tiene sentido probar la syscall o malloc- del sistema operativo a menos que escriba sistemas operativos). Puede ser útil medir el generador de números aleatorios, pero su objetivo no es escribir un generador aleatorio, solo para ver que obtiene 52 tarjetas cada vez, y que cambian de orden.

Esa es una forma larga de decir que realmente hay dos tareas de prueba aquí: probar que el RNG está produciendo la distribución correcta y verificar que el código aleatorio de su tarjeta esté usando ese RNG para producir resultados aleatorios. Si está escribiendo el RNG, use un análisis estadístico para probar su distribución, si está escribiendo el barajador de tarjetas, asegúrese de que haya 52 tarjetas no repetidas en cada salida (es un mejor caso de prueba por inspección que está usando El RNG).


4

Puede confiar en generadores seguros de números aleatorios

Tuve un pensamiento horrible: no estás escribiendo tu propio generador de números aleatorios, ¿verdad?

Suponiendo que no lo eres, entonces debes probar el código del que eres responsable , no el código de otras personas (como la SecureRandomimplementación de tu marco).

Probar tu código

Para comprobar que su código responde correctamente, es normal utilizar un método de baja visibilidad para producir números aleatorios para que una clase de prueba unitaria pueda anularlo fácilmente. Este método anulado se burla efectivamente del generador de números aleatorios y le brinda un control completo sobre lo que se produce y cuándo. En consecuencia, puede ejercer completamente su código, que es el objetivo de las pruebas unitarias.

Obviamente, verificará las condiciones de borde y se asegurará de que el barajado tenga lugar exactamente como su algoritmo lo dicta dadas las entradas apropiadas.

Probar el generador seguro de números aleatorios

Si no está seguro de que el generador seguro de números aleatorios para su idioma no sea realmente aleatorio o tenga errores (proporciona valores fuera de rango, etc.), debe realizar un análisis estadístico detallado de la salida durante varios cientos de millones de iteraciones. Trace la frecuencia de ocurrencia de cada número y debería aparecer con la misma probabilidad. Si los resultados se sesgan de una forma u otra, debe informar sus hallazgos a los diseñadores del marco. Definitivamente estarán interesados ​​en solucionar el problema, ya que los generadores seguros de números aleatorios son fundamentales para muchos algoritmos de cifrado.


1

Bueno, nunca estarás 100% seguro, así que lo mejor que puedes hacer es que es probable que los números sean aleatorios. Elija una probabilidad: digamos que aparecerá una muestra de números o elementos x veces dado un millón de muestras, dentro de un margen de error. Ejecute la cosa un millón de veces y vea si está dentro del margen. Afortunadamente, las computadoras hacen que este tipo de cosas sea fácil de hacer.


¿Pero las pruebas unitarias como esta se consideran una buena práctica? Siempre he pensado que una prueba unitaria debería ser lo más simple posible: sin bucles, ramas o cualquier otra cosa que pueda evitarse.
dlras2

44
Las pruebas unitarias deben ser correctas . Si se necesita ramificación, bucles, recursión, ese es el precio. No puede realizar pruebas unitarias en clases extremadamente sofisticadas y altamente optimizadas con pruebas unitarias de una sola línea. He implementado el algoritmo de Dijkstra para probar una vez una clase.
K.Steff

3
@ K.Steff, wow. ¿Probó su unidad de prueba para verificar que el algoritmo de Dijkstra fuera correcto?
Winston Ewert

Buen punto, de hecho, sí, pero esta vez con pruebas 'triviales'. Sin embargo, también fueron pruebas unitarias para el programa original (A *). Creo que es una muy buena práctica: probar algoritmos rápidos contra implementaciones poco convincentes (pero correctas).
K.Steff

1

Para probar que una fuente de números aleatorios está generando algo que al menos tiene la apariencia de aleatoriedad, quisiera que la prueba generara una secuencia bastante grande de bytes, que los escribiera en un archivo temporal y que luego fuera a la herramienta ent de Fourmilab . Déle al conmutador -t (conciso) para que genere un CSV fácil de analizar. Luego, verifique los distintos números para ver si son "buenos".

Para decidir qué números son buenos, use una fuente conocida de aleatoriedad para calibrar su prueba. La prueba casi siempre debe pasar cuando se le da un buen conjunto de números aleatorios. Debido a que incluso una secuencia verdaderamente aleatoria tiene una probabilidad de generar una secuencia que parece no ser aleatoria, no puede obtener una prueba que seguramente pasará. Simplemente selecciona umbrales que hacen que sea improbable que una secuencia aleatoria cause una falla en la prueba. ¿No es divertido el azar?

Nota: No puede escribir una prueba que muestre que un PRNG genera una secuencia "aleatoria". Solo puede escribir una prueba que, si pasa, indica cierta probabilidad de que la secuencia generada por el PRNG sea "aleatoria". ¡Bienvenido a la alegría de la aleatoriedad!


1

Caso 1: Prueba de una barajadura:

Considere una matriz [0, 1, 2, 3, 4, 5], barajela, ¿qué puede salir mal? Lo habitual: a) sin barajar en absoluto, b) barajando 1-5 pero no 0, barajando 0-4 pero no 5, barajando y generando siempre el mismo patrón, ...

Una prueba para atraparlos a todos:

Mezcle 100 veces, agregue los valores en cada ranura. La suma de cada ranura debe ser similar entre sí. Avg / Stddev se puede calcular. (5 + 0) /2=2.5, 100 * 2.5 = 25. El valor esperado es de alrededor de 25, por ejemplo.

Si los valores están fuera de rango, existe una pequeña posibilidad de que obtenga un falso negativo. Puedes calcular qué tan grande es esa posibilidad. Repite la prueba. Bueno, por supuesto, hay una pequeña posibilidad de que la prueba falle 2 veces seguidas. Pero no tiene una rutina que elimine automáticamente su fuente, si la prueba unitaria falla, ¿verdad? Ejecútelo de nuevo!

¿Puede fallar 3 veces seguidas? Tal vez deberías probar suerte en la lotería.

Caso 2: tira un dado

La pregunta del lanzamiento de dados es la misma pregunta. Lanza los dados 6000 veces.

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
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.