Numpy: Decidir sobre el nuevo PRNG BitGenerator predeterminado

Creado en 27 may. 2019  ·  166Comentarios  ·  Fuente: numpy/numpy

13163 traerá el tan esperado reemplazo de la infraestructura PRNG de numpy. Con el fin de mantener ese PR manejable, lo fusionaremos para dominar antes de que se finalicen todas las decisiones, como las cuales BitGenerator serán nominadas como predeterminadas.

Debemos tomar una decisión antes del primer lanzamiento con la nueva infraestructura. Una vez liberados, estaremos atascados con nuestra elección por un tiempo, por lo que debemos estar seguros de que estamos cómodos con nuestra decisión.

Por otra parte, la elección de la opción predeterminada no tiene que muchas consecuencias. No estamos hablando de las BitGenerator predeterminadas subyacentes a las funciones de conveniencia numpy.random.* . Según NEP 19, estos siguen siendo alias del legado RandomState , cuyo BitGenerator sigue siendo MT19937 . El único lugar donde aparece el valor predeterminado es cuando se crea una instancia de Generator() sin argumentos; es decir, cuando un usuario solicita un Generator con un estado arbitrario, presumiblemente para luego llamar al método .seed() en él. Esto probablemente sea bastante raro, ya que sería casi tan fácil instanciarlo explícitamente con el BitGenerator inicializado que realmente quieren. Una opción legítima aquí podría ser no nominar BitGenerator .

No obstante, tendremos recomendaciones sobre qué BitGenerator deben usar las personas la mayor parte del tiempo, y aunque podemos cambiar las recomendaciones con bastante libertad, el que tenga un lugar de honor probablemente se escribirá más en libros, blogs, tutoriales. , y tal.

En mi opinión, hay algunas opciones principales (con mi comentario, no dude en no estar de acuerdo; no he intentado transferir todos los comentarios relevantes del # 13163):

Ningún valor predeterminado

Siempre requiera Generator(ChosenBitGenerator(maybe_seed)) . Esto es un poco desagradable, pero como es una forma bastante conveniente de inicializar correctamente el generador para la reproducibilidad, la gente puede terminar haciendo esto de todos modos, incluso si tenemos un valor predeterminado.

MT19937

Esta sería una buena opción conservadora. Ciertamente, no es peor que el status quo. Como el Mersenne Twister todavía se considera ampliamente como la opción "estándar", podría ayudar a los usuarios académicos que necesitan que sus trabajos sean revisados ​​por personas que podrían cuestionar las opciones "no estándar", independientemente de las cualidades específicas del PRNG. "Nadie fue despedido por contratar a IBM". Las principales desventajas de MT19937 son principalmente que es más lento que algunas de las alternativas disponibles, debido a su estado muy grande, y que falla algunas pruebas de calidad estadística. Al elegir otro PRNG, tenemos una _oportunidad_ (pero no una _obligación_, en mi opinión) de opinar aquí y tratar de mover "el estándar", si lo deseamos.

PCG64

Este es probablemente el que usaré con más frecuencia, personalmente. La principal desventaja es que utiliza aritmética de enteros de 128 bits, que se emula en C si el compilador no proporciona dicho tipo de entero. Las dos plataformas principales para las que este es el caso son las CPU de 32 bits y el MSVC de 64 bits, que simplemente no admite enteros de 128 bits incluso cuando la CPU lo hace. Personalmente, no sugiero que dejemos que el rendimiento cada vez más raro de las CPU de 32 bits dicte nuestras elecciones. Pero el rendimiento de MSVC es importante, ya que nuestras compilaciones de Windows necesitan ese compilador y no otros compiladores de Windows. Probablemente se pueda abordar con algunos elementos intrínsecos de ensamblador / compilador, pero alguien tendría que escribirlos. El hecho de que sea _sólo_ MSVC por lo que tengamos que hacer esto hace que esto sea algo más aceptable que otras ocasiones en las que nos enfrentamos al ensamblaje.

Xoshiro256

Otra opción moderna para un PRNG pequeño y rápido. Tiene algunas peculiaridades estadísticas conocidas, pero es poco probable que sean un factor importante para la mayoría de los usos. Esas peculiaridades me hacen rehuirlo, pero esa es mi elección personal para el código que escribiré.

15 - Discussion numpy.random

Comentario más útil

Muy inspirado por este hilo, tengo algunas novedades que informar ...

Antecedentes

En muchas medidas, pcg64 es bastante bueno; por ejemplo, bajo las medidas habituales de calidad estadística, obtiene un certificado de buena salud. Ha sido probado de varias formas; más recientemente, lo he ejecutado hasta medio petabyte con PractRand. Funciona bien en casos de uso normales.

PERO, las patologías que surgieron en este hilo no me sentaron bien. Claro, podría decir " bueno, no lo sostengas así ", pero el objetivo de un PRNG de propósito general es que debe ser robusto. Quería hacerlo mejor ...

Entonces, hace unos 25 días comencé a pensar en diseñar un nuevo miembro de la familia PCG ...

Objetivo

Mi objetivo era diseñar un nuevo miembro de la familia PCG que pudiera reemplazar la variante actual pcg64 . Como tal:

  • La función de salida debería codificar los bits más que XSL RR (porque hacerlo evitará los problemas que surgieron en este hilo).
  • El rendimiento debería ser tan rápido (o más rápido) que el actual pcg64 .
  • El diseño debe ser similar al PCG (es decir, no ser trivialmente predecible y, por lo tanto, no permitir que _cualquier_ trabajo de la función de salida se deshaga fácilmente).

Como siempre, existe una compensación, ya que tratamos de obtener la mejor calidad posible lo más rápido posible. Si no nos importara en absoluto la velocidad, podríamos tener más pasos en la función de salida para producir una salida más codificada, pero el punto de PCG era que el LCG subyacente era "casi lo suficientemente bueno" y por eso no necesitábamos hacer tanto esfuerzo como lo haríamos con algo así como un contador que se incrementa en 1.

Revelación

¡Me complace informar el éxito! Hace unos 25 días, cuando estaba pensando por primera vez en esto, estaba de vacaciones. Cuando regresé hace unos diez días, probé las ideas que tenía y me complació comprobar que funcionaban bien. El tiempo posterior se ha dedicado principalmente a varios tipos de pruebas. Ayer estaba lo suficientemente satisfecho como para insertar el código en la versión C ++ de PCG. Las pruebas en tamaños pequeños indican que es mucho mejor que XSL RR y competitivo con RXS M, pero en realidad brilla en tamaños más grandes. También cumple con todos los demás objetivos.

Detalles

FWIW, la nueva función de salida es (para el caso de salida de 64 bits):

uint64_t output(__uint128_t internal)
{
    uint64_t hi = internal >> 64;
    uint64_t lo = internal;

    lo |= 1;
    hi ^= hi >> 32;
    hi *= 0xda942042e4dd58b5ULL;
    hi ^= hi >> 48;
    hi *= lo;
    return hi;
}

Esta función de salida está inspirada en xorshift-multiply, por la cual se usa ampliamente. La elección de los multiplicadores es (a) para mantener bajo el número de constantes mágicas, y (b) para evitar que la permutación se deshaga (si no tiene acceso a bits de orden inferior), y también proporcionar el "randomized- por sí misma ”que suelen tener las funciones de salida de PCG.

Otros cambios

También es el caso de que 0xda942042e4dd58b5 es el multiplicador LCG para este PRNG (y todos los generadores PCG de estado de 128 bits con prefijo cm_ ). En comparación con 0x2360ed051fc65da44385df649fccf645 utilizado por pcg64 , esta constante sigue siendo bastante buena en términos de propiedades de prueba espectral, pero es más barata de multiplicar porque 128 bits × 64 bits es más fácil que 128 bits × 128 bits. He usado esta constante LCG durante varios años sin problemas. Cuando utilizo la variante de multiplicador barato, ejecuto la función de salida en el estado pre-iterado en lugar del estado post-iterado para un mayor paralelismo a nivel de instrucción.

Pruebas

Lo he probado a fondo (PractRand y TestU01) y estoy contento con él. Las pruebas incluyeron escenarios descritos en este hilo (por ejemplo, tomar un grupo de generadores ya sea en vapor secuencial o avanzado en 2 ^ 64 e intercalar su salida - probé un grupo de cuatro y un grupo de 8192 a 8 TB sin problemas, también como un arroyo y su contraparte en tierra opuesta).

Velocidad

Podría continuar con las pruebas de velocidad y los puntos de referencia. Hay todo tipo de factores que influyen en si un PRNG se ejecuta más rápido que otro en un punto de referencia determinado, pero en general, esta variante parece ser un poco más rápida, a veces mucho más rápida y, en ocasiones, un poco más lenta. Factores como el compilador y la aplicación tienen un impacto mucho mayor en la variabilidad del índice de referencia.

Disponibilidad

Los usuarios del encabezado C ++ pueden acceder a este nuevo miembro de la familia _ahora_ como pcg_engines::cm_setseq_dxsm_128_64 ; en algún momento en el futuro, cambiaré pcg64 de pcg_engines::setseq_xsl_rr_128_64 a este nuevo esquema. Mi plan actual es hacerlo este verano como parte de un aumento de la versión de PCG 2.0.

Anuncios formales

En general, estoy muy contento con este nuevo miembro de la familia y en algún momento más adelante en el verano, habrá publicaciones de blog con más detalles, probablemente haciendo referencia a este hilo.

Tus opciones...

Por supuesto, tienes que averiguar qué hacer con esto. Independientemente de si lo usaría o no, en realidad tendría bastante curiosidad por ver si funciona mejor o peor en sus puntos de referencia de velocidad.

Todos 166 comentarios

¿Qué hace el compilador de Windows de Intel para enteros de 128 bits? ¿Cuánto más lento se compila PCG64 con MSVC en comparación con MT1993 en Windows? Sospecho que la función de salto adelante será ampliamente utilizada, por lo que sería bueno tenerla por defecto.

¿Qué hace el compilador de Windows de Intel para enteros de 128 bits?

No estoy del todo seguro; No sé si hay implicaciones de ABI por las que ICC se preocuparía por verse limitado. Si solo queremos tener una idea del ensamblado generado que podríamos usar, entonces este es un recurso útil: https://godbolt.org/z/kBntXH

Sospecho que la función de salto adelante será ampliamente utilizada, por lo que sería bueno tenerla por defecto.

¿Te refieres a corrientes configurables? Ese es un buen punto, pero me pregunto si no podría ser al revés. Si nuestra elección de valores predeterminados realmente importa mucho, entonces tal vez si elegimos uno de estos PRNG con más funciones, la gente usará esas funciones de manera más amplia en el código de la biblioteca sin documentar que requieren esas funciones "avanzadas", porque, después de todo, están disponibles "estándar". Pero luego, si otro usuario intenta usar esa biblioteca con un BitGenerator con menos funciones por velocidad u otras razones, entonces chocará contra una pared de ladrillos. En un mundo No default o MT19937 , es más probable que las bibliotecas piensen y documenten las funciones avanzadas que requieren.

En la mano apremiante, esa eventualidad haría que los BitGenerator s sin flujos configurables parezcan menos deseables, y me gusta la idea de avanzar en lo que se considera la mejor práctica en esa dirección (puramente personalmente; no siento una obligación de hacer que NumPy-the-project comparta esa noción). Podría ayudar a evitar algunos de los abusos que veo con personas .seed() ing en medio de su código. Pero nuevamente, todo eso se basa en la noción de que tener un incumplimiento cambiará significativamente el comportamiento de las personas, por lo que es probable que todas estas preocupaciones estén bastante atenuadas.

¿Cuánto más lento se compila PCG64 con MSVC en comparación con MT1993 en Windows?

En los puntos de referencia publicados por @bashtage en # 13163, PCG64 es casi la mitad de la velocidad de MT19937, que es un rendimiento bastante decepcionante de MSVC y amigos. Se compara con un 23% más rápido en Linux.

¿Qué hace el compilador de Windows de Intel para enteros de 128 bits?

Otros compiladores como Clang, GCC y el compilador Intel implementan enteros de 128 bits en sistemas de 64 bits de la misma manera que implementaron enteros de 64 bits en sistemas de 32 bits. Todas las mismas técnicas sin necesidad de nuevas ideas. Microsoft no se molestó en hacer eso para MSVC, por lo que el compilador no admite números enteros de 128 bits directamente.

Como resultado, para MSVC, la implementación existente de PCG64 en # 13163 implementa manualmente matemáticas de 128 bits llamando a intrínsecos de Microsoft como _umul128 en x86_64 (y presumiblemente también podría usar intrínsecos Intel equivalentes y más portátiles como _mulx_u64 en

Sospecho que la función de salto adelante será ampliamente utilizada, por lo que sería bueno tenerla por defecto.

Me alegra que te guste saltar adelante, pero tengo curiosidad por saber por qué crees que se usaría ampliamente. (Personalmente, me gusta mucho distance , que te dice qué tan separados están dos PRNG. Eso está en la versión C ++ de PCG, pero no en la C. Sin embargo, sería lo suficientemente trivial agregarlo si hubiera interesar.)

Me alegra que te guste saltar adelante, pero tengo curiosidad por saber por qué crees que se usaría ampliamente.

Probablemente no esté familiarizado con la terminología actual. Lo que quiero decir es corrientes independientes que se obtienen fácilmente que se pueden usar para ejecutar simulaciones en paralelo. No sé cuántos problemas de simulación se pueden paralelizar, pero sospecho que son muchos y, dada la cantidad de núcleos que la gente obtiene en un chip en estos días, eso podría compensar fácilmente una desventaja de velocidad.

Microsoft no se molestó en hacer eso para MSVC, por lo que el compilador no admite números enteros de 128 bits directamente.

Así que eso dañará nuestras ruedas, OTOH, muchas personas en Windows obtienen sus paquetes de Anaconda o Enthought, los cuales usan Intel, y las personas que realmente se preocupan por el rendimiento probablemente estén en Linux, Mac o tal vez AIX.

EDITAR: Y quizás si Microsoft está preocupado, podrían ofrecer una recompensa por solucionar el problema.

FWIW, aquí está el ensamblado que clang generaría para la función crítica, incluidos los bits necesarios para desempaquetar / reempaquetar uint128_t en la estructura de uint64_t s: https: // godbolt.org/z/Gtp3os

Muy bien, @rkern. ¿Alguna posibilidad de que pueda hacer lo mismo para ver qué está haciendo MSVC con el código escrito a mano de 128 bits?

Muy bien, @rkern. ¿Alguna posibilidad de que pueda hacer lo mismo para ver qué está haciendo MSVC con el código escrito a mano de 128 bits?

No es bonito. ~ https://godbolt.org/z/a5L5Gz~

Vaya, olvídate de agregar -O3 , pero sigue siendo feo: https://godbolt.org/z/yiQRhd

No es tan malo. No tenía la optimización activada, por lo que no incluyó nada. He añadido /Ox (¿tal vez haya una mejor opción?). También arreglé el código para usar el intrínseco de rotación incorporado ( _rotr64 ) ya que aparentemente MSVC es incapaz de detectar el modismo de rotación de C.

Sin embargo, sigue siendo una especie de choque de trenes. Pero creo que es justo decir que con un poco de atención, el código PCG64 podría modificarse para compilarse en MSVC en algo que no sea del todo vergonzoso para todos.

Para permitir que todo lo demás se combine, ¿por qué no elegir "sin valor predeterminado" por ahora? Eso nos deja libres para tomar una decisión sobre el valor predeterminado más adelante (incluso después de una o más versiones) sin romper la compatibilidad.

La mayoría de nuestros usuarios no son expertos en números aleatorios, deberíamos proporcionarles valores predeterminados.

Más allá del prosaico "ahora necesitan escribir más código", ¿qué pasa cuando cambiamos algo? En el caso de que BitGenerator esté codificado de forma rígida (porque no proporcionamos un valor predeterminado), cada usuario no sofisticado ahora tendrá que refactorizar su código y, con suerte, comprender los matices de su elección (tenga en cuenta que ni siquiera podemos estar de acuerdo entre nosotros sobre lo que es mejor). Sin embargo, si proporcionamos un valor predeterminado, podríamos romper ruidosamente sus pruebas porque la nueva versión predeterminada o nueva no es compatible con bit-stream.

Entre la suposición de que el flujo de bits siempre será constante frente a la suposición de que los desarrolladores de NumPy saben lo que están haciendo y los valores predeterminados deberían ser los mejores de la marca, me equivocaría del lado de la segunda suposición, incluso si rompe el primero.

Editar: aclare qué desarrolladores deben saber lo que están haciendo

La mayoría de nuestros usuarios no son expertos en números aleatorios, deberíamos proporcionarles valores predeterminados.

Bueno, ciertamente documentaremos recomendaciones, al menos, independientemente de si tenemos o no un valor predeterminado o cuál es el predeterminado.

Más allá del prosaico "ahora necesitan escribir más código", ¿qué pasa cuando cambiamos algo?

¿En qué "algo" estás pensando? No puedo seguir tu argumento.

Más allá del prosaico "ahora necesitan escribir más código", ¿qué pasa cuando cambiamos algo?

¿En qué "algo" estás pensando? No puedo seguir tu argumento.

@mattip se refiere a cambiar el generador de bits predeterminado.

Esto volvería locos a los usuarios que lo han adoptado y podrían requerir algún cambio de código.

Por ejemplo, si usaste

g = Generator()
g.bit_generator.seed(1234)

y se cambió el generador de bits subyacente, entonces esto estaría mal.

Si hicieras lo más sensato y usaras

Generator(BitGenerator(1234))

entonces no lo verías.

En mi opinión, al considerar la elección del valor predeterminado, deberíamos pensar que está arreglado hasta que se encuentre una falla fatal en el generador de bits subyacente o Intel agregue un QUANTUM_NI a sus chips, lo que produce muchas mejoras de OOM en el rendimiento aleatorio.

Me doy cuenta de que soy un poco extraño aquí, pero no creo que sea razonable esperar que el PRNG que es la opción predeterminada se arregle para siempre y nunca cambie. (En C ++, por ejemplo, std::default_random_engine queda a discreción de la implementación y puede cambiar de una versión a otra).

Más bien, debe haber un mecanismo para reproducir resultados anteriores. Por lo tanto, una vez que existe una implementación en particular, no es muy bueno cambiarla (por ejemplo, el MT19937 _is_ MT19937, no puede modificarlo para que dé una salida diferente). [Y tampoco es interesante eliminar una implementación que ya existe].

Cuando cambie el valor predeterminado, las personas que quieran seguir reproduciendo resultados antiguos deberán solicitar el valor predeterminado anterior por nombre. (Puede hacerlo proporcionando un mecanismo para seleccionar el valor predeterminado correspondiente a una versión anterior).

Dicho esto, incluso si se le permite cambiar el generador predeterminado por otra cosa, realmente debe ser estrictamente mejor: cualquier característica presente en el generador predeterminado representa un compromiso para admitir esa característica en el futuro. Si su generador predeterminado tiene un advance eficiente, no puede eliminarlo más tarde. (Podría bloquear la funcionalidad avanzada en el generador predeterminado para evitar este problema).

En resumen, hay formas de asegurarse de que los usos puedan tener resultados reproducibles sin intentar encerrarse en un contrato donde el valor predeterminado no se modifica para siempre. También reducirá las apuestas por la elección que haga.

(FWIW, esto es lo que hice en PCG. El PRNG predeterminado de PCG de 32 bits es actualmente la variante XSH-RR [se accede como pcg_setseq_64_xsh_rr_32_random_r en la biblioteca C y la clase pcg_engines::setseq_xsh_rr_64_32 en C ++ library], pero en principio si realmente desea una reproducibilidad a prueba de futuro, debe especificar XSH-RR explícitamente, en lugar de usar pcg32_random_r o pcg32 que son alias y, en principio, pueden actualizarse a otra cosa. .)

Realmente no es para siempre (todo este proyecto está impulsado en un 90% por una promesa real, genuina y honrada para siempre hecha hace unos 14 años), pero como usted dice, el cambio necesita (a) una razón convincente para cambiar y (b) tomar al menos unos años dar el ciclo de depreciación.

Es mucho mejor esforzarse hoy para hacerlo lo más cerca posible.

Una cosa que no está prohibida, por supuesto, es mejorar el código PRNG después del lanzamiento, ya que produce los mismos valores. Por ejemplo, si optamos por un PRNG que usara uint128, podríamos permitir que MS agregue soporte para uint128 (gran posibilidad) o agregue ensamblado para Win64 en una versión futura.

Por ejemplo, si usaste

g = Generator()
g.bit_generator.seed(1234)

y se cambió el generador de bits subyacente, entonces esto estaría mal.

Correcto, eso parece estar discutiendo, con @ eric-wieser, por la opción "No predeterminado", que no puedo cuadrar con la declaración inicial "La mayoría de nuestros usuarios no son expertos en números aleatorios, deberíamos proporcionar valores predeterminados para ellos . "

Entre ningún valor predeterminado y un amigable, asumiendo completamente el valor predeterminado, siempre elegiría lo último:

Ahora:

Generator() # OK
Generator(DefaultBitGenerator(seed)) # OK
Generator(seed) # error

_mi preferencia:

Generator(1234) == Generator(DefaultBitGenerator(1234)
Generator(*args**kwargs) == Generator(DefaultBitGenerator(*args, **kwargs))

Ahora no creo que esto vaya a entrar, pero creo que una forma de prolongar el uso de RandomState es hacer que esto solo esté disponible para los usuarios que se sientan lo suficientemente expertos como para elegir un generador de bits.

En resumen, hay formas de asegurarse de que los usos puedan tener resultados reproducibles sin intentar encerrarse en un contrato donde el valor predeterminado no se modifica para siempre. También reducirá las apuestas por la elección que haga.

Sí, tenemos eso. Los usuarios pueden tomar BitGenerator s por nombre (por ejemplo, MT19937 , PCG64 , etc.) y crear una instancia de ellos con semillas. BitGenerator objetos [0..1) float64 sy enteros (así como cualquier capacidad divertida de jumpahead / stream que tengan) . La clase Generator la que estamos hablando toma un objeto BitGenerator y lo envuelve para proporcionar todas las distribuciones no uniformes, los gaussianos, los gammas, los binomios, etc. Tenemos estrictas garantías de compatibilidad de transmisión para los BitGenerator s. No nos desharemos de ninguno (que llegue al lanzamiento), ni los cambiaremos.

La pregunta central sobre el valor predeterminado es "¿Qué hace el código g = Generator() , sin argumentos?" En este momento, en el PR, crea un Xoshiro256 BitGenerator con un estado arbitrario (es decir, extraído de una buena fuente de entropía como /dev/urandom ). La opción "No predeterminado" sería convertirlo en un error; los usuarios tendrían que nombrar explícitamente el BitGenerator que quieran. El punto de @ eric-wieser es que "No predeterminado" es una opción categóricamente _safe_ para la primera versión. Una versión posterior que proporcione un valor predeterminado no causará problemas de la misma manera que lo hace el cambio de un valor predeterminado existente.

@rkern , si solo le importa el caso sin argumentos en el que la semilla se genera automáticamente a partir de la entropía disponible, entonces realmente no importa mucho cuál sea el generador subyacente; podría cambiar cada hora, ya que los resultados nunca serían reproducibles ( diferentes carreras obtendrían diferentes semillas).

Por el contrario, @bashtage parece preocuparse por un generador predeterminado que se proporciona con una semilla.

@rkern , si solo le importa el caso _sin argumentos_ donde la semilla se genera automáticamente a partir de la entropía disponible, entonces realmente no importa mucho cuál es el generador subyacente; podría cambiar cada hora ya que los resultados nunca serían reproducibles ( diferentes carreras obtendrían diferentes semillas).

Puede reiniciar el BitGenerator vez creado. Entonces, si Generator() funciona, lo que espero que suceda es que las personas que quieran un PRNG sembrado simplemente lo sembrarán en la siguiente línea, como en el ejemplo de

g = Generator()
g.bit_generator.seed(seed)

Eso es algo tedioso, por lo que sugerí en la parte superior que tal vez la mayoría de las personas generalmente optarían por Generator(PCG64(<seed>)) todos modos, ya que es igual de conveniente para escribir. Sin embargo, @bashtage observa correctamente cierta resistencia cuando se enfrenta a tomar una decisión adicional.

Así que supongo que _también_ tenemos una pregunta más amplia frente a nosotros: "¿Cuáles son todas las formas en que queremos que los usuarios creen una de estas? Y si esas formas tienen configuraciones predeterminadas, ¿cuáles deberían ser esas configuraciones predeterminadas?" Tenemos un espacio de diseño abierto y la sugerencia de @bashtage de Generator(<seed>) o Generator(DefaultBitGenerator(<seed>)) todavía son posibilidades.

@bashtage ¿Cuánto crees que ayudaría la documentación? Es decir, si dijimos en la parte superior " PCG64 es nuestro valor predeterminado preferido BitGenerator " y usamos Generator(PCG64(seed)) consistentemente en todos los ejemplos (cuando no se muestran específicamente otros algoritmos)?

Podría estar más convencido de tener una función default_generator(<seed>) _función_ sobre Generator(<seed>) o g=Generator();g.seed(<seed>) . Entonces, si realmente necesitáramos cambiarlo y no quisiéramos romper cosas, podríamos simplemente agregar una nueva función y agregar advertencias a la anterior. Podría recomendar marcarlo experimental para la primera versión, dándonos algo de tiempo para observar esta infraestructura en la naturaleza antes de hacer un compromiso firme.

¿Qué hay de hacer un objeto DefaultBitGenerator que no exponga ningún detalle de su estado interno? Este sería un proxy para uno de los otros objetos generadores de bits, pero en principio podría envolver cualquiera de ellos, excepto, por supuesto, su secuencia específica de números generados. Es de esperar que esto desanime a los usuarios de hacer suposiciones programáticas sobre lo que pueden hacer con el BitGenerator predeterminado, al tiempo que nos permite seguir usando un algoritmo mejorado.

Estoy de acuerdo con @bashtage en que sería mucho más amigable admitir directamente semillas enteras como argumentos para Generator , por ejemplo, np.random.Generator(1234) . Esto, por supuesto, haría uso de DefaultBitGenerator .

En la documentación de Generator , podríamos dar un historial completo de cuál era el generador de bits predeterminado en cada versión anterior de NumPy. Esta es básicamente la sugerencia de @imneme , y creo que sería suficiente para propósitos de reproducibilidad.

(Acabo de ver esta edición de un comentario anterior)

Vaya, olvídate de agregar -O3 , pero sigue siendo feo: https://godbolt.org/z/yiQRhd

Para MSVC, no es -O3 , es /O2 o /Ox (¡pero no /O3 !).

En la documentación de Generator , podríamos dar un historial completo de cuál era el generador de bits predeterminado en cada versión anterior de NumPy. Esta es básicamente la sugerencia de @imneme , y creo que sería suficiente para propósitos de reproducibilidad.

En realidad, sería incluso mejor incluir un argumento explícito version , como el argumento protocol pickle , en Generator / DefaultBitGenerator . Luego, podría escribir algo como np.random.Generator(123, version=1) para indicar que desea números aleatorios de la "versión 1" (lo que sea) o np.random.Generator(123, version=np.random.HIGHEST_VERSION) (comportamiento predeterminado) para indicar que desea el último / mejor generador de bits (sea lo que sea).

Presumiblemente version=0 sería el MT19937 que NumPy ha usado hasta ahora, y version=1 podría ser cualquier nuevo valor predeterminado que escojamos.

¿Qué hay de hacer un objeto DefaultBitGenerator que no exponga ningún detalle de su estado interno? Este sería un proxy para uno de los otros objetos generadores de bits, pero en principio podría envolver cualquiera de ellos, excepto, por supuesto, su secuencia específica de números generados. Es de esperar que esto desanime a los usuarios de hacer suposiciones programáticas sobre lo que pueden hacer con el BitGenerator predeterminado, al tiempo que nos permite seguir usando un algoritmo mejorado.

Hmmm. Eso es atractivo. Se siente como si estuviera complicando demasiado las cosas y agregando otro bucle a este nudo gordiano (y que debería haber un trazo más al estilo de Alexander disponible para nosotros), pero eso es realmente lo único malo que tengo que decir al respecto. Facilita las decisiones restantes: podemos centrarnos en la calidad y el rendimiento estadísticos.

En realidad, sería incluso mejor incluir un argumento explícito version , como pickle , en Generator / DefaultBitGenerator .

Soy menos fan de esto. A diferencia del caso pickle , estas cosas tienen nombres significativos que podemos usar y ya tenemos el mecanismo implementado.

Soy menos fan de esto. A diferencia del caso pickle , estas cosas tienen nombres significativos que podemos usar y ya tenemos el mecanismo implementado.

Considere lo siguiente desde la perspectiva de un usuario típico de NumPy:

  • np.random.Generator(seed, version=0) frente a np.random.Generator(seed, version=1)
  • np.random.Generator(MT19937(seed)) frente a np.random.Generator(PCG64(seed))

Creo que es seguro asumir que la mayoría de nuestros usuarios saben muy poco sobre los méritos relativos de los algoritmos RNG. Pero incluso sin leer ningún documento, pueden adivinar con seguridad que version=1 (el valor predeterminado más reciente) debe ser mejor en la mayoría de los casos que version=0 . Para la mayoría de los usuarios, eso es todo lo que necesitan saber.

Por el contrario, nombres como MT19937 y PCG64 son realmente solo significativos para expertos o personas que ya han leído nuestra documentación :).

En su caso de uso, nadie está seleccionando el version que _quieren_. Solo seleccionan los version que _necesitan_ para replicar los resultados de una versión conocida. Siempre están buscando un valor específico que se usó (implícitamente, porque permitimos que fuera implícito) en los resultados que quieren replicar; no necesitan razonar sobre la relación entre varios valores.

Y, en cualquier caso, ese nivel de reproducibilidad de liberación cruzada es algo que rechazamos en NEP 19 . Los argumentos en contra de la versión de las distribuciones se aplican igualmente aquí.

Algunas reflexiones sobre el valor predeterminado:

  • Al 99,9% de los usuarios no les importará o querrá saber acerca de los algoritmos subyacentes, solo quieren números aleatorios. Así que +1 por tomar una decisión obstinada por defecto, no haga que los usuarios elijan.
  • dSFMT parece ser simplemente una versión más rápida que MT19937 (sería bueno indicar en los documentos qué tan rápido y eliminar "SSE2"). Dado que de todos modos no garantizamos la reproducibilidad del flujo de bits, las diferencias de estado interno no son muy interesantes y dSFTM debería preferirse a MT19937 incluso si el argumento ganador aquí es "hacer la vida más fácil durante la revisión del artículo" .
  • El rendimiento es importante para una fracción significativa de la base de usuarios. Las propiedades estadísticas de los generadores solo son importantes para una fracción muy pequeña de usuarios. Todos los generadores incluidos están bien para casos de uso normales. Entonces +1 para elegir el más rápido por defecto.

Lamento decirlo, pero 32 bits todavía son importantes en Windows; consulte https://github.com/pypa/manylinux/issues/118#issuecomment -481404761

Creo que deberíamos preocuparnos mucho por las propiedades estadísticas, porque estamos en el proceso de un gran cambio hacia un mayor uso de métodos de remuestreo en el análisis de datos. Si Python tiene la reputación de ser un poco descuidado en este asunto, incluso si es solo por defecto, eso podría ser una barrera para que las personas que estén considerando Python para el análisis de datos lo adopten. Me complacería mucho que Python fuera el paquete elegido por las personas que se toman en serio la permutación y la simulación.

Creo que está bien ofrecer algoritmos más rápidos que no sean de última generación, pero no de forma predeterminada, en la medida en que podamos evitarlo y mantener la compatibilidad con versiones anteriores.

Para algunos análisis forenses y discusión, consulte: https://arxiv.org/pdf/1810.10985.pdf

Los libros de texto dan métodos que suponen implícita o explícitamente que los PRNG pueden sustituirse por verdaderas variables IIDU [0,1) sin introducir errores materiales [20, 7, 2, 16, 15]. Mostramos aquí que esta suposición es incorrecta para los algoritmos en muchos paquetes estadísticos de uso común, incluidos MATLAB, el módulo aleatorio de Python, R, SPSS y Stata.

@kellieotto , @pbstark - ¿Tienen todos una opinión sobre qué PRNG deberíamos elegir aquí, para dar la mejor base posible para la permutación y el arranque?

Creo que deberíamos preocuparnos mucho por las propiedades estadísticas, porque estamos en el proceso de un gran cambio hacia un mayor uso de métodos de remuestreo en el análisis de datos.

Convenido. Siempre que esas propiedades sean relevantes para algunos casos de uso del mundo real, eso es muy importante. Las preocupaciones que suelen plantearse son siempre extremadamente académicas.

Para algunos análisis forenses y discusión, consulte: https://arxiv.org/pdf/1810.10985.pdf

Articulo muy interesante. Concluye que NumPy es la única biblioteca que lo hace bien (parte superior de la página 9), a diferencia de R, Python stdlib & co.

Sería muy útil obtener ejemplos aún más concretos que en el documento. Si nuestro generador predeterminado actual también se descompone en algún momento, ¿cuándo es eso? Ejemplos como la función sample R que genera un 40% de números pares y un 60% de números impares al extraer ~ 1.700 millones de muestras. ¿Cuál es el equivalente de bootstrapping / remuestreo aquí?

La última versión de R (3.6) corrige el enfoque de truncamiento frente a bits aleatorios
para generar enteros aleatorios. El Mersenne Twister sigue siendo el predeterminado
PRNG, sin embargo.

@Kellie Ottoboni [email protected] y creo que el PRNG predeterminado en
Los lenguajes científicos y los paquetes de estadísticas deben ser criptográficamente
seguro (un CS-PRNG, por ejemplo, SHA256 en modo contador), con la opción de caer
volver a algo más rápido pero de menor calidad (por ejemplo, el Mersenne Twister)
si la velocidad lo requiere.

Hemos estado trabajando en un CS-PRNG para Python:
https://github.com/statlab/cryptorandom

El rendimiento no es excelente (todavía). El cuello de botella parece ser la conversión de tipos
dentro de Python para convertir cadenas binarias (salida hash) como enteros. Fueron
trabajando en una implementación que mueva más trabajo a C.

Salud,
Felipe

El lunes 27 de mayo de 2019 a las 6:27 a.m. Ralf Gommers [email protected]
escribió:

Creo que deberíamos preocuparnos mucho por las propiedades estadísticas, porque estamos
en el proceso de un gran cambio hacia un mayor uso de métodos de remuestreo en
análisis de los datos

Convenido. Siempre que esas propiedades sean relevantes para algún uso en el mundo real
casos, eso es muy importante. Las preocupaciones que suelen plantearse son
siempre extremadamente académico.

Para algo de análisis forense y análisis, consulte:
https://arxiv.org/pdf/1810.10985.pdf

Articulo muy interesante. Concluye que NumPy es el único
biblioteca que lo hace bien (parte superior de la página 9), a diferencia de R, Python stdlib & co.

Sería muy útil obtener ejemplos aún más concretos que en el
papel. Si nuestro generador predeterminado actual también falla en algún momento,
¿cuando es eso? Ejemplos como la función de muestra de R que genera un 40% incluso
números y 60% números impares al extraer ~ 1.7 mil millones de muestras. Cual es el
bootstrapping / remuestreo equivalente aquí?

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWJEIA4CTLLHVGZVKBLPXPOUFA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG63JKTMVNX49 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWJW445QDPGZDGXMPA3PXPOUFANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

El rendimiento es importante para una fracción significativa de la base de usuarios. Las propiedades estadísticas de los generadores solo son importantes para una fracción muy pequeña de usuarios. Todos los generadores incluidos están bien para casos de uso normales. Entonces +1 para elegir el más rápido por defecto

Primero, simplemente no hay forma de elegir "el más rápido". @bashtage ejecutó algunos puntos de

También me pregunto qué tan grande es la “fracción significativa” de usuarios que se preocupan por la velocidad. Python, en promedio, es aproximadamente 50 veces más lento que C. ¿Qué fracción de la base de usuarios de NumPy lo está ejecutando en PyPy (lo que daría un aumento de velocidad de 4 ×)? Algunos, ciertamente, pero sospecho que no es un número muy alto.

Y para esa “fracción significativa” a la que le importa la velocidad, dada toda la variabilidad descrita anteriormente, ¿quién va a creer en su palabra de que el PRNG predeterminado se ejecutará más rápido para su aplicación? Algo sensato (que también es bastante divertido y al alcance de la mayoría de los usuarios) es comparar los diferentes PRNG disponibles y ver cuál es el más rápido _ para ellos_.

Por el contrario, aunque pueden encontrar pistas en la documentación, averiguar la calidad estadística de los PRNG particulares no está, como puede observar, en el radar de la mayoría de los usuarios (y es un desafío incluso para los expertos). La mayoría ni siquiera sabrá cuándo o si debería importarles o no. Yo diría que este es un lugar para cierto paternalismo: el hecho de que a la mayoría de los usuarios no les importe algo no significa que los mantenedores no deberían preocuparse por eso.

Es cierto que todos los PRNG incluidos están bien para la mayoría de los casos de uso, pero esa es una barra bastante baja. Los sistemas Unix se han enviado con una mezcla heterogénea de PRNG de biblioteca C que son estadísticamente terribles y, sin embargo, se han utilizado ampliamente durante años sin que el mundo se salga de su eje.

Más allá de las propiedades estadísticas, hay otras propiedades que el usuario puede que no sepa que desea para sí mismo, pero que yo podría querer para ellas. Personalmente, como proveedor de PRNG, quiero evitar la previsibilidad trivial: no quiero que alguien mire algunos resultados del PRNG y luego pueda decir cuáles serán todos los resultados futuros. En la mayoría de los contextos donde se usa NumPy, la previsibilidad no es un problema: no hay adversario que se beneficie de poder predecir fácilmente la secuencia. Pero alguien en algún lugar va a usar los PRNG de NumPy no porque necesiten a NumPy para hacer estadísticas, sino porque ahí es donde han encontrado PRNG antes; ese código puede enfrentarse a un adversario real que se beneficiará de poder predecir el PRNG. No vale la pena pagar mucho (por ejemplo, una pérdida significativa de velocidad) para asegurarse sólidamente contra esta situación atípica, pero un seguro modesto podría valer la pena.

Para algunos análisis forenses y discusión, consulte: https://arxiv.org/pdf/1810.10985.pdf

Los libros de texto dan métodos que suponen implícita o explícitamente que los PRNG pueden sustituirse por verdaderas variables IIDU [0,1) sin introducir errores materiales [20, 7, 2, 16, 15]. Mostramos aquí que esta suposición es incorrecta para los algoritmos en muchos paquetes estadísticos de uso común, incluidos MATLAB, el módulo aleatorio de Python, R, SPSS y Stata.

FWIW, hay un buen artículo de @lemire sobre en mi propio artículo . (Al generar 64 bits, el método de Lemire usa la multiplicación de 128 bits para evitar la división lenta de 64 bits, con todos los problemas familiares que pueden surgir para los usuarios de MSVC).

@pbstark @kellieotto Leí su artículo con interés cuando apareció en arXiv. Estaba visitando a unos amigos en BIDS y habían mencionado su trabajo. La sección de Discusión señala que "hasta ahora, no hemos encontrado una estadística con un sesgo consistente lo suficientemente grande como para ser detectado en O (10 ^ 5) replicaciones" para MT19937. ¿Has encontrado uno todavía? ¿Ha encontrado un ejemplo concreto para un PRNG de estado de 128 bits como PCG64? Me parece que ese es un umbral razonable de relevancia práctica, donde esta consideración podría comenzar a pesar más que otras (OMI), al menos con el propósito de seleccionar un valor predeterminado de propósito general.

La buena característica de nuestro nuevo marco PRNG # 13163 es que permite que cualquiera proporcione su propio BitGenerator que puede simplemente conectarse. Ni siquiera tiene que estar en numpy para que la gente lo use en su numpy código. Te animo a que consideres implementar cryptorandom como BitGenerator en C para que podamos compararlo cara a cara con las otras opciones.

Personalmente, espero que aquellos que realmente se preocupan por la velocidad hagan un esfuerzo adicional si es necesario (no es mucho aquí). Deberíamos proporcionar valores predeterminados seguros , y mi mejor suposición actual es que esto significa valores predeterminados seguros para todos los propósitos, con la excepción de la criptografía (probablemente deberíamos tener una advertencia sobre eso en los documentos). Muchos usuarios se preocupan por la velocidad, pero, francamente, esa es exactamente la razón por la que evito darle una prioridad demasiado alta.
Ese artículo en el que Numpy lo hizo bien parece interesante (¡felicitaciones a Robert probablemente por hacerlo bien!), Pero en realidad es el muestreador, no el generador de bits.

@pbstark, ¿ tal vez le gustaría implementar esto como un BitGenerator compatible con numpy / randomgen? Es probable que sea la forma más fácil de acelerarlo y ponerlo a disposición de una amplia audiencia de una forma mucho más útil. Como parece que tú y Kellie Ottoboni están en Berkeley, ¿podríamos reunirnos algún tiempo para empezar? (Solo una oferta, primero debería echar un vistazo más de cerca al código).

Con respecto al papel _Muestreo aleatorio: la práctica hace imperfecto_, es una buena lectura, pero vale la pena recordar que si tuviéramos 1 billón de núcleos produciendo un número por nanosegundo durante 100 años, generaríamos menos de 2 ^ 102 números.

Para los PRNG trivialmente predecibles (incluso aquellos con grandes espacios de estado como el Mersenne Twister), podemos saber si alguna vez se puede producir alguna secuencia de salida específica (y encontrar la semilla que la generaría si existe o sentir nostalgia si no es así) ), pero para otros PRNG no trivialmente predecibles, no podemos (fácilmente) saber qué secuencias de salida nunca se pueden producir y cuáles están allí, pero son lo suficientemente raras como para que sea muy poco probable encontrarlas en un eón de búsqueda. (Como sabrá, tengo un PRNG que sé que escupirá un archivo zip con _Twelth Night_ dentro de 2 ^ 80 salidas, pero buena suerte para encontrarlo).

Si realmente desea un cryptoprng, la única opción en el hardware moderno es
AES ya que tiene una instrucción dedicada. @lemire tiene una implementación
aquí https://github.com/lemire/testingRNG/blob/master/source/aesctr.h que
es tan rápido como los generadores no criptográficos. También hay ChaCha20 que puede ir
rápido con SIMD. Sin embargo, ambos serán lentos en hardware antiguo. ThreeFry y
Philox ya está incluido y es un contador de criptolitas.

La criptografía de la OMI está sobrevalorada en términos de beneficios de costos. No estoy al tanto de ninguna
retracción importante debido a problemas de PRNG con Mt, que creo que ha sido
utilizado en el orden de 10e6 artículos publicados. Las únicas aplicaciones que he visto
donde el PRNG fue realmente problemático fueron los casos en que el período fue tan
pequeño que el generador completó el ciclo completo. Incluso aquí el único
efecto fue la reducción del tamaño de la muestra del estudio, que replicó los principales
resultados una vez que se vuelven a ejecutar en un sistema con un período más largo.

El lunes 27 de mayo de 2019 a las 19:50, Robert Kern [email protected] escribió:

@pbstark https://github.com/pbstark @kellieotto
https://github.com/kellieotto Leí su artículo con interés cuando
apareció en arXiv. Estaba visitando a unos amigos en BIDS, y ellos
mencionó su trabajo. La sección Discusión señala que "hasta ahora, no hemos
encontró una estadística con un sesgo consistente lo suficientemente grande como para ser detectado en
O (10 ^ 5) replications "para MT19937. ¿Ya has encontrado una? ¿Has encontrado una
¿Un ejemplo concreto de un PRNG de estado de 128 bits como PCG64? Eso me parece
ser un umbral razonable de relevancia práctica, donde esta consideración
podría comenzar a superar a otros (OMI), al menos con el propósito de seleccionar
un valor predeterminado de propósito general.

La buena característica de nuestro nuevo marco PRNG # 13163
https://github.com/numpy/numpy/pull/13163 es que permite que cualquiera
proporcionan su propio BitGenerator que puede simplemente conectarse. No
incluso tiene que estar en numpy para que la gente lo use en su código numpy. me gustaría
animarle a considerar la implementación de criptorandom como BitGenerator en C
para que podamos compararlo cara a cara con las otras opciones.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRO5PKW4MRFSBGUFUNTPXQUOLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMDVNWCOMA
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABKTSRMRIHC4OYDR52HLTHDPXQUOLANCNFSM4HPX3CHA
.

También me pregunto qué tan grande es la “fracción significativa” de usuarios que se preocupan por la velocidad. El promedio de Python es aproximadamente 50 veces más lento que C. ¿Qué fracción de la base de usuarios de NumPy lo ejecuta en PyPy (lo que daría un aumento de velocidad de 4 veces)? Algunos, ciertamente, pero sospecho que no es un número muy alto.

Sospecho que no eres un usuario habitual :) NumPy es principalmente C bajo el capó, y es tan rápido como hacer lo tuyo en C (bueno, más rápido en su mayoría). Además, PyPy no está listo para producción para aplicaciones científicas y, en cualquier caso, es más lento (porque está limitado a usar la API CPython que usa NumPy, por lo que no puede obtener los beneficios de su JIT).

De cualquier manera, esto está fuera de tema. Afirmar que la velocidad importa no es controvertido.

@imneme estamos usando el método de lemires para enteros acotados. Dado que este
nuevo comienzo sin legado ni depreciación con el que nos hemos esforzado al principio
buenos algoritmos.

El lunes 27 de mayo de 2019 a las 19:46, imneme [email protected] escribió:

Para algo de análisis forense y análisis, consulte:
https://arxiv.org/pdf/1810.10985.pdf

Los libros de texto dan métodos que suponen implícita o explícitamente que los PRNG pueden
sustituirse por verdaderas variables IIDU [0,1) sin introducir material
error [20, 7, 2, 16, 15]. Mostramos aquí que esta suposición es incorrecta para
algoritmos en muchos paquetes estadísticos de uso común, incluido MATLAB,
Módulo aleatorio de Python, R, SPSS y Stata.

FWIW, hay un buen artículo https://arxiv.org/abs/1805.10941 por @lemire
https://github.com/lemire sobre cómo generar de manera eficiente un número en un rango
sin prejuicios. Lo usé como punto de partida para explorar y ejecutar algunos
puntos de referencia también en mi propio artículo
http://www.pcg-random.org/posts/bounded-rands.html . (Al generar
64 bits, el método de Lemire utiliza la multiplicación de 128 bits para evitar
División de 64 bits, con todos los problemas familiares que podrían surgir para MSVC
usuarios.)

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKNAQAK4WIXG5SVLO3PXQUA3A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXH63LSMVBW5 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABKTSRKV3KYKRLNMBKNU4JLPXQUA3ANCNFSM4HPX3CHA
.

Deberíamos proporcionar valores predeterminados _seguros_, y mi mejor suposición actual es que esto significa valores predeterminados seguros para todos los propósitos con la excepción de la criptografía

Eso es difícil de discutir. Mi pregunta es: ¿qué es seguro? Solo hay diversos grados de cuasialeatorio con varias propiedades. Hasta ahora no he visto a nadie dar un ejemplo concreto, ni aquí ni en otros temas, PRs o hilos. No es útil hablar simplemente de propiedades estadísticas abstractas.

Mi sensación es que PCG64 sería un buen valor predeterminado. La desventaja de la velocidad en Windows no será evidente para las personas que usan Anaconda et. al., y es probable que se solucione en algún momento. Dado que la ejecución paralela es lo nuevo en Python, también creo que tener flujos configurables es una propiedad deseable.

Soy muy escéptico de que la penalización de velocidad de PCG64 bajo Visual Studio sea algo que no se pueda borrar.

¿Fue esto cuidadosamente evaluado en alguna parte?

Afirmar que la velocidad importa no es controvertido.

Mi pregunta es: ¿qué es seguro?

Aplicar la lógica de forma coherente: "¿qué es rápido"? No tengo una gran idea de qué numerosos programas tienen realmente el rendimiento de BitGenerator como un cuello de botella significativo. Si utilizo un BitGenerator que es dos veces más rápido, ¿obtendré una aceleración del 5% en mi cálculo completo? Probablemente ni siquiera eso. Python-no-ser-tan-rápido-como-C no es el problema; es solo que incluso los programas con mucho PRNG que son realmente útiles no gastan una gran cantidad de tiempo en BitGenerator . Probablemente cualquiera de las opciones disponibles sea suficiente.

Soy muy escéptico de que la penalización de velocidad de PCG64 bajo Visual Studio sea algo que no se pueda borrar.

Up-thread, muestro cómo clang compila PCG64 en un ensamblaje que podemos robar para MSVC de 64 bits, así que no, no creo que MSVC en Windows de 64 bits sea un problema insuperable.

Lo que puede ser más complicado es PCG64 en sistemas de 32 bits, de los cuales solo Windows de 32 bits puede ser prácticamente importante para nosotros. En ese caso, se trata menos de MSVC que de limitarnos al ISA de 32 bits.

Lo que @Kellie Ottoboni [email protected] y yo
incluso para problemas de tamaño modesto, el espacio de estado de MT es demasiado pequeño para aproximarse
permutaciones uniformes (n <2100) o muestras aleatorias uniformes (n = 4e8, k = 1000).

Eso afecta a todo, desde el bootstrap hasta las pruebas de permutación y MCMC.
La diferencia entre la distribución prevista y la real
La distribución puede ser arbitrariamente grande (la distancia de variación total se aproxima
2). Es grande y es serio.

No hemos hecho ningún esfuerzo para romper la MT en funciones "estadísticas" en un
Par de años. Estoy bastante seguro de que hay una forma sistemática de romperlo.
(dado que las distancias de distribución son tan grandes).

Salud,
Felipe

El lunes 27 de mayo de 2019 a las 12:26 p.m. Robert Kern [email protected]
escribió:

Afirmar que la velocidad importa no es controvertido.

Mi pregunta es: ¿qué es seguro?

Aplicar la lógica de forma coherente: "¿qué es rápido"? No tengo una gran idea
qué numerosos programas tienen realmente el rendimiento de BitGenerator como
un cuello de botella importante. Si uso un BitGenerator que es dos veces más rápido,
¿Obtendré una aceleración del 5% en mi cálculo completo? Probablemente ni siquiera eso.
Python-no-ser-tan-rápido-como-C no es el problema; es solo que incluso
Los programas con mucho PRNG que son realmente útiles no gastan una gran cantidad de
tiempo en BitGenerator. Probablemente alguna de las opciones disponibles sea
suficiente.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWKSMPAG3GFUCUFRXCDPXQYVRA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43KNTMVNW5H4DFVREXG43VMDVNW5
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWIDCPAJJ6DJ3RO332LPXQYVRANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@pbstark Lo que me gustaría ver es una implementación concreta del problema (podría ser artificial, pero no demasiado artificial) en el que MT o un PRNG de 128 bits falla y cryptorandom funcionaría. ¿Puede señalar un conjunto de datos donde el método de remuestreo da inferencias incorrectas con un PRNG de 128 bits y correctas inferencias con cryptorandom ?

Pasar a PCG64 empeora el límite inferior del tamaño del problema,
ya que su espacio de estado es incluso más pequeño que el de MT. Por supuesto que podría
todavía produce una "mejor" aleatoriedad en el sentido de que podría muestrear un subgrupo de
grupo de permutación de manera más uniforme que MT. Pero tiene que romperse antes
500 elige 10, ¡y antes de 21 !.

Salud,
Felipe

El lunes 27 de mayo de 2019 a las 12:30 p.m. Robert Kern [email protected]
escribió:

Soy muy escéptico de que la penalización de velocidad de PCG64 en Visual Studio sea
algo que no se puede borrar.

Up-thread muestro cómo clang compila PCG64 en un ensamblaje que podemos robar
para MSVC de 64 bits, así que no, no creo que MSVC en Windows de 64 bits sea un
Problema insuperable.

Lo que puede ser más complicado es PCG64 en sistemas de 32 bits, de los cuales solo 32 bits
Windows puede seguir siendo prácticamente importante para nosotros. En ese caso es menos
sobre MSVC que sobre restringirnos al ISA de 32 bits.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWJFCINQCYGFCI7ULI3PXQZGLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXWHG43WNMV6 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWK6QTB65Z4TJU76XKTPXQZGLANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

No sé lo suficiente sobre los PRNG para sopesar realmente en cualquier caso, solo quiero que el enfoque esté en las propiedades estadísticas primero (si la respuesta es que todas son muy, muy buenas, bien). Una cosa que me pregunto ahora es la equidistribución k-dimensional. ¿Usamos actualmente variantes de, por ejemplo, PCG que funcionan bien aquí en comparación con MT? (Viniendo de la dinámica no lineal, eso me pone un poco nervioso, pero no tengo suficiente información general sobre los PRNG y no la obtendré en los próximos 2 días ...

Parece poco probable que haya muchos usuarios de Windows de 32 bits que se preocupen por el rendimiento de vanguardia. No se necesita mucho esfuerzo para cambiar a 64 bits.

Yo también me gustaría verlo.

Sabemos, sobre la base de las matemáticas, que debe haber muchos problemas grandes,
pero todavía no podemos señalar un ejemplo.

El principio de precaución diría que, dado que sabemos que existen grandes
problemas y sabemos cómo prevenirlos (CS-PRNG), también podríamos hacerlo
eso de forma predeterminada, y permite que los usuarios sean menos cautelosos si así lo desean.

El lunes 27 de mayo de 2019 a las 12:39 p. M. Robert Kern [email protected]
escribió:

@pbstark https://github.com/pbstark Lo que me gustaría ver es un concreto
implementación del problema (podría ser artificial, pero no demasiado artificial) en
en qué MT o PRNG de 128 bits falla y en el que funcionaría el criptorandom. Puedes
señalar un conjunto de datos donde el método de remuestreo da errores
inferencias con un PRNG de 128 bits y correctas inferencias con criptorandom?

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWODTUIPNMVOJB6QP3DPXQ2FPA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXWG43WNMVDJ89 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWITAGQFZDQSIFNEHETPXQ2FPANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

k-equidistribución es una propiedad de conjunto de las salidas PRNG en todo el
período del PRNG. Es algo bueno, pero no dice nada sobre otros
tipos de fallas de aleatoriedad, como la correlación serial de salidas.
Es una barra relativamente baja.

El lunes 27 de mayo de 2019 a las 12:48 p.m. Sebastian Berg [email protected]
escribió:

No sé lo suficiente sobre los PRNG para pesar realmente en cualquier caso, solo quiero
el enfoque debe estar en las propiedades estadísticas primero (si la respuesta es que
todos están muy muy bien, bien). Una cosa que me pregunto ahora es la
Equidistribución k-dimensional. ¿Usamos actualmente variantes de, por ejemplo, PCG?
que hacen bien aquí en comparación con MT? (Viniendo de la dinámica no lineal, que
me pone un poco nervioso, pero no tengo suficiente información general sobre los PRNG y
no lo obtendrá en los próximos 2 días ...

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWPOBTPYHC3XBINQYA3PXQ3HZA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXWHG43WZVMV6 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWOEB7KR2YJZWHRRAHLPXQ3HZANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@pbstark MT falla una serie de pruebas estadísticas que pasa PCG (y otros generadores).

@rkern

Si uno quiere que MSVC genere la instrucción ror, creo que debe usar el intrínseco "_rotr64".

También se podría preferir el indicador '/ O2' para la optimización.

Mirándolo, de hecho sería mejor escribirlo en ensamblador, si uno quiere usar PCG64.

Para @pbstark , aquí hay un resultado del PCG-64 inicializado con una semilla desconocida para usted (de hecho, incluso le diré la transmisión, es 0x559107ab8002ccda3b8daf9dbe4ed480 ):

  64bit: 0x21fdab3336e3627d 0x593e5ada8c20b97e 0x4c6dce7b21370ffc
     0xe78feafb1a3e4536 0x35a7d7bed633b42f 0x70147a46c2a396a0
  Coins: TTTHHTTTHHTHTTTTHTHHTTTTTHTTHHTTHHTHHTHHHHHHHHTTHHTTHHTHHHHHTHTHH
  Rolls: 5 3 5 2 5 3 1 6 6 5 4 4 5 5 5 6 2 3 5 3 2 3 2 5 6 2 4 6 2 3 4 6 3
  Cards: 5h 3h 3c 8d 9h 7s Kh Ah 5d Kc Tc 6h 7h 8s Ac 5c Ad Td 8c Qd 2h As
     8h 2d 3s 5s 4d 6d 2s Jd 3d 4h Ks 6s Qc Js Th 9d 9c Ts Jh 4c 2c 9s
     6c 4s 7c 7d Jc Qs Kd Qh

Ahora, supongamos que inicializa otro generador de pcg con una semilla elegida al azar. Escojamos, por el bien del argumento 0xb124fedbf31ce435ff0151f8a07496d3 . ¿Cuántas salidas debemos generar antes de descubrir esta salida conocida? Debido a que conozco la semilla que usé anteriormente, puedo responder eso (a través de la función de distancia de PCG), aproximadamente 2.5 × 10 ^ 38 (o aproximadamente 2 ^ 127.5) salidas. Como referencia, 10 ^ 38 nanosegundos es 230 mil millones de veces la edad del universo.

Así que hay una secuencia en PCG-64 que está realmente ahí, pero, prácticamente hablando, nunca la encontrarás a menos que te diga dónde buscar. (Y habría aún más posibilidades si variamos el flujo).

El PCG normal tiene en realidad cero posibilidades de producir una obra de Shakespeare; el esquema de generación extendida de PCG en realidad puede producir una obra de Shakespeare, pero la posibilidad de que alguna vez lo haga en un escenario no artificial es tan infinitesimal que es esencialmente cero también. En mi opinión, hay muy poco valor en una propiedad que no tiene ninguna consecuencia práctica.

(Además, los PRNG criptográficamente seguros no están garantizados para ser equidistribuidos k-dimensionalmente, ni son una fórmula mágica para las personas que quieren PRNG que puedan generar todas las secuencias posibles. En el momento en que desee más bits de un PRNG de los que toma como su seed and stores como su estado, hay necesariamente algunas secuencias de bits que no se pueden generar (prueba: por el principio de casillero). Y si te limitas a la misma cantidad de salida que pones como semilla, lo que Lo que realmente busca es una función hash, o tal vez solo la función de identidad si su entrada de semilla es verdaderamente aleatoria, no un PRNG).

Por curiosidad, terminé un generador de bits de contador AES usando aesctr.h , tiempo en ns por valor aleatorio:

+---------------------+--------------+----------+--------------+
|                     |   Xoshiro256 |    PCG64 |   AESCounter |
|---------------------+--------------+----------+--------------+
| 32-bit Unsigned Int |      3.40804 |  3.59984 |      5.2432  |
| Uniform             |      3.71296 |  4.372   |      4.93744 |
| 64-bit Unsigned Int |      3.97516 |  4.55628 |      5.76628 |
| Exponential         |      4.60288 |  5.63736 |      6.72288 |
| Normal              |      8.10372 | 10.1101  |     12.1082  |
+---------------------+--------------+----------+--------------+

Buen trabajo, @bashtage.

Algunas cosas a tener en cuenta, una es que las instrucciones AES específicas pueden variar según las arquitecturas y no están presentes en todas las CPU que se usan activamente, por lo que debe haber una ruta de respaldo (lenta).

Además, es un poco una comparación de manzanas con naranjas. Además de usar instrucciones especializadas, el código AES obtiene una parte de su velocidad al desenrollar el bucle; en realidad, genera números en bloques y luego los lee. El desenrollado puede acelerar potencialmente cualquier PRNG. FWIW, @lemire en realidad tiene una versión vectorizada de PCG que usa instrucciones AVX para generar múltiples salidas a la vez.

Permítanme ver si puedo resumir al menos un punto de consenso: todos estamos de acuerdo en que numpy debe tener opiniones sobre qué algoritmo BitGenerator usar y promover un BitGenerator sobre otros.

Permítanme una puñalada más para esbozar una opción "No predeterminada" que esté de acuerdo con ese consenso y evite algunos de los problemas que podrían tener algunas de las otras opciones. Si no consigue tracción, me callaré.

Lo que realmente quise decir con la opción "No predeterminado" fue "No predeterminado anónimo". Todavía hay formas en las que podemos diseñar la API de modo que la forma más conveniente de obtener un Generator inicializado sea una que nombre el PRNG que nominamos. Por ejemplo, digamos que _no_ incluimos un rango completo de algoritmos BitGenerator . Intentamos mantener el número bastante mínimo y dejar la terminación en scipy y otras bibliotecas de terceros, en general, y puede ser una buena idea hacerlo aquí. La belleza de la arquitectura actual es que nos permite mover esos BitGenerator s a otras bibliotecas. Entonces, digamos que solo proporcionamos MT19937 para respaldar el legado RandomState y el BitGenerator que preferimos que usen las personas. Por el bien del argumento, digamos que es Xoshiro256 . Hagamos que el constructor Generator.__init__() requiera un BitGenerator . Pero también, definamos una función np.random.xoshiro256_gen(seed) que devuelva Generator(Xoshiro256(seed)) debajo de las cubiertas. Documentamos esa función de conveniencia como la forma de obtener Generator sembrados.

Ahora adelanta algunos lanzamientos. Digamos que empujamos PCG64 , ThreeFry , etc.a random-tng o scipy o algún otro paquete, y uno de ellos se vuelve popular debido a las características adicionales o los nuevos defectos estadísticos se encuentran en Xoshiro256 . Decidimos que queremos actualizar la opinión de numpy sobre qué BitGenerator deben usar las personas a PCG64 . Entonces, lo que hacemos es agregar la clase PCG64 BitGenerator y agregar la función np.random.pcg64_gen(seed) . Agregamos una advertencia de obsolescencia a np.random.xoshiro256_gen(seed) para decir que ya no es el algoritmo preferido: recomendamos que el nuevo código use np.random.pcg64_gen(seed) , pero que continúe usando el algoritmo Xoshiro256 sin advertencias, deben usar explícitamente Generator(Xoshiro256(seed)) .

Creo que esto evita algunos de los problemas que tengo con una API "anónima" (es decir, Generator(seed) , Generator(DefaultBitGenerator(seed)) , np.random.default_gen(seed) ). Podemos admitir los algoritmos que ya no son preferidos a perpetuidad. Nunca necesitaremos hacer que nuestro constructor "preferido" obstinado haga algo diferente cuando cambiemos de opinión. Debido a que usamos los nombres reales para distinguir cosas en lugar de números de versión, siempre sabrá cómo actualizar el código para reproducir resultados antiguos (si no puede soportar las advertencias inofensivas por alguna razón). Incluso puede recoger el código sin procedencia o números de versión numerosos registrados y hacer esa actualización. Al mismo tiempo, al limitar la cantidad de algoritmos a un mínimo absoluto y hacer de las mejores prácticas la forma más fácil de trabajar con la biblioteca, podemos expresar la opinión de numpy de manera efectiva.

¿Como suena eso? Aún deberíamos esforzarnos mucho en hacer una elección sólida por esos BitGenerator para la primera versión. Sigue siendo trascendente.

Parece poco probable que haya muchos usuarios de Windows de 32 bits que se preocupen por el rendimiento de vanguardia. No se necesita mucho esfuerzo para cambiar a 64 bits.

Estoy de acuerdo. Como el problema con PCG64 en Win32 es simplemente el rendimiento (y uno que probablemente podamos mejorar con algo de esfuerzo), ¿estaría de acuerdo en que no es un bloqueador?

Si uno quiere que MSVC genere la instrucción ror, creo que debe usar el intrínseco "_rotr64".

También se podría preferir el indicador '/ O2' para la optimización.

¡Gracias! @imneme me señaló todos estos errores. :-)

@seberg ¿Tiene alguna cita de su campo que le haya llevado a su desconfianza? Por ejemplo, un artículo que mostró que la propiedad de equidistribución k = 623 de MT19937 solucionó problemas en simulaciones de dinámica no lineal que causaba un PRNG más pequeño. Podría proporcionar una garantía más específica con eso como referencia. En _general_, mi punto de vista sobre la equidistribución es que generalmente desea que la equidistribución del PRNG esté cerca del máximo permitido por el tamaño del estado del PRNG. En _práctica_, si su PRNG es lo suficientemente grande para sus propósitos en otros aspectos (pasa PractRand, tiene un período mayor que el cuadrado de la cantidad de muestras que planea extraer, etc.), nunca he visto muchas razones para preocuparme por el precisa k . Otros pueden tener opiniones diferentes, y tal vez haya problemas específicos en su campo que no conozco. Si ese es el caso, ¡hay soluciones específicas disponibles!

Por curiosidad, terminé un generador de bits de contador AES usando aesctr.h

Podría estar equivocado, pero no creo que esto ayude con las preocupaciones de @pbstark . AES-CTR es un CS-RNG, pero no todos los CS-RNG tienen los períodos largos que uno necesitaría para (teóricamente) poder alcanzar todas las posibles permutaciones de k! por k . El contador sigue siendo un número de 128 bits y, una vez que se reinicia, ha llegado al final del período. @pbstark aboga por PRNG de períodos muy largos, la mayoría de los cuales resultan ser CS-RNG.

En general, mi opinión sobre la equidistribución es que, por lo general, se desea que la equidistribución del PRNG esté cerca del máximo permitido por el tamaño del estado del PRNG.

Aunque algunos consideran que la equidistribución máxima es una propiedad deseable, también puede considerarse un defecto (y hay artículos que lo dicen). Si tenemos un PRNG de _k_ bits y cada secuencia de _k_ bits ocurre exactamente una vez, eso terminará violando el problema de cumpleaños que dice que esperaríamos ver una repetición de salida después de aproximadamente 2 ^ (k / 2) salidas. ( Escribí una prueba estadística de problemas de cumpleaños basada en estas ideas . Detectó correctamente la ausencia estadísticamente inverosímil de cualquier repetición en SplitMix, un PRNG de estado de 64 bits con salida de 64 bits y Xoroshiro64 +, salida de 64 bits de 32 bits -estar PRNG equidistribuido en dos dimensiones, entre otros.)

Curiosamente, aunque es muy práctico escribir una prueba estadística que fallará en un PRNG por falta de repeticiones de 64 bits (o demasiadas repeticiones; esperamos una distribución de Poisson), por el contrario _no_ práctico escribir una prueba que Detectamos la omisión del 36,8% de todos los valores de 64 bits si no sabemos cuáles se omiten.

Obviamente, probar la falla de falta de repeticiones esperadas comienza a ser poco práctico para ejecutar a medida que _k_ se hace más grande, pero a medida que llegamos a un tamaño de estado cada vez más grande (y período), el tamaño agregado significa que no es práctico demostrar que un PRNG equidistribuido al máximo tiene fallas por no repetirse y es igualmente poco práctico para mostrar que un PRNG equidistribuido no al máximo tiene fallas para repetir algunas secuencias de _k_ bits (de una manera estadísticamente plausible) y omitir otras por completo. En ambos casos, el PRNG es demasiado grande para que podamos distinguir los dos.

Yo también me gustaría verlo. Sabemos, sobre la base de las matemáticas, que debe haber muchos problemas importantes, pero todavía no podemos señalar un ejemplo. El principio de precaución diría que, dado que sabemos que hay grandes problemas y sabemos cómo prevenirlos (CS-PRNG), también podríamos hacerlo de forma predeterminada y dejar que los usuarios sean menos cautelosos si así lo desean.

Debo decir que esta línea de argumentación y evidencia no me convence. Como tal, no me sentiría cómodo parado frente a muchos usuarios y les diría que deberían cambiar por esta razón. No estoy preparado para defender esta afirmación, por lo que pido estos ejemplos. Serían persuasivos y me prepararían para defender esta posición que usted recomienda que adoptemos.

Hay muchas propiedades por las que los PRNG finitos no alcanzan los verdaderos RNG. Y hay muchos cálculos que queremos hacer que, teóricamente, dependen de esas propiedades de los verdaderos RNG (o al menos, no hemos probado rigurosamente cuánto podemos relajarlos). Pero muchas de esas deficiencias solo tienen un efecto muy pequeño, prácticamente imperceptible, en los resultados de los cálculos reales que realizamos. Estas violaciones no son de tipo dispositivo, todo o nada, pasa / no pasa. Tienen un tamaño de efecto y podemos tolerar más o menos el efecto.

Muestra, de manera convincente, por supuesto, que los PRNG de cierto tamaño no pueden generar uniformemente todas las permutaciones k! para algunos k . El paso que me falta es cómo esa incapacidad para generar todas esas permutaciones afecta un cálculo concreto que me interesaría realizar. Me falta la prueba que recomendaría agregar a PractRand o TestU01 que demostraría el problema a la gente.

Una de las líneas de análisis que encontré muy informativas del artículo de PCG de @imneme fue derivar múltiples versiones de estado más pequeño de cada PRNG y ver exactamente dónde comenzaron a fallar TestU01. Eso nos da alguna forma de comparar normalmente las arquitecturas PRNG en lugar de simplemente decir, todo o nada, "X PRNG pasa" o falla. También nos permite estimar cuánto margen tenemos en los tamaños de estado en uso (que pasan TestU01 en una gran cantidad de GiB de muestras). ¿Hay cálculos concretos que pueda hacer que demuestren un problema con los PRNG de 8 bits? PRNG de 16 bits? Entonces podemos ver si esta nueva prueba nos da información sobre el PRNG antes que TestU01 / PractRand lo hace actualmente. Y eso sería muy útil.

Por otro lado, si se necesitan _más_ datos extraídos del PRNG para mostrar una falla basada en estas permutaciones que el punto de falla de los PRNG pequeños para el conjunto actual de pruebas en PractRand, entonces concluiría que este problema no es práctico. preocupación, y podríamos usar "pasa PractRand" como un buen indicador de si el problema de la permutación tendría o no un efecto práctico en mis cálculos.

Pero hasta que no tenga un programa en mi mano que pueda ejecutar y mostrarle a la gente el problema, no me sentiré cómodo presionando por un CS-PRNG sobre esta base. No podría explicar la elección de manera convincente.

Para aquellos que exigen eso al mezclar 32 elementos, ¡los 32! los barajados (es decir, todos los 263130836933693530167218012160000000 de ellos) deben poder generarse, en lugar de un PRNG que proporciona simplemente una muestra aleatoria de esos 32. baraja, en realidad diría que si vas a exigir grandes números, simplemente no estás

Por lo tanto, afirmaría (en broma) que el orden en el que salen esas barajas tampoco debe estar predeterminado. ¡Claramente debe exigir que produzca los 32! baraja en todos los órdenes posibles - (¡32!)! es lo que necesitas! Por supuesto, esto requerirá 3.8 × 10 ^ 18 exabytes para el estado y una cantidad similar de entropía para inicializar, pero seguramente vale la pena saber que todo está ahí.

... Agregamos una advertencia de obsolescencia a np.random.xoshiro256_gen(seed) para decir que ya no es el algoritmo preferido: recomendamos que el nuevo código use np.random.pcg64_gen(seed) , pero para continuar usando el algoritmo Xoshiro256 sin advertencias, deberían usar explícitamente Generator(Xoshiro256(seed))

Eso sigue molestando a los usuarios con una desaprobación y con nombres que suenan extraños de los que realmente no quieren saber.

La NEP dice: En segundo lugar, se permitirá con precaución romper la compatibilidad de transmisión para introducir nuevas funciones o mejorar el rendimiento.

Si hay una razón suficientemente buena para actualizar nuestro valor predeterminado, entonces simplemente hacer eso y romper la reproducibilidad bit a bit para los usuarios que no especificaron explícitamente un algoritmo es la mejor opción en mi humilde opinión (y una por la que discutías antes).

Lo que realmente quise decir con la opción "No predeterminado" fue "No predeterminado anónimo".

Entonces, ¿quieres decir que _quieres_ que los usuarios sepan el nombre del PRNG que están usando?

Mire esto desde el punto de vista de un usuario. Ya es bastante difícil hacer que pasen de np.random.rand & co a np.random.RandomState() y luego usar métodos. Ahora vamos a presentar un sistema mejor, y lo que ven es np.random.xoshiro256_gen() ? Eso sería una gran regresión en cuanto a usabilidad.

Entonces, ¿quieres decir que _quieres_ que los usuarios sepan el nombre del PRNG que están usando?

No, es para mitigar los problemas de tener una API de "objetivo móvil designado" como default_generator(seed) que la gente estaba trabajando (por ejemplo, el argumento version @shoyer ).

Mantener la compatibilidad de la transmisión (que NEP 19 niega) es secundario a la rotura de API. Los diferentes BitGenerator s tienen diferentes API efectivas, dependiendo de sus conjuntos de características (settable-streams, jumpahead, principalmente, aunque puede haber otros, dependiendo de qué tan parametrizable sea el PRNG). Por lo tanto, algunos cambios en nuestra selección PRNG predeterminada romperían el código (es decir, ya no se ejecuta o ya no se ejecuta correctamente), no solo cambiarían los valores que aparecen.

Por ejemplo, digamos que primero elegimos PCG64 . Tiene un estado de 128 bits, 2 ^ 127 flujos configurables e implementa jumpahead; agradable y con todas las funciones. Entonces la gente comienza a escribir default_generator(seed, stream=whatever) . Ahora, digamos que el trabajo futuro encuentra algún defecto estadístico importante que nos hace querer cambiar a otra cosa. El próximo PRNG que promocionemos como predeterminado debe tener> = estado de 128 bits (fácil; no recomendaría nada más pequeño como predeterminado de propósito general), jumpahead (¡difícil!),> = 2 ^ 127 flujos configurables (whoo, ¡chico!), para no romper los usos de default_generator() que ya existen en el código. Ahora quizás podamos vivir con ese trinquete.

@shoyer sugirió que tal vez podríamos hacer que el valor predeterminado BitGenerator siempre se redujera deliberadamente a las características del mínimo común denominador. ¡Eso funcionaria! Pero también perdería la oportunidad de promover transmisiones configurables para resolver el problema de las transmisiones paralelas como a @charris le gustaría hacer.

Ahora vamos a presentar un sistema mejor, y lo que ven es np.random.xoshiro256_gen() ? Eso sería una gran regresión en cuanto a usabilidad.

Si el problema es el nombre que suena extraño, entonces me complacería usar un nombre genérico más amigable, siempre y cuando la política sea la misma (agregamos una nueva función y comenzamos a advertir sobre la anterior). Lo consideraría equivalente. No deberíamos estar haciendo esto con demasiada frecuencia.

También estoy bien si decidimos vivir con el trinquete y evitar un mecanismo version .

Mi opinión sobre un "predeterminado" es que podríamos dejarlo como un detalle de implementación, de modo que Generator() siempre funcionaría. Parlamentaría esto con una fuerte nota de precaución de que la única forma de obtener resultados siempre reproducibles (hasta los cambios en Generator) es usar la sintaxis Generator(BitGenerator(kwarg1=1,kwargs2=b,...))

No es práctico ocultar realmente los detalles de implementación, ya que el acceso al estado requiere decapado.

La alternativa es simplemente tratarlo como cualquier otra función, y la generación aleatoria en general, y pasar por un ciclo de depreciación estándar en caso de que haya una necesidad imperiosa de cambiar. Esto nunca afectará a los usuarios que hacen las cosas correctamente, y con suficiente advertencia en los documentos, podría ser posible obtener una tasa de éxito decente en esto, al menos entre los proyectos grandes. Lo que estoy sugiriendo aquí es que uno podría olvidar que la garantía de compatibilidad de transmisión se hizo alguna vez al pensar en la nueva API.

@bashtage en # 13650 No permití el acceso a Generator().bit_generator de una manera que aún permite el decapado sin acceso directo a state . Pasa el test_pickle ligeramente reescrito de una manera que permitiría su uso en python Thread s

Mi pregunta es: ¿qué es seguro? Solo hay diversos grados de cuasialeatorio con varias propiedades. Hasta ahora no he visto a nadie dar un ejemplo concreto, ni aquí ni en otros temas, PRs o hilos.

"Pasa PractRand en _N_ GiB" para algunos _N_ (512, 1024) es una definición aceptable si desea un criterio claro de aprobación / falla de línea brillante. Si desea un ejemplo concreto, MT y sus variantes se excluirían en función de este criterio. También retiramos a algunos de los miembros mayores de la familia Xoroshiro del PR debido a esto.

Si desea una vista más sofisticada de la calidad estadística que permita la clasificación ordinal de algoritmos, le recomiendo la Sección 3 del artículo PCG de BitGenerator 128 bits pasa PractRand pero su versión de 120 bits falla, probablemente sea bastante arriesgado.

@mattip Eso parece razonable. Aunque alguien en alguna parte lo hará

import gc
state = [o for o in gc.get_objects() if 'Xoshiro256' in str(o)][0].state

si quieren ahondar tan profundamente, está bien. Solo quiero ayudar al usuario no experto

Pasa el test_pickle ligeramente reescrito de una manera que permitiría su uso en python Thread s

Vale la pena señalar que este es un problema pendiente (# 9650); idealmente, Generator() se reiniciaría en subprocesos secundarios. IIRC esto solo es práctico en Python> = 3.7

Mi opinión sobre un "predeterminado" es que podríamos dejarlo como un detalle de implementación, de modo que Generator() siempre funcionaría. Parlamentaría esto con una fuerte nota de precaución de que la única forma de obtener resultados siempre reproducibles (hasta los cambios en Generator) es usar la sintaxis Generator(BitGenerator(kwarg1=1,kwargs2=b,...))

Hay dos tipos de reproducibilidad que debemos distinguir. Una es que ejecuto mi programa dos veces con la misma semilla y obtengo los mismos resultados. Ese es el que necesitamos apoyar. La otra es la reproducibilidad entre versiones de numpy, que hemos rechazado, al menos en el sentido más estricto.

Generator() argumentos, es decir, "dame un PRNG sembrado arbitrariamente que recomiende numpy" no es el caso de uso principal. No requiere mucho apoyo. "Dame el PRNG que numpy recomienda con _esta_ semilla" es, y es para lo que estamos discutiendo opciones. Necesitamos una forma para que Numpy exprese una opinión sobre cómo obtener un PRNG sembrado, y esa forma debe ser fácil y conveniente para los usuarios (o de lo contrario no la usarán). _Me gusta_ nombrar el algoritmo (aunque a través de una función más conveniente), pero @rgommers cree que es un paso demasiado lejos, y simpatizo con eso.

El generador sin argumentos (), es decir, "dame un PRNG sembrado arbitrariamente que recomiende numpy" no es el caso de uso principal. No requiere mucho apoyo. "Dame el PRNG que numpy recomienda con esta semilla" es, y es para lo que estamos discutiendo opciones. Necesitamos una forma para que Numpy exprese una opinión sobre cómo obtener un PRNG sembrado, y esa forma debe ser fácil y conveniente para los usuarios (o de lo contrario no la usarán). Me gusta nombrar el algoritmo (aunque a través de una función más conveniente), pero @rgommers cree que es un paso demasiado lejos, y simpatizo con eso.

Yo diría que los usuarios en realidad están mal equipados para proporcionar buenas semillas. Por ejemplo, ¿cuántos usuarios conocen la forma correcta de sembrar el Mersenne Twister? No es tan fácil como cree : si no lo está alimentando con 624 enteros aleatorios de 32 bits (para proporcionar 19937 bits de estado), lo está haciendo mal.

Entonces, en realidad, diría que la forma correcta para que el usuario obtenga resultados reproducibles es crear el PRNG (sin proporcionar una semilla, dejando que se siembre bien automáticamente) y luego encurtirlo.

Si la discusión es solo sobre la manera correcta, entonces estoy a favor
Generator(BitGenerator(**kwargs)) ya que solo lo usarán
usuarios de semiaware que se preocupan por la reproducción.

Creo que el valor predeterminado utilizado para Generator() importa, ya que será
interpretado tantas como una elección considerada y, por lo tanto, tómela como una
recomendación al utilizar el formulario de semillas.

Solo para tirar uno más, un método de clase Generator.seeded(seed[, bit_generator]) where bit generator is a string. This would allow the pattern of switching from one value to None to warn if the default was going to change, like lstsq. I would also only support a limited pallatte of but generators initially (i.e. 1). Doesn't make it easy to expose advanced features I suppose. In a perfect world it would use kwarg only to allow any keyword argument to be used which avoids most depreciation problems. Of course, this doesn't really need to be a class function, just sembrado`.

El martes 28 de mayo de 2019 a las 16:38, Robert Kern [email protected] escribió:

Mi opinión sobre un "predeterminado" es que podríamos dejarlo como una implementación
detalle, para que Generator () siempre funcione. Parlamentaría esto con un
fuerte nota de precaución que la única forma de obtener resultados siempre reproducibles
(hasta cambios en Generator) es usar la sintaxis
Generador (BitGenerator (kwarg1 = 1, kwargs2 = b, ...))

Hay dos tipos de reproducibilidad que debemos distinguir. Uno es
que ejecuto mi programa dos veces con la misma semilla y obtengo los mismos resultados.
Ese es el que necesitamos apoyar. El otro es la reproducibilidad a través
versiones de numpy, que hemos rechazado, al menos en el sentido más estricto.

Generador sin argumentos (), es decir, "dame un PRNG sembrado arbitrariamente que
numpy recomienda "no es el caso de uso principal. No requiere mucho
apoyo. "Dame el PRNG que recomienda numpy con esta semilla" es,
y para eso estamos discutiendo opciones. Necesitamos una forma de numpy para
expresar una opinión sobre cómo obtener un PRNG sembrado, y de esa manera debe ser
fácil y conveniente para los usuarios (o de lo contrario no lo usarán). Me gusta
nombrar el algoritmo (aunque a través de una función más conveniente), pero
@rgommers https://github.com/rgommers cree que es un paso demasiado lejos y
Simpatizo con eso.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKA4SSNW6XZEVFUMCDPXVGW5A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVDVREXWG43V2 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABKTSROCMLHG6E6BLWI6TWDPXVGW5ANCNFSM4HPX3CHA
.

El martes 28 de mayo de 2019 a las 16:38, Robert Kern [email protected] escribió:

Mi opinión sobre un "predeterminado" es que podríamos dejarlo como una implementación
detalle, para que Generator () siempre funcione. Parlamentaría esto con un
fuerte nota de precaución que la única forma de obtener resultados siempre reproducibles
(hasta cambios en Generator) es usar la sintaxis
Generador (BitGenerator (kwarg1 = 1, kwargs2 = b, ...))

Hay dos tipos de reproducibilidad que debemos distinguir. Uno es
que ejecuto mi programa dos veces con la misma semilla y obtengo los mismos resultados.
Ese es el que necesitamos apoyar. El otro es la reproducibilidad a través
versiones de numpy, que hemos rechazado, al menos en el sentido más estricto.

Generador sin argumentos (), es decir, "dame un PRNG sembrado arbitrariamente que
numpy recomienda "no es el caso de uso principal. No requiere mucho
apoyo. "Dame el PRNG que recomienda numpy con esta semilla" es,
y para eso estamos discutiendo opciones. Necesitamos una forma de numpy para
expresar una opinión sobre cómo obtener un PRNG sembrado, y de esa manera debe ser
fácil y conveniente para los usuarios (o de lo contrario no lo usarán). Me gusta
nombrar el algoritmo (aunque a través de una función más conveniente), pero
@rgommers https://github.com/rgommers cree que es un paso demasiado lejos y
Simpatizo con eso.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKA4SSNW6XZEVFUMCDPXVGW5A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVDVREXWG43V2 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABKTSROCMLHG6E6BLWI6TWDPXVGW5ANCNFSM4HPX3CHA
.

Desde una perspectiva de usabilidad, creo que realmente necesitamos admitir Generator(seed) . De lo contrario, frente a tomar una decisión que no están preparados para tomar, los usuarios simplemente se quedarán con RandomState .

Para versionar el generador de bits predeterminado en Generator , podríamos usar bit_version=1 lugar de version=1 , aunque también estoy de acuerdo con descartar la idea version . No creo que los usuarios necesiten configurar generadores de bits explícitamente muy a menudo.

Mi preferencia para resolver casos de uso específicos que necesitan características de generador particulares sería diseñar nuevas API de BitGenerator genéricas que oculten los detalles de implementación. Estos pueden agregarse a DefaultBitGenerator o colocarse en nuevas clases si su uso implica compensaciones, por ejemplo, ParallelBitGenerator .

Definitivamente me gustaría evitar advertencias sobre cambios futuros en el flujo de RNG debido al cambio del generador de bits predeterminado. Estas advertencias serían solo ruido para la gran mayoría de usuarios que no confían en tales detalles, pero que establecen un seed en Generator solo para evitar que sus números aleatorios cambien espontáneamente.

los usuarios simplemente se quedarán con RandomState.

Eso está bien, no son los primeros en adoptar. Estoy presionando mucho (¿quizás demasiado?) Por la mínima API viable posible, ya que siempre podemos ampliar la API, pero es mucho más difícil reducirla. Los matices entre Generator(Philox()) , Generator(seed(3)) y Generator(bit_version=1) son un poco difíciles de ver hasta que esto llega a los usuarios finales.

Obtengamos una primera versión sin Generator(seed) y obtengamos algunos comentarios.

Obtengamos una primera versión sin Generator(seed) y obtengamos algunos comentarios.

Bien, no tengo objeciones serias aquí. En ese caso, también podríamos requerir especificar el BitGenerator completo por ahora.

Entonces, en realidad, diría que la forma correcta para que el usuario obtenga resultados reproducibles es crear el PRNG (sin proporcionar una semilla, dejando que se siembre bien automáticamente) y luego encurtirlo.

Digo lo mismo, pero obtengo muy poca tracción con eso. Como usted dice, "Bueno, ¡el hecho de que algo sea una mala idea no significa que la gente no quiera hacerlo!"

Parte del problema se ve agravado por el enorme estado de MT, que realmente requiere la serialización en un archivo. Es difícil hacer que ese baile basado en archivos sea la API más fácil disponible para que los usuarios quieran usarla. Las cosas irán mejor con un PRNG predeterminado con un estado mucho más pequeño. 128 bits es el tamaño de un UUID, que es lo suficientemente pequeño como para imprimir en hexadecimal y copiar y pegar. Por lo tanto, un buen patrón podría ser escribir su programa de manera que por defecto tenga una buena semilla de entropía y luego imprima su estado de manera que pueda copiarlo y pegarlo la próxima vez que ejecute el programa.

❯ python secret_prng.py
Seed: 0x977918d0c7da45e5168f72005586500c
...
Result = 0.7223650399276123

❯ python secret_prng.py
Seed: 0xe8962534e5fb585483b86119fcb852ce
...
Result = 0.10640984721018876

❯ python secret_prng.py --seed 0xe8962534e5fb585483b86119fcb852ce
Seed: 0xe8962534e5fb585483b86119fcb852ce
...
Result = 0.10640984721018876

No estoy seguro de si un patrón como este proporcionaría tanto la simplicidad como la prueba para el futuro.

NumPy 1.siguiente

class Generator:
    def __init__(bitgen_or_seed=None, *, bit_generator='pcg64', inc=0):

NumPy 1.20.x

`` pitón
generador de clases:
def __init __ (bitgen_or_seed = Ninguno, *, bit_generator = Ninguno, inc = Ninguno):
si bit_generator no es None o inc no es None:
warn ('El valor predeterminado es cambiar de PCG64 a AESCtr. La palabra clave inc'
'el argumento está en desuso y se planteará en el futuro', FutureWarning)
`` ``

NumPy 1.22

`` pitón
generador de clases:
def __init __ (bitgen_or_seed = Ninguno, *, bit_generator = 'aesctr', inc = Ninguno, contador = 0):
si bit_generator == 'pcg64' o inc no es None:
raise Exception ('PCG ya no es compatible y se ha eliminado inc')
`` ``

No estoy seguro de si un patrón como este proporcionaría tanto la simplicidad como la prueba para el futuro.

Como señalé anteriormente en https://github.com/numpy/numpy/issues/13635#issuecomment -496589421, creo que esto sería sorprendente y frustrante para la mayoría de los usuarios. Preferiría proporcionar un objeto BitGenerator explícito que planear comenzar a emitir advertencias si los usuarios no han establecido todos los argumentos opcionales. Ese debería ser realmente un último recurso, para los casos en los que descubrimos que las API están dañadas de formas que no anticipamos.

El problema es el período de transición. Todos los que utilicen los valores predeterminados recibirán repentinamente advertencias sin una excelente manera de cambiar durante el período de transición. O al menos, los mueve de un "estado de baja energía" de Generator(seed) a un "estado de alta energía" menos conveniente de Generator(seed, bit_generator='aesctr') . Dado que el objetivo de esta API era proporcionar un "estado de bajo consumo de energía" conveniente, no hemos logrado nuestro propósito durante esta transición. Hicimos esto una vez con una de nuestras funciones de histograma, IIRC, y fue una pesadilla.

Esto es común a todas las depreciaciones que intentan cambiar el significado de los argumentos en el lugar. Las bajas que te _mueven_ de una función a otra son mucho más fáciles de gestionar, y eso es lo que yo estaba defendiendo.

Obtengamos una primera versión sin Generator(seed) y obtengamos algunos comentarios.

Por "primera versión", ¿te refieres a un lanzamiento completo? ¿O simplemente fusionar las relaciones públicas (lo que ha sucedido desde entonces)?

Si es un lanzamiento completo, todavía tenemos algunas cosas por determinar, como cuántos BitGenerator s incluimos. Si incluimos el complemento actual completo, entonces hemos revelado algunas de las opciones.

Las bajas que te _mueven_ de una función a otra son mucho más fáciles de gestionar, y eso es lo que yo estaba defendiendo.

+1 de acuerdo

No, es para mitigar los problemas de tener una API de "objetivo móvil designado" como default_generator (semilla) con la que la gente estaba trabajando (por ejemplo, el argumento de la versión de @shoyer ).

Mantener la compatibilidad de la transmisión (que NEP 19 niega) es secundario a la rotura de API. Diferentes BitGenerators tienen diferentes API efectivas

Ah está bien, ahora tiene más sentido para mí.

Si el problema es el nombre que suena extraño, entonces me complacería usar un nombre genérico más amigable, siempre y cuando la política sea la misma (agregamos una nueva función y comenzamos a advertir sobre la anterior). Lo consideraría equivalente. No deberíamos estar haciendo esto con demasiada frecuencia.

Esta parece la mejor solución hasta ahora. Debería ser posible elegir un nombre sensato aquí. Y hay un gran espacio de otros nombres cuerdos, que probablemente nunca necesitaremos.

Algo como np.random.generator o np.random.default_generator .

cuántos BitGenerators incluimos

¿Podría abrir un problema separado con una propuesta para eliminar los que cree que deberíamos eliminar de los incluidos actualmente (MT19937, DSFMT, PCG32, PCG64, Philox, ThreeFry, Xoshiro256, Xoshiro512)?

Todavía no hemos resuelto el problema en cuestión aquí: qué BitGenerator debería ser predeterminado (actualmente Xoshiro256 )

Bueno, esta cuestión trata más sobre "cuál debería promocionarse como el distinguido BitGenerator ", que se basa en la elección predeterminada, pero también cuáles deberían incluirse o descartarse. La mecánica mediante la cual proporcionamos valores predeterminados (si proporcionamos valores predeterminados) agrega algunas restricciones, por lo que estas son todas las cosas que más o menos deben decidirse juntas. Es un gran lío peludo, y después de todo el trabajo que ha hecho para guiar este PR, estoy seguro de que está exhausto de ver otro mega hilo sin ningún código que contribuya, así que tiene mis condolencias. :-)

En cuanto a los algoritmos per se, ya he dado mis recomendaciones: debemos mantener MT19937 por RandomState y con fines de comparación, y me gusta PCG64 con fines de recomendación.

Me equivoqué un poco en Compiler Explorer, y creo que implementé PCG64 para MSVC de 64 bits usando intrínsecos de una manera que obliga al compilador a generar ensamblado cerca de uint128_t math: https: // godbolt de clang .org / z / ZnPd7Z

No tengo un entorno de desarrollo de Windows configurado en este momento, así que no sé si realmente es _correcto _... @bashtage, ¿te importaría darle una oportunidad a esto?

Sin parche:

Uniforms per second
************************************************************
PCG64            62.77 million

Con parche:

Uniforms per second
************************************************************
PCG64           154.50 million

El parche pasa las pruebas, incluida la generación del mismo conjunto de 1000 valores uint64 para 2 semillas diferentes.

Para comparar con GCC nativo y en modo de comparación:

Time to produce 1,000,000 Uniforms
************************************************************
Linux-64, GCC 7.4                      PCG64            4.18 ms
Linux-64, GCC 7.4, Forced Emulation    PCG64            5.19 ms
Win64                                  PCG64            6.63 ms
Win32                                  PCG64           45.55 ms

guau, eso parece realmente malo. Tal vez deberíamos ampliar la información en la página de comparaciones para demostrar el rendimiento msvc2017-on-win {64, 32} frente a gcc7.4-on-linux {64, 32} en la misma máquina (supongo que estás usando msvc2017, probablemente debería incluir esa información en algún lugar).

Win32 no tiene remedio aquí. Sospecho que Linux de 32 bits también será bastante terrible, pero no tengo un sistema Linux de 32 bits para probarlo fácilmente.

Definitivamente puedo ver el caso para hacer una recomendación para las personas que se quedan en una máquina de 32 bits (probablemente Windows debido a las políticas de TI corporativas). Este recco es claro: DSFMT para 32 bits (o MT19937 también es bueno). Sin embargo, los puntos de referencia serían buenos.

Por lo que vale, soy bastante escéptico ante la afirmación de PCG que se repite a menudo de múltiples flujos aleatorios independientes . ¿Alguien ha realizado algún análisis estadístico serio para respaldar la afirmación de independencia? (En realidad, creo que el artículo de O'Neill solo se refiere a corrientes "distintas", sin ningún reclamo de independencia).

Creo que hay una buena razón para ser escéptico: para un multiplicador LCG dado, todos estos flujos distintos simplemente se relacionan mediante el escalado [*]. Entonces, dados dos flujos LCG con el mismo multiplicador, uno de ellos será simplemente un múltiplo constante (módulo 2**64 o 2**32 según corresponda) del otro, aunque con diferentes puntos de partida. La parte de permutación del PCG ayudará a ocultar esto un poco, pero realmente no sería sorprendente que hubiera correlaciones detectables estadísticamente.

Flujos tan independientes al pie de la letra sin algunas pruebas serias.

[*] Ejemplo: suponga que x[0], x[1], x[2], ... es un flujo LCG estándar de 64 bits, con x[i+1] := (m*x[i] + a) % 2**64 . Establezca y[i] := 3*x[i] % 2**64 para todos los i . Entonces y[i] es un flujo LCG con y[i+1] := (m*y[i] + 3*a) % 2**64 , por lo que simplemente escalando el flujo original ha producido uno de estos flujos LCG distintos con el mismo multiplicador pero una constante aditiva diferente. Al usar otros multiplicadores impares en lugar de 3 , y suponiendo que solo estamos interesados ​​en LCG de período completo (y por lo tanto a es impar), obtendrá todos los posibles período de LCG con ese multiplicador.


EDITAR: Se corrigió una declaración incorrecta sobre el número de clases de conjugación.

Creo que el análisis público más completo de las transmisiones PCG está aquí: http://www.pcg-random.org/posts/critiquing-pcg-streams.html

@imneme ¿Puedes ampliar tu consejo final? "La correspondencia con David Blackman muestra que puede ser más fácil de lo que pensaba hacer transmisiones" cercanas "con inicializaciones correlacionadas como una semilla constante y transmisiones de 1, 2, 3, 4. Si va a utilizar múltiples transmisiones en al mismo tiempo, por ahora recomendaré que la identificación de la secuencia y la semilla sean distintas y no tengan correlaciones obvias entre sí, lo que significa que no las haga a las dos, 1,2,3,4 ".

¿Significa esto que cree que está bien tener una sola semilla buena (por ejemplo, derivada de una fuente de entropía) y luego los ID de flujo 1,2,3,4? ¿O deberían elegirse aleatoriamente tanto el ID de la semilla como del flujo de una buena fuente de entropía?

Algunos números de Linux-32 (Ubuntu 18.04 / GCC 7.4)

Time to produce 1,000,000 Uniforms
***************************************************************
Linux-64, GCC 7.4                      PCG64            4.18 ms
Linux-64, GCC 7.4, Forced Emulation    PCG64            5.19 ms
Win64                                  PCG64            6.63 ms
Win32                                  PCG64           45.55 ms
Linux-32, GCC 7.4                      PCG64           25.45 ms

Por lo que es dos veces más rápido que Win-32 pero lento. Los 4 tiempos se realizaron en la misma máquina

Other Linux-32/GCC 7.4 Timing Results
-----------------------------------------------------------------
DSFMT            6.99 ms
MT19937         13.09 ms
Xoshiro256      17.28 ms
numpy           15.89 ms

NumPy es el NumPy 1.16.4. DSFMT es el único generador con buen rendimiento en 32 bits (x86). Esto debe documentarse claramente para cualquier usuario de 32 bits. MT19937 también es una opción relativamente buena para un usuario de 32 bits.

Entonces, necesitamos tener MT19937 para propósitos heredados. Si queremos ser mínimos sobre qué PRNG incluimos (es decir, MT19937 más nuestra única recomendación de propósito general), entonces no me sentiría obligado a usar el rendimiento de 32 bits para restringir nuestra única recomendación de propósito general ni siéntase obligado a agregar un tercer PRNG "recomendado para 32 bits". MT19937 siempre estará disponible, y no es peor que lo que tienen actualmente. Y los paquetes de terceros estarán disponibles para usos más específicos.

Por supuesto, si queremos incluir un conjunto más completo de PRNG por otros motivos, podemos hacer todo tipo de recomendaciones específicas en la documentación.

Tenía curiosidad por saber cuánto mitigaba la parte "P" de PCG los problemas potenciales de los flujos correlacionados.

Entonces, aquí está (quizás) el peor caso posible para los LCG: donde la constante aditiva de un flujo LCG es la negación exacta de la constante aditiva para el otro. Luego, con elecciones de semilla apropiadamente terribles, terminamos con una de las corrientes de LCG siendo la negación exacta de la otra.

Pero ahora, si estamos usando ambos flujos para generar una serie de flotantes, tanto la parte de permutación del PCG como la conversión a float64 deberían ayudarnos un poco.

Aquí hay una gráfica que muestra cuánto ayuda la permutación:

streams

Ese es un diagrama de dispersión de 10000 flotadores de una de esas corrientes, contra 10000 de su gemelo negado. No es terrible, pero tampoco genial: hay artefactos claros.

No estoy seguro de qué concluir de esto: es absolutamente un ejemplo artificial, con el que es poco probable (espero) que te encuentres por accidente. Por otro lado, demuestra que se requiere un poco de pensamiento y cuidado si realmente necesita múltiples flujos no correlacionados.

Para que conste, aquí está la fuente:

import matplotlib.pyplot as plt
import numpy as np

from pcgrandom import PCG64

gen1, gen2 = PCG64(), PCG64()
multiplier, increment, state = gen1._get_core_state()
new_increment, new_state = -increment % 2**128, -state % 2**128
gen2._set_core_state((multiplier, new_increment, new_state))

xs = np.array([gen1.random() for _ in range(10**4)])
ys = np.array([gen2.random() for _ in range(10**4)])
plt.scatter(xs, ys, s=0.1)
plt.show()

PCG64 es el generador que O'Neill llama PCG-XSL-RR (sección 6.3.3 del documento PCG). El paquete pcgrandom es de aquí

Pensé que la forma estándar de obtener transmisiones independientes es usando jumpahead ().
Volver a sembrar para obtener flujos "independientes" es, en general, peligroso.

Los generadores de contador / hash tienen un jumpahead () trivial. ¿Tiene PCG?

También una petición de un usuario: proporcione al menos un flujo de bits que sea
calidad criptográfica, con un espacio de estado ilimitado.

Salud,
Felipe

(EDITAR por seberg: cita de correo electrónico eliminada)

@pbstark : Esto no es solo resembrar: los dos generadores LCG subyacentes son realmente distintos: x ↦ mx + a (mod 2 ^ 128) y x ↦ mx + b (mod 2 ^ 128) para diferentes incrementos ay b. El documento PCG de O'Neill vende la idea de poder crear diferentes flujos cambiando ese incremento de LCG (consulte la sección 4.3.2 del documento).

Pero la simplicidad de LCG significa que cambiar esa constante aditiva _es_ solo equivale a un salto en una cantidad desconocida en el generador original, combinado con una transformación lineal simple (multiplicación por una constante, o simplemente suma de una constante en algunos casos).

No es una razón para no usar PCG, y ni por un momento estoy argumentando que no es adecuado para el nuevo PRNG principal de NumPy; Simplemente no quiero que la gente se deje engañar por la promesa de transmisiones aleatorias "independientes". En el mejor de los casos, la idea de flujos configurables para PCG ofrece una forma conveniente de hacer algo equivalente a un salto rápido más una transformación adicional multiplicativa o aditiva adicional.

Discutimos un poco el criptográfico en la llamada comunitaria. Creo que fuimos un poco cautelosos al respecto. Parece una buena idea, pero si incluimos un RNG criptográficamente sólido, también tenemos que estar al día con cualquier problema de seguridad que surja, ya que no sabemos si los usuarios los usan con fines criptográficos reales.

En la nota de cuántos incluir: El consenso tendió a que estaba bien tener algunos generadores de bits más (por supuesto, una pequeña documentación sería buena). La carga de mantenimiento no parece demasiado grande. Al final, supongo que optaríamos por lo que sugieran Kevin y Robert.

En la nota de nombres: personalmente no me importa usar los nombres RNG y obligar a los usuarios a usarlos, el único inconveniente es que es posible que tengamos que buscar el nombre mientras codificamos. Deberíamos intentar tener la menor cantidad posible de advertencias de desaprobación. Me gusta la API mínima expuesta para el RNG predeterminado sin sembrar.

@mdickinson , intenté reproducir su gráfico yo mismo y fallé. Usé la versión canónica de C ++ del código con este programa, que debería ser el equivalente moral del tuyo.

#include "pcg_random.hpp"
#include <iostream>
#include <random>

int main() {
    std::random_device rdev;
    pcg_detail::pcg128_t seed = 0;
    pcg_detail::pcg128_t stream = 0;
    for (int i = 0; i < 4; ++i) {
        seed   <<= 32;           
        seed   |= rdev();
        stream <<= 32;           
        stream |= rdev();
    }
    pcg64 rng1(seed,stream);
    pcg64 rng2(-seed,-stream);
    std::cerr << "RNG1: " << rng1 << "\n";
    std::cerr << "RNG2: " << rng2 << "\n";
    std::cout.precision(17);
    for (int i = 0; i < 10000; ++i) {
        std::cout << rng1()/18446744073709551616.0 << "\t";
        std::cout << rng2()/18446744073709551616.0 << "\n";
    }
}

Cuando ejecuto esto, genera (para permitir la reproducibilidad):

RNG1: 47026247687942121848144207491837523525 203756742601991611962280963671468648533 41579532896305845786243518008404876432
RNG2: 47026247687942121848144207491837523525 136525624318946851501093643760299562925 52472962479578397910044896975270170620

y puntos de datos que pueden trazar el siguiente gráfico:

corr1

Si puede averiguar lo que estoy haciendo de manera diferente, sería útil.

(No digo esto para refutar la idea de que las correlaciones son posibles, porque lo son, y escribiré un comentario por separado sobre el tema, pero fue al escribir ese comentario que me di cuenta de que no podía reproducir su resultado usando mis herramientas habituales.)

@mdickinson pulsó en el estado calculado y se incrementó directamente en las pasar por la rutina de inicialización habitual , que tiene dos avances de paso.

Escribí un pequeño script de controlador rápido que entrelaza múltiples flujos PCG32, construidos de varias maneras, para alimentar PractRand. Utiliza master de numpy en Python 3. Cuando introduzco directamente los estados / incrementos internos adversarios, PractRand falla rápidamente. No me queda claro si podemos encontrar formas razonables de encontrar _seeds_ adversarios (que en realidad pasan por la rutina del inicializador) para lograr los estados adversarios.

Como se señaló en la publicación de mi blog que se mencionó anteriormente, las transmisiones de PCG tienen mucho en común con las de SplitMix.

Con respecto al gráfico de @mdickinson, para _ cada_ PRNG que le permite sembrar su estado completo, incluidos los criptográficos basados ​​en contador, podemos idear semillas donde tendríamos PRNG cuyas salidas se correlacionaron de alguna manera (la forma más fácil de hacerlo es hacer estados PRNG que estén a una corta distancia, pero a menudo podemos hacer otras cosas basándonos en una comprensión de cómo funcionan). Y aunque los PRNG que no permiten la siembra de estado completo pueden evitar este problema, hacerlo solo introduce uno nuevo, solo brinda acceso práctico a una pequeña fracción de sus posibles estados.

La forma correcta de pensar en las corrientes es simplemente un estado más aleatorio que debe sembrarse. El uso de valores pequeños como 1,2,3 es generalmente una mala idea para cualquier propósito de siembra de _cualquier_ PRNG (porque si todos favorecen estas semillas, sus secuencias iniciales correspondientes estarán sobrerrepresentadas).

Podemos elegir no llamarlo flujo en absoluto y simplemente llamarlo estado. Eso es lo que hizo Marsaglia en XorWow . Si observa el código, la secuencia de Weyl counter no interactúa en absoluto con el resto del estado y, como las LCG, y las variaciones en el valor inicial en realidad solo equivalen a una constante agregada.

Las transmisiones de SplitMix, PCG y XorWow son lo que podríamos llamar transmisiones "estúpidas". Constituyen una trivial reparametrización del generador. Sin embargo, esto tiene valor. Supongamos que sin corrientes, nuestro PRNG tendría una interesante repetición cercana de 42, donde 42 aparece varias veces en rápida sucesión y solo hace esto para 42 y ningún otro número. Con secuencias estúpidas de “solo un incremento” o “solo un xor”, en realidad evitaremos cablear la extraña repetición a 42; todos los números tienen una secuencia en la que se repiten de forma extraña. (Por esta razón, la solución que aplicaría para reparar los problemas de repetición cercana en Xoshiro 256 es mezclar en una secuencia de Weyl).

No soy un experto, pero por el lado de la criptografía, lo que se propone que no está disponible en:
https://cryptography.io/en/latest/ de la Autoridad Criptográfica de Python ?

Su página sobre generación de números aleatorios también menciona:

A partir de Python 3.6, la biblioteca estándar incluye el módulo de secretos , que se puede utilizar para generar números aleatorios criptográficamente seguros, con ayudantes específicos para formatos basados ​​en texto.

Supongo que tal vez agregue matrices a la generación. Tengo que preguntarme si la posible carga de mantenimiento de estar asociado con la robustez criptográfica realmente vale la pena y es apropiada en NumPy frente a comunicarse con Pyca y tal vez pensar en un generador / complemento de terceros para eso. Creo que Nathaniel mencionó anteriormente una preocupación similar.

De hecho, me parece que cosas como la posible refactorización / mejora de dtype también están diseñadas para proporcionar infraestructura API sin asumir necesariamente la carga de mantener una gran variedad de nuevas aplicaciones especializadas.

Por cierto, también hay más sobre la creación de estados PRNG correlacionados en mi respuesta a la crítica de PCG de Vigna [específicamente, esta sección ]. Algo que observé allí es que con PCG, debido a que tiene una función de distancia, puede verificar con la función de distancia para detectar semillas artificiales. En los PRNG sin una función de distancia, las personas aún pueden idear pares de siembra que son malas elecciones (especialmente si pasan por alto la API pública para la siembra), pero no existe un mecanismo proporcionado que pueda detectar incluso las artimañas más evidentes.

Por un lado, es divertido pensar si es posible tomar su PRNG favorito (o el menos favorito) e idear semillas para que haga cosas divertidas o terribles (por ejemplo, esta patología ) ...

Pero mirando el panorama general, creo que tiene sentido analizar los problemas que enfrentan los usuarios en la práctica. Qué consejo les damos, etc. La mayoría de los usuarios no se dan cuenta de que _para todos los PRNG (pasados ​​y futuros) _, una semilla de 32 bits es una idea absolutamente terrible y da como resultado un sesgo trivialmente detectable sin importar qué PRNG esté en juego. Claro, podemos ignorar eso y, en cambio, pasar nuestro tiempo preocupándonos de si alguien podría inicializar el Mersenne Twister a un estado mayormente de ceros (o el estado de todos ceros donde los LFSR no funcionan en absoluto), o si alguien podría hacerlo. inicializar Xoshiro hasta cerca del punto donde repite la misma salida siete veces en el espacio de once salidas, o idear dos flujos PCG similares, o lo que sea, pero todos estos inventos tienen básicamente una posibilidad infinitesimal (en la práctica, cero) de suceder si el generador se siembra con datos aleatorios. A pesar de lo intelectualmente interesantes y académicamente interesantes que son estas diversiones, pensar en ellas mientras se ignora el hecho de que los usuarios generalmente tienen poca idea de lo que están haciendo cuando se trata de sembrar es jugar mientras Roma arde.

Si inc=1,2,3,4 es una mala idea, ¿no sugeriría eso que debería estar documentado con mucha claridad, o quizás deberíamos tener una API ligeramente diferente? Tal vez incluso new_generator = (Bit)Generator().independent() , podemos ponerle una advertencia si el generador de bits (subyacente) no proporciona una excelente manera de lograrlo.

Además, dependiendo de qué tan mala sea la siembra de 32 bits. ¿Podemos pensar en una buena API para crear y almacenar una semilla para congelarla? Yo no sé. Tal vez incluso "cree un archivo de caché de semillas congelado si no existe".

Para PCG podría simplemente sembrar -> uint64_t [2] -> splitmix64 (seed_by_array) -> uint128 lo que aseguraría que las semillas bajas y consecutivas se esparzan.

Para PCG podría simplemente sembrar -> uint64_t [2] -> splitmix64 (seed_by_array) -> uint128 lo que aseguraría que las semillas bajas y consecutivas se esparzan.

O simplemente use cualquier buen hash de entero. (Debería ser una biyección). Hay muchos baratos y cortos. Unas pocas rondas de Multiply – XorShift está bien.

Para el punto de @mdickinson , creo que todavía quiere convencer un poco de que la dependencia de la transmisión se limita a un pequeño conjunto de configuraciones artificiales / adversas. Y si ese es el caso, entonces podemos resolverlo con las prácticas adecuadas para prevenir tales casos. Con el código actual que tenemos, hay algunos estados incorrectos en los que los usuarios pueden caer fácilmente con las API actuales. Puedo confirmar el hallazgo de David Blackman de que establecer tanto seed=1 como inc=0,1,2,... crea correlaciones. Mi controlador PractRand más reciente para flujos PCG32 intercalados se puede utilizar para demostrar esto.

❯ ./pcg_streams.py --seed 1 --inc 0 |time ./RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 12728272447693586011,
            "inc": 1
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 7009800821677620407,
            "inc": 3
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0x470537d5
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0x470537d5
length= 128 megabytes (2^27 bytes), time= 4.0 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+0,13-3,T)                  R=  +9.6  p =  2.3e-4   mildly suspicious
  ...and 116 test result(s) without anomalies

rng=RNG_stdin32, seed=0x470537d5
length= 256 megabytes (2^28 bytes), time= 8.7 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+0,13-2,T)                  R= +26.1  p =  6.3e-13    FAIL           
  ...and 123 test result(s) without anomalies

./RNG_test stdin32  8.86s user 0.11s system 93% cpu 9.621 total

Todavía no me he encontrado con una falla con una semilla aleatoria pero con los mismos incrementos cercanos. Me comunicaré contigo mañana.

Me doy cuenta de que no estamos utilizando el incremento predeterminado recomendado cuando no se especifica ninguno en el constructor. Probablemente deberíamos arreglar eso. Quizás ese sería un buen número base del cual derivamos el incremento real del ID de flujo dado en lugar de 2*inc + 1 .

Podemos intentar construir algunas herramientas para ayudar a las personas a usar las semillas de entropía predeterminadas y guardarlas. Una pregunta que tengo es si los incrementos para múltiples flujos se pueden generar simplemente o si también necesitamos muestrearlos entropía y guardarlos también. Es realmente conveniente poder codificar el "estado inicial" de una simulación como un número único que se puede copiar y pegar del correo electrónico de un colega en lugar de un archivo opaco. Con estos PRNG más pequeños con solo 128 o 256 bits de estado, puedo imprimirlo fácilmente en hexadecimal en mi archivo de registro y luego simplemente copiarlo y pegarlo en mi línea de comando cuando quiera reproducirlo. Es más grande que un entero de 32 bits, pero es manejable. Si también tengo que muestrear entropía todos mis ID de transmisión, entonces tengo que renunciar a eso y asegurarme de registrar todo en un archivo de estado en algún lugar. Eso podría excluir algunos casos de uso que hemos discutido donde queremos generar dinámicamente nuevas transmisiones. Si puedo incrementar un contador para obtener una buena ID de transmisión (tal vez derivarla a través de un hash del contador, o lo que sea), entonces solo necesito registrar la semilla inicial y no las ID de transmisión.

IIRC, el módulo de secretos llama a la fuente de entropía del sistema operativo, que puede ser bastante
es malo en algunos sistemas y no es replicable / reproducible independientemente.

El miércoles 29 de mayo de 2019 a las 3:19 p.m.Tyler Reddy [email protected]
escribió:

No soy un experto, pero en el lado de la criptografía, lo que se propone es
no disponible en:
https://cryptography.io/en/latest/ de la Autoridad Criptográfica de Python
https://github.com/pyca ?

Su página sobre generación de números aleatorios
https://cryptography.io/en/latest/random-numbers/ también menciona:

A partir de Python 3.6, la biblioteca estándar incluye los secretos
https://docs.python.org/3/library/secrets.html módulo, que puede ser
utilizado para generar números aleatorios criptográficamente seguros, con
ayudantes para formatos basados ​​en texto.

Supongo que tal vez agregue matrices a la generación. Me pregunto si el
carga de mantenimiento potencial de estar asociado con criptografía
la robustez realmente vale la pena y es apropiada en NumPy vs. say
comunicarse con la pyca y tal vez pensar en un tercero
generador / complemento para eso. Creo que Nathaniel mencionó una preocupación similar
previamente.

De hecho, me parece que cosas como el refactor potencial dtype /
Las mejoras también están diseñadas para proporcionar una infraestructura API sin
necesariamente asumiendo la carga de mantener una gran variedad de
nuevas aplicaciones especializadas.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWKXSSTX6QI7HJ65GYTPX36O3A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVDVREXWJWKNMA2HS4DFVDVDXHJWKNM2HS4DFVREXWJWKNM2
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWKGZMUB67VPCMFZYGTPX36O3ANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@tylerjereddy Son para obtener una pequeña cantidad de bits aleatorios de una fuente de entropía física que son impredecibles para un atacante (¡y para usted!). Se utilizan en criptografía para cosas como vectores de inicialización, nonces, claves, que son todos cortos. El punto de estos es que no hay forma de reproducirlos, lo que está en desacuerdo con los propósitos de simulación numérica de np.random . Esa página no está hablando de PRNG criptográficamente seguros _reproducibles_, que también son cosas que existen y _podrían_ construirse a partir de las primitivas disponibles en el paquete cryptography . En _práctica_, sin embargo, tenemos mejores implementaciones de esos algoritmos ya disponibles para nosotros en código C eficiente, al menos los que han sido formulados y probados para propósitos de simulación. @bashtage implementó algunos para este marco.

También quiero dejarle claro al equipo numpy que lo que propone @pbstark no es cualquier PRNG basado en criptografía. Más bien, quiere uno con _estado ilimitado_, que proporcionaría la propiedad matemática que está buscando.

La mayoría de los PRNG basados ​​en criptografía que se consideran comúnmente para la simulación _no_ tienen el estado ilimitado que @pbstark quiere. Por lo general, se basan en el cifrado de un contador finito. Una vez que ese contador rueda, has alcanzado el período finito. Técnicamente, su cryptorandom también está limitado a condiciones iniciales únicas de 2**(256+64) debido al estado de resumen de 256 bits de tamaño fijo y al contador de longitud de 64 bits de tamaño fijo. Eso probablemente señala el camino para implementar un PRNG verdaderamente ilimitado haciendo que el contador de longitud tenga un tamaño arbitrario, pero nunca había visto un algoritmo de este tipo publicado o probado.

Por otro lado, si solo desea un algoritmo PRNG que tenga un estado de tamaño arbitrario, solo uno que esté fijo al principio a algo por encima de lo que necesita, entonces los generadores extendidos de PCG funcionarían bien para esa tarea. Estos claramente no son CS-PRNG, pero en realidad satisfarían el deseo de @pbstark de tener enormes espacios estatales a pedido. No obstante, no recomiendo que los incluyamos en numpy.

Hay otras propiedades que tienen los CS-PRNG estándar y delimitados que quizás deseemos, pero no son un valor predeterminado obvio, en mi opinión.

El espacio de estado de Cryptorandom no es el hash de 256 bits: no tiene límites. los
El estado inicial es una cadena de longitud arbitraria, y cada actualización agrega un cero
al estado actual. Incrementar un contador de enteros ilimitados
lograr lo mismo. Inicialmente implementamos eso, pero cambiamos a
añadir en lugar de incrementar, porque permite una actualización más eficiente para
el resumen que hash de cada estado desde cero (aceleración notable).

El miércoles 29 de mayo de 2019 a las 7:26 p.m. Robert Kern [email protected]
escribió:

@tylerjereddy https://github.com/tylerjereddy Esos son para obtener un
pequeña cantidad de bits aleatorios de una fuente de entropía física que son
impredecible para un atacante (¡y para usted!). Se utilizan en criptografía para
cosas como vectores de inicialización, nonces, claves, que son todas cortas. los
Todo esto es que no hay forma de reproducirlos, que es
probabilidades con los propósitos de simulación numérica de np.random. Esa pagina es
sin hablar de PRNG reproducibles criptográficamente seguros, que
también son cosas que existen y podrían construirse a partir de las primitivas
disponible en el paquete de criptografía. En la práctica , sin embargo, tenemos
mejores implementaciones de los algoritmos que ya tenemos disponibles en
código C eficiente, al menos los que han sido formulados y probados
con fines de simulación. @bashtage https://github.com/bashtage implementado
algunos https://github.com/numpy/numpy/issues/13635#issuecomment-496287650
para este marco.

También quiero dejarle claro al equipo numpy que lo que @pbstark
https://github.com/pbstark está proponiendo que no sea cualquier basado en cripto
PRNG. Más bien, quiere uno con un estado ilimitado , que proporcionaría
la propiedad matemática que está buscando.

La mayoría de los PRNG basados ​​en criptografía que se consideran comúnmente para simulación
no tienen el estado ilimitado que @pbstark
https://github.com/pbstark quiere. Por lo general, se basan en
encriptar un contador finito. Una vez que ese contador rueda, ha
período finito. Técnicamente, su criptorandom
https://statlab.github.io/cryptorandom/ también está limitado a 2 ** (256 + 64)
condiciones iniciales únicas debido al estado de resumen de 256 bits de tamaño fijo y
contador de longitud de 64 bits de tamaño fijo. Eso probablemente señala el camino a
implementar un PRNG verdaderamente ilimitado haciendo que el contador de longitud
tamaño arbitrario, pero nunca he visto un algoritmo de este tipo publicado o
probado.

Por otro lado, si solo desea un algoritmo PRNG que tenga un
Estado de tamaño arbitrario, solo uno que se fija al principio para
algo por encima de lo que necesita, entonces los generadores extendidos de PCG
http://www.pcg-random.org/party-tricks.html funcionaría bien para eso
tarea. Estos claramente no son CS-PRNG, pero en realidad satisfarían
@pbstark https://github.com/pbstark El deseo de tener enormes espacios estatales
Bajo demanda. No obstante, no recomiendo que los incluyamos en numpy.

Hay otras propiedades que tienen los CS-PRNG estándar y acotados que
es posible que queramos, pero no son una opción predeterminada obvia, en mi opinión.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWLIJD3UCVY3NXCLPKDPX43ONANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Me temo que no es así como funciona la actualización en línea de SHA-256. El estado que mantiene es solo el resumen de 256 bits y el contador de longitud de 64 bits de tamaño fijo que agrega cuando calcula la actualización. No contiene todo el texto. Comprimir hashes. Así es como puede realizar la actualización eficiente en cada byte. Básicamente, hay muchas inicializaciones / historias pasadas que se asignan al mismo estado interno SHA-256, que es finito. Si bien los ciclos son ciertamente largos, tal vez más de 2**(256+64) , ciertamente existen. Y en cualquier caso, solo tiene menos de 2**(256+64) posibles condiciones iniciales (para cada longitud de texto de 0 a 2**64-1 , puede tener como máximo 2**256 estados hash internos; una vez que la longitud del texto es superior a 32 bytes, debe haber colisiones a la casillero). Simplemente no hay más bits en la estructura de datos.

Muchas gracias; entendido. Lo expresaría de otra manera: el estado
El espacio es ilimitado, pero (por casillero) muchos estados iniciales distintos deben
producir secuencias de salida indistinguibles.

El miércoles 29 de mayo de 2019 a las 8:21 p. M. Robert Kern [email protected]
escribió:

Me temo que no es así como funciona la actualización en línea de SHA-256. El estado
que mantiene es solo el resumen de 256 bits y el tamaño fijo de 64 bits
contador de longitud que agrega cuando calcula la actualización. No aguanta
todo el texto. Comprimir hashes. Así es como puede hacer lo eficiente
actualizar en cada byte. Fundamentalmente, hay muchas inicializaciones / pasadas
historias que se asignan al mismo estado interno SHA-256, que es finito.
Si bien los ciclos son ciertamente largos, tal vez más de 2 (256 + 64),ciertamente existen.
posibles condiciones iniciales (para cada longitud de texto de 0 a 2 64-1, puedetener como máximo 2 256 estados hash internos; una vez que la longitud del texto supera los 32
bytes, debe haber colisiones a la casillero). Simplemente no hay ninguno
más bits en la estructura de datos.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWO6HRJZHTVBF2TLK3LPX5B3TA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVLOXWG43 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AANFDWM56HCQRZXDO3BAQHDPX5B3TANCNFSM4HPX3CHA
.

-
Philip B. Stark | Decano Asociado, Ciencias Físicas y Matemáticas |
Profesor, Departamento de Estadística |
Universidad de California
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

También es el caso de que solo hay 2**(256+64) estados por los que posiblemente pueda pasar. Dado que la actualización toma la misma forma cada vez, eventualmente llega a un estado que ha visto antes y entra en un ciclo de período desconocido (para mí) pero finito. Ya sea el número finito de estados iniciales o un período finito, cryptorandom tiene ambos, y creo que son más pequeños incluso que MT19937 .

No es que crea que eso es un problema con cryptorandom , per se. No estoy convencido de que tener un conjunto ilimitado de estados iniciales o un período ilimitado sea algo que sea realmente necesario en un sentido práctico.

Todavía no me he encontrado con una falla con una semilla aleatoria pero con los mismos incrementos cercanos. Me comunicaré contigo mañana.

Sigue siendo fuerte en 512GiB:

❯ ./pcg_streams.py -i 0 |time ./RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 10843219355420032665,
            "inc": 1
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 5124747729404067061,
            "inc": 3
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0xb83f7253
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0xb83f7253
length= 128 megabytes (2^27 bytes), time= 4.0 seconds
  no anomalies in 117 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 256 megabytes (2^28 bytes), time= 8.6 seconds
  no anomalies in 124 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 512 megabytes (2^29 bytes), time= 16.9 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+2,13-2,T)                  R=  -8.0  p =1-2.1e-4   mildly suspicious
  ...and 131 test result(s) without anomalies

rng=RNG_stdin32, seed=0xb83f7253
length= 1 gigabyte (2^30 bytes), time= 33.8 seconds
  no anomalies in 141 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 2 gigabytes (2^31 bytes), time= 65.7 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+2,13-1,T)                  R=  -7.8  p =1-3.8e-4   unusual          
  ...and 147 test result(s) without anomalies

rng=RNG_stdin32, seed=0xb83f7253
length= 4 gigabytes (2^32 bytes), time= 136 seconds
  no anomalies in 156 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 8 gigabytes (2^33 bytes), time= 270 seconds
  no anomalies in 165 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 16 gigabytes (2^34 bytes), time= 516 seconds
  no anomalies in 172 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 32 gigabytes (2^35 bytes), time= 1000 seconds
  no anomalies in 180 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 64 gigabytes (2^36 bytes), time= 2036 seconds
  no anomalies in 189 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 128 gigabytes (2^37 bytes), time= 4064 seconds
  no anomalies in 196 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 256 gigabytes (2^38 bytes), time= 8561 seconds
  no anomalies in 204 test result(s)

rng=RNG_stdin32, seed=0xb83f7253
length= 512 gigabytes (2^39 bytes), time= 19249 seconds
  no anomalies in 213 test result(s)

Ah, si hacemos más de 2 transmisiones con incrementos secuenciales, vemos fallas rápidamente. Tendremos que ver cómo derivar el incremento real de la entrada del usuario con algún tipo de biyección para distribuirlo por el espacio.

❯ ./pcg_streams.py -n 3  | time ./build/RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 18394490676042343370,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 12676019050026377766,
            "inc": 2891336455
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 6957547424010412162,
            "inc": 2891336457
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0x4a9d21d1
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0x4a9d21d1
length= 128 megabytes (2^27 bytes), time= 3.2 seconds
  Test Name                         Raw       Processed     Evaluation
  DC6-9x1Bytes-1                    R= +19.4  p =  1.6e-11    FAIL           
  [Low8/32]DC6-9x1Bytes-1           R= +13.2  p =  4.6e-8    VERY SUSPICIOUS 
  ...and 115 test result(s) without anomalies

@imneme

Si puede averiguar lo que estoy haciendo de manera diferente, sería útil.

Creo que necesitas reemplazar la línea pcg64 rng2(-seed,-stream); en tu código con pcg64 rng2(-seed,-1-stream); , para permitir la transformación increment = 2 * stream + 1 . La negación del incremento corresponde a la negación bit a bit del índice de flujo. Si hago ese cambio y ejecuto su código, veo algo muy parecido a mi diagrama anterior. (Y confirmo que si no hago ese cambio, todo se ve bien visualmente).

@imneme

La forma correcta de pensar en las corrientes es simplemente un estado más aleatorio que debe sembrarse.

Convenido. Creo que eso da una imagen muy clara para los LCG: para un LCG de 64 bits con un multiplicador fijo bien elegido a , tenemos un espacio de estado de tamaño 2^127 , que consta de todos los pares (x, c) de enteros mod 2 ^ 64, donde c es el incremento impar. La función de actualización de estado es next : (x, c) ↦ (ax+c, c) , dividiendo el espacio de estado en 2^63 ciclos disjuntos de longitud 2^64 cada uno. La siembra solo implica elegir un punto de partida en este espacio de estado.

Entonces hay una acción de grupo obvia que facilita el análisis y aclara las relaciones entre las diferentes corrientes: el grupo de transformaciones afines 1-d invertibles en Z / 2^64Z tiene un orden exactamente 2^127 , y actúa de manera transitiva (y también fielmente) en el espacio de estados: la transformación afín y ↦ ey + f mapea el par (x, c) a (ex + f, ec + (1-a)f) . Esa acción de grupo conmuta con la función next , por lo que el elemento de grupo único que transforma un punto (x, c) en el espacio de estado en otro, (x2, c2) , también mapea la secuencia generada por (x, c) a la secuencia generada por (x2, c2) .

tl; dr: para un multiplicador fijo, dos secuencias LCG cualesquiera con el mismo multiplicador (ya sea usando el mismo incremento, como en el caso de anticipación, o incrementos diferentes) están relacionadas por una transformación afín. En los casos desafortunados que queremos evitar, esa transformación afín es algo horriblemente simple, como agregar 2 o multiplicar por -1 . En el caso general, esperamos que la transformación afín sea lo suficientemente complicada como para que las pruebas estadísticas estándar no puedan detectar la relación entre las dos corrientes.

@mdickinson cubre muy bien la situación. Las permutaciones de PCG cambiarán un poco las cosas con respecto al caso de LCG, pero no mucho. El objetivo de las permutaciones de PCG es que podemos elegir cuánto codificar hacer. Debido a que los LCG truncados de 128 bits ya pasan BigCrush, cuando elegí una permutación para pcg64 , elegí una cantidad modesta de codificación para ese tamaño de LCG (XSL RR). En contraste, los LCG de 64 bits fallan rápidamente en varias pruebas estadísticas, por lo que pcg32 usa un poco más de codificación, pero aún no es la permutación más fuerte del documento PCG. Como mencioné en el hilo de solicitud de extracción del hilo, comencé a inclinarme hacia una permutación PCG más fuerte (RXS M) para el caso de uso pcg32 . Todavía no es el predeterminado, debe solicitar esa versión explícitamente, pero hay una buena posibilidad de que cambie el predeterminado cuando haga un cambio de versión principal para PCG. (RXS M es la mitad alta de RXS M XS, que Vigna ha probado ampliamente en este tamaño y también la permutación que le gusta a David Blackman).

Podemos visualizar la diferencia: una versión actualizada del programa de prueba near-streams usa ambos esquemas para pcg32 (XSH RR y RCS M [y el LCG subyacente sin procesar, también]):

#include "pcg_random.hpp"
#include <iostream>
#include <random>

// Create a "PCG" variant with a trivial output function, just truncation

template <typename xtype, typename itype>
struct truncate_only_mixin {
    static xtype output(itype internal)
    {
        constexpr size_t bits = sizeof(itype) * 8;
        return internal >> (bits/32);
    }
};

using lcg32 = pcg_detail::setseq_base<uint32_t, uint64_t, truncate_only_mixin>;

int main() {
    std::random_device rdev;
    uint64_t seed = 0;
    uint64_t stream = 0;
    for (int i = 0; i < 2; ++i) {
        seed   <<= 32;           
        seed   |= rdev();
        stream <<= 32;           
        stream |= rdev();
    }
    lcg32 rng1(seed,stream);
    lcg32 rng2(-seed,-1-stream);
    // pcg32 rng1(seed,stream);
    // pcg32 rng2(-seed,-1-stream);
    // pcg_engines::setseq_rxs_m_64_32 rng1(seed,stream);
    // pcg_engines::setseq_rxs_m_64_32 rng2(-seed,-1-stream);
    std::cerr << "RNG1: " << rng1 << "\n";
    std::cerr << "RNG2: " << rng2 << "\n";
    std::cout.precision(17);
    for (int i = 0; i < 10000; ++i) {
        std::cout << rng1()/4294967296.0 << "\t";
        std::cout << rng2()/4294967296.0 << "\n";
    }
}

Antes de comenzar, veamos el gráfico que dibujó @mdickinson, pero solo para un LCG sin permutación, solo truncamiento:

corr-truncated-lcg

Tenga en cuenta que esto es para LCG patológica con estados que se correlacionan. Si, en cambio, hubiéramos elegido dos LCG con constantes aditivas elegidas al azar (pero el mismo valor inicial), se vería así:

corr-truncated-lcg-good

Pasando a las funciones de salida de PCG, si usamos XSH RR en el caso patológico, se ve así: es una gran mejora en el gráfico anterior, pero claramente no oculta por completo lo horrible:

corr-pcg32-current

y este es RXS M con el mismo par LCG subyacente (mal correlacionado):

corr-pcg32-future

Pero esto es solo algo que estoy reflexionando por pcg32 . La penalización en el rendimiento es pequeña, y pcg32 es lo suficientemente pequeña como para imaginar que algún usuario pesado esté preocupado por crear una tonelada de generadores pcg32 semilla aleatoria y pedir una tonelada de números tener una probabilidad no infinitesimal de correlaciones. Sin embargo, estoy, francamente, en dos mentes al respecto, porque este mítico usuario avanzado estaría usando pcg32 en primer lugar.

Una razón por la que no me molesta demasiado hacer que las transmisiones de pcg64 más independientes es que no estoy seguro de ver un caso de uso en el que sería sensato mantener todos los demás estados iguales y cambiar la transmisión a uno diferente (por ejemplo, a un valor aleatorio, y mucho menos a uno cercano). Para casi todos los PRNG, la forma correcta de hacer un segundo es inicializarlo con entropía nueva.

En conclusión, para NumPy, creo que puede tener más sentido considerar que PCG64 quiere dos 256 bits de estado (técnicamente son 255 ya que se ignora el bit alto de la secuencia) y llamarlo hecho. Eso evitará problemas relacionados con la API también porque será una característica menos que la gente tendrá en un BitGenerator y no en otro.

(Pero es posible que desee cambiar la variante PCG de 32 bits a la RXS M. Para la fuente C, necesita una versión reciente, ya que solía no molestarme en proporcionar RXS M explícitamente en el código C, solo haciéndolo disponible en C ++ encarnación.)

[¡Lo siento si esto es más de lo que alguna vez quisiste saber! Bueno, no _eso_ lo siento. ;-)]

Una razón por la que no me molesta demasiado hacer que las transmisiones de pcg64 más independientes es que no estoy seguro de ver un caso de uso en el que sería sensato mantener todos los demás estados iguales y cambiar la transmisión a uno diferente (por ejemplo, a un valor aleatorio, y mucho menos a uno cercano). Para casi todos los PRNG, la forma correcta de hacer un segundo es inicializarlo con entropía nueva.

He descrito el caso de uso anteriormente. Hay fuertes razones de UX para escribir un programa estocástico que acepte una única entrada "semilla" corta (es decir, algo del tamaño que pueden copiar y pegar de un correo electrónico a una línea de comandos) que luego hace que la salida del programa sea determinista. @stevenjkern me señaló en una conversación fuera de línea que ese tipo de interacción era esencial para trabajar con las agencias reguladoras que tenían que validar su software. Si tuvieras que usar el archivo _output_ de una ejecución de un programa para replicar el resultado, eso parece un poco sospechoso en tales circunstancias. El regulador tendría que hacer un análisis profundo del código (que puede que no esté disponible para ellos) para asegurarse de que la información en el archivo sea realmente kosher.

Ahora tenemos buenas herramientas en Python para acelerar N procesos paralelos dinámicamente para hacer una parte del trabajo, luego recopilar los resultados y avanzar en el proceso principal (luego girar M procesos más tarde, etc.). A diferencia de los esquemas más antiguos y menos flexibles como MPI, no solo activamos N procesos fijos al principio. En esos casos, pude ver la siembra de entropía de cada uno de los N PRNG y guardarlos en un archivo porque solo hay un lugar en el programa que hace eso. Al menos desde una perspectiva de programación, eso no es demasiado difícil. Ahora tenemos herramientas mucho más flexibles para el paralelismo. Sembrar los PRNG es ahora el cuello de botella que nos impide utilizar esa flexibilidad en los programas estocásticos. Ya no hay un solo punto de responsabilidad donde podamos poner la contabilidad basada en archivos.

La necesidad de derivar N streams de forma reproducible es lo suficientemente fuerte como para que la gente haga cosas raras para conseguirlo con nuestro algoritmo MT actual. Tuve que derribar un montón de esquemas arriesgados, y esperaba que las transmisiones de PCG nos ayudaran a llegar allí.

¿Qué piensas sobre el uso de un buen hash biyectivo sobre 2**63 / 2**127 para derivar incrementos de una secuencia de contador 0,1,2,3, ... mientras se mantiene el mismo estado? ¿Prevés problemas con eso? ¿Qué opinas sobre la combinación del incremento hash seguido de un gran salto para mover el estado a una parte lejana del nuevo ciclo? Tal vez podamos trasladar esta sub-discusión al correo electrónico u otro tema e informar.

@rkern , puede haber algo agradable en poder dar semillas muy cortas a los PRNG, pero (independientemente del PRNG), es una idea terrible. Si proporciona _k_ bits de entrada inicial y luego exige _k_ bits (ya sea inmediatamente o omita _j_ bits primero [para algunos _j_ arbitrarios] y luego lee _k_ bits), aunque los 2 ^ _k_ enteros son entradas válidas, no los 2 Se pueden observar las salidas de ^ _k_ (porque no se garantiza que la función de bits de entrada a bits de salida sea [y realmente no puede ser] una biyección). La distribución esperada es un binomio (2 ^ k, 2 ^ -k), que podemos aproximar como una distribución de Poisson y, por lo tanto, no se observarán los valores de 2 ^ k / e. Esto es cierto sin importar lo que sea PRNG. Todo lo que podemos hacer es hacer que _k_ sea lo suficientemente grande como para que sea totalmente impracticable averiguar qué falta.

El problema se agrava cuando todos, por ejemplo, usan el mismo PRNG (por ejemplo, el Mersenne Twister) y recogen semillas del mismo conjunto pequeño (por ejemplo, números menores de 10000), porque en lugar de un sesgo arbitrario particular por programa, es un sesgo _para todo el mundo que hace esto_. Por ejemplo, supongamos que elige una semilla de cuatro dígitos y luego toma una cantidad razonable de números del Mersenne Twister (digamos, menos un millón). En esa situación, puedo asegurarles que el desafortunado número 13 _nunca aparecerá_ como cualquiera de los 10 mil millones de salidas (de hecho, aproximadamente el 10% de los enteros de 32 bits estarán ausentes), y el número 123580738 está sobrerrepresentado por un factor de 16. Esto es exactamente lo que esperaríamos para una muestra aleatoria de diez mil millones de enteros de 32 bits, pero es un problema real _si todos usan la misma muestra_. Tendríamos un problema exactamente análogo si todos escogieran semillas de nueve dígitos y solo sacaran 10000 números.

El hecho de que mucha gente quiera hacer algo no lo convierte en una buena idea. (Eso no significa que esté bien decirle simplemente a las personas que lo están haciendo mal o que quieren algo incorrecto. Tienes que averiguar qué es lo que realmente necesitan (por ejemplo, resultados reproducibles de un argumento de línea de comandos corto, posiblemente el Lo correcto es permitir la siembra a partir de un UUID y un pequeño entero; en esta publicación de blog se pueden encontrar algunas ideas sobre cómo mezclar bien estas cosas para hacer datos de semillas y se abrieron camino en randutils ).

(Aquí está el código para jugar, ya que es bastante corto ...)

// mtbias.cpp -- warning, uses 4GB of RAM, runs for a few minutes
// note: this is *not* showing a problem with the Mersenne Twister per se, it is
// showing a problem with simplistic seeding

#include <vector>
#include <iostream>
#include <random>
#include <cstdint>

int main() {
    std::vector<uint8_t> counts(size_t(std::mt19937::max()) + 1);
    for (size_t seed=0; seed < 10000; ++seed) {
        std::mt19937 rng(seed);
        for (uint i = 0; i < 1000000; ++i) {
            ++counts[rng()];
        }
    }
    size_t shown = 0;
    std::cout << "Never occurring: ";
    for (size_t i = 0; i <= std::mt19937::max(); ++i) {
        if (counts[i] == 0) {
            std::cout << i << ", ";
            if (++shown >= 20) {
                std::cout << "...";
                break;
            }
        }
    }
    std::cout << "\nMost overrepresented: ";
    size_t highrep_count = 0;
    size_t highrep_n = 0;
    for (size_t i = 0; i <= std::mt19937::max(); ++i) {
        if (counts[i] > highrep_count) {
            highrep_n = i;
            highrep_count = counts[i];
        }
    }
    std::cout << highrep_n << " -- repeated " << highrep_count << " times\n";
}

Como dije antes, creo que las semillas de 128 bits son lo suficientemente cortas para ese propósito, y puedo construir las herramientas para ayudar a las personas a escribir programas que hagan lo correcto. Es decir, muestrelos por entropía de forma predeterminada, imprimiéndolos o registrándolos, y luego permitiendo que se pasen más tarde. Su recomendación de generar un UUID para cada programa y mezclar una semilla proporcionada por el usuario posiblemente más pequeña por ejecución también es buena.

Supongamos que puedo hacer que la gente use buenas semillas de 128 bits para la parte estatal de PCG64 , de una forma u otra. ¿Tiene algún comentario sobre la derivación de transmisiones del mismo estado? No estoy buscando sacar más números, en general, de los que obtendríamos de una sola secuencia PCG64 . Solo quiero poder dibujar estos números en diferentes procesos sin coordinación en cada sorteo. El uso de un hash multiply-xorshift ad hoc de 63 bits parece funcionar bastante bien hasta ahora (estoy en 32 GiB en este momento), para 8192 transmisiones intercaladas.

@imneme

Puede resultar útil definir qué entendemos por abreviatura.

Creo que @rkern escribió "algo sobre el tamaño que pueden copiar y pegar de un correo electrónico a una línea de comandos". Puedo representar números bastante grandes en unos pocos caracteres como 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff.

Solo quiero poder dibujar estos números en diferentes procesos sin coordinación en cada sorteo. El uso de un hash multiply-xorshift ad hoc de 63 bits parece funcionar bastante bien hasta ahora (estoy en 32 GiB en este momento), para 8192 transmisiones intercaladas.

¿Ha intentado intercalar secuencias de n usando una semilla de calidad única avanzando el estado en un número lo suficientemente grande (digamos 2 ** 64, que es lo que usa PCG64.jumped )? Esta parece ser la forma más sencilla de coordinar de manera flexible flujos de n grandes en un clúster usando algo como PCG64(seed).jumped(node_id)

donde node_id es 0,1,2, ...

¿Existe un PRNG que sea realmente bueno para producir flujos independientes simples utilizando algo como un índice? Creo que un MLFG puede, pero no me gustó esto ya que era un generador de 63 bits.

@bashtage , esa realmente no es la forma correcta de hacerlo. La forma correcta es tomar una semilla, si desea agregar un número entero pequeño, use una función hash para hacerlo. Como se mencionó anteriormente, previamente (independientemente de PCG) escribí una función de mezcla seria [editar: arreglar enlace a la publicación correcta] para mezclar varios tipos de entropía grandes y pequeños. No tienes que usar el mío, pero te recomiendo que hagas algo en ese sentido.

Idealmente, desea un mecanismo que no sea específico de PCG. Es posible que PCG no sea su opción predeterminada, e incluso si lo fuera, desea que la gente haga cosas similares con todos los generadores. No creo que deba desear un esquema para hacer varios PRNG independientes que dependan de las transmisiones o el avance.

(Vaya, he vinculado a la publicación de blog incorrecta; he editado el mensaje anterior, pero en caso de que esté leyendo por correo electrónico, quise vincular a esta publicación de blog)

@imneme En este momento, todos los generadores que tenemos admiten un salto (algunos de los cuales son realmente llamadas de tipo avanzado). No tengo ninguna duda de que la siembra cuidadosa es una buena idea, sospecho que muchos usuarios se verán tentados a usar la llamada PRNG.jumped() . ¿Es esto algo que debería disuadirse?

En cuanto a la siembra, todos los generadores de MT hacen uso de las rutinas de inicio del autor, PCG usa la suya y el resto, algo así

seed = np.array(required_size, dtype=np.uint64)
last = 0
for i in range(len(user_seed))
    if i < len(user_seed)
        last = seed[i] = splitmix64(last ^ user_seed[i])
    else:
        last = seed[i] = splitmix64(last)

Imagino que esto podría mejorarse.

¿Es esto algo que debería disuadirse?

No había visto jumped . Es bastante horrible para la LCG subyacente.

Supongamos que tenemos un multiplicador, M, de 0x96704a6bb5d2c4fb3aa645df0540268d . Si calculamos M ^ (2 ^ 64), obtenemos 0x6147671fb92252440000000000000001 que es un multiplicador de LCG terrible. Por lo tanto, si tomara cada elemento 2 ^ 64 de un LCG de 128 bits, sería terrible (los bits de orden inferior son solo un contador). Las funciones de permutación estándar de PCG están diseñadas para codificar la salida normal de una LCG, no para codificar contadores.

PCG64 se prueba actualmente hasta medio petabyte con Practrand y un análisis más detallado muestra que puede leer muchos petabytes sin problemas relacionados con las potencias de dos. Por desgracia, si avanza para saltar hacia adelante enormes poderes exactos de dos, las permutaciones habituales (algo modestas) de PCG no pueden compensar suficientemente la secuencia patológica de omitir las enormes distancias de LCG subyacentes como esta. Puede aumentar la apuesta de la fuerza de permutación para solucionar esto, y de hecho, tanto I como Vigna compararon de forma independiente las permutaciones estándar de PCG con las funciones de hash de enteros disponibles en el mercado que probablemente lo harían (después de todo, son la base de SplitMix, que es solo un contador). Cuando lo miré en 2014 con Fast Hash, la velocidad no parecía tan buena, pero cuando Vigna lo hizo más recientemente con murmurhash, afirmó que el rendimiento superó al PCG estándar (!).

Si realmente desea tener un salto de 2 ^ 64, creo que debe cambiar a una permutación de función de salida más fuerte (que, como hemos visto, se puede hacer a bajo costo). Pero si cree que eso hace que ya no sea realmente PCG “estándar” y desea mantener la permutación de salida habitual, entonces jumped() probablemente deba desaparecer.

(Por cierto, el salto adelante patológico también se aplica a otros PRNG. Se sabe que SplitMix tiene algunos incrementos malos y es razonable suponer que, aunque el incremento habitual (también conocido como "gamma") de 0xbd24b73a95fb84d9 está bien, avanzando 2 ^ 32 le dará un incremento de 0x95fb84d900000000, que no es tan bueno. Para LFSR, el salto adelante malo probablemente no sea una potencia de dos, pero estoy bastante seguro de que habrá saltos donde la matriz subyacente termina patológicamente escaso.)

Puedo confirmar, al menos con PCG32 , 4 secuencias intercaladas usando .jumped() fallan muy rápidamente.

❯ ./pcg_streams.py --jumped -n 4 | time ./RNG_test stdin32
[
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 10149010587776656704,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 1158608670957446464,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 10614950827847787840,
            "inc": 2891336453
        }
    },
    {
        "bit_generator": "PCG32",
        "state": {
            "state": 1624548911028577600,
            "inc": 2891336453
        }
    }
]
RNG_test using PractRand version 0.93
RNG = RNG_stdin32, seed = 0xeedd49a8
test set = normal, folding = standard (32 bit)

rng=RNG_stdin32, seed=0xeedd49a8
length= 128 megabytes (2^27 bytes), time= 2.1 seconds
  Test Name                         Raw       Processed     Evaluation
  BCFN(2+0,13-3,T)                  R= +58.7  p =  1.3e-27    FAIL !!!       
  BCFN(2+1,13-3,T)                  R= +48.0  p =  1.5e-22    FAIL !!        
  BCFN(2+2,13-3,T)                  R= +16.0  p =  2.3e-7   very suspicious  
  DC6-9x1Bytes-1                    R= +53.5  p =  1.8e-32    FAIL !!!       
  [Low8/32]DC6-9x1Bytes-1           R= +27.4  p =  1.1e-17    FAIL !         
  ...and 112 test result(s) without anomalies

Idealmente, desea un mecanismo que no sea específico de PCG. Es posible que PCG no sea su opción predeterminada, e incluso si lo fuera, desea que la gente haga cosas similares con todos los generadores.

Bueno, eso es lo que estamos tratando de decidir aquí. :-) Estábamos razonablemente contentos con simplemente exponer las características que proporcionaba cada PRNG, asumiendo que las propiedades expuestas por cada algoritmo están bien estudiadas. También estamos razonablemente contentos con decir "aquí está el PRNG predeterminado que recomendamos; tiene un montón de características que son útiles; los demás pueden no tenerlas".

La noción de usar un hash para derivar un nuevo estado a partir de un estado dado y un ID de flujo, para cualquier algoritmo, es interesante. ¿Sabes lo bien estudiado que está? Parece un problema de investigación verificar que funciona bien para todos los algoritmos. Dudaría en afirmar que "este es el procedimiento general para derivar flujos independientes para todos nuestros PRNG". Estoy más contento con "aquí hay una API común para derivar flujos independientes; cada PRNG lo implementa de la manera que sea apropiada para el algoritmo y puede que no lo implemente si el algoritmo no lo admite bien".

Por otro lado, si solo se trata de asignar suficientes ciclos de CPU para probar los flujos intercalados para cada BitGenerator fuera de N GiB en PractRand, eso no es demasiado oneroso.

La noción de usar un hash para derivar un nuevo estado a partir de un estado dado y un ID de flujo, para cualquier algoritmo, es interesante. ¿Sabes lo bien estudiado que está? Parece un problema de investigación verificar que funciona bien para todos los algoritmos. Dudaría en afirmar que "este es el procedimiento general para derivar flujos independientes para todos nuestros PRNG". Estoy más contento con "aquí hay una API común para derivar flujos independientes; cada PRNG lo implementa de la manera que sea apropiada para el algoritmo y puede que no lo implemente si el algoritmo no lo admite bien".

No sé si se puede llamar "un problema de investigación" (y por lo tanto "bien estudiado") exactamente, pero C ++ 11 (que se trataba principalmente de utilizar técnicas probadas y verdaderas establecidas desde hace mucho tiempo) proporciona el concepto _SeedSequence_ (y la implementación específica std::seed_seq ) cuyo trabajo es proporcionar datos de siembra a PRNG completamente arbitrarios.

En general, casi todos los PRNG esperan ser inicializados / sembrados con bits aleatorios. No hay nada especialmente mágico en los bits aleatorios que salen de (digamos) random.org y los bits aleatorios que salen de algo más algorítmico (CS PRNG, función hash, etc.).

Es bastante sencillo pensar en una colección de PRNG del mismo esquema, todos sembrados con sus propios bits aleatorios. Puede pensar en lo que estamos haciendo como puntos de selección (o en realidad intervalos hasta una cierta longitud máxima correspondiente a la cantidad de números aleatorios que esperamos pedir plausiblemente, por ejemplo, 2 ^ 56) en una línea (por ejemplo, un línea con 2 ^ 255 puntos). Podemos calcular la probabilidad de que si pedimos _n_ intervalos, uno se superponga con otro. Es una probabilidad bastante básica: no estoy seguro de que se pueda publicar un artículo al respecto porque (según tengo entendido) a nadie le entusiasman los artículos que contienen matemáticas elementales. (¡ @lemire podría no estar de acuerdo!)

[Yo diría que lo que generalmente no deberías hacer es sembrar un PRNG con bits aleatorios que salen de sí mismo. Eso me parece demasiado incestuoso.]

Bien, para mí está claro que usar algo así como una _SeedSequence_ bien diseñada sería una forma de tomar una semilla inicial arbitraria y dibujar múltiples puntos de partida en el ciclo de nuestro algoritmo que no deberían superponerse. Y si esa es la única forma real de obtener transmisiones independientes, que así sea. Será una cuestión de diseño de API para que sea conveniente.

Lo que tengo menos claro es qué tan seguro es tomar el estado actual de un PRNG inicializado, mezcla de hash en la ID de transmisión para saltar a un nuevo estado en el ciclo, que es lo que pensé que estabas sugiriendo (y más tarde se me ocurrió que podría haberme equivocado en eso). Estar bien separados en el ciclo no es el único factor, como muestra la falla de jumped() . jumped() también asegura que se le enviará a una parte lejana de la secuencia que no se superpondrá; es solo una parte que puede correlacionarse muy fuertemente con la parte inicial si el salto no está bien elegido. Puede ser necesario conocer los aspectos internos de cada algoritmo para saber qué es y qué no es un buen salto. Obviamente, no lo hicimos para el caso de PCG.

Básicamente, si pensamos en los PRNG como funciones de transición y funciones de salida, esta new_state = seed_seq(old_state||streamID) es solo otra función de transición que estamos introduciendo en un paso. Tenemos que estar seguros de que las operaciones involucradas en ese seed_seq sean lo suficientemente diferentes de las funciones de transición en cada algoritmo PRNG (o sus inversas) y tal vez tengamos que estar seguros de otras cosas. No quisiera usar algo construido a partir de, digamos, wyhash para inicializar wyrand . Como dice, no desea utilizar el PRNG en sí mismo para proporcionar los bits por sí mismo. Por eso creo que se necesita un poco de estudio para asegurar eso para todos nuestros PRNG (estudio que esperaba no tener que hacer yo mismo).

Por otro lado, new_state = seed_seq(old_state||streamID) probablemente no sea peor en este sentido que el uso previsto de _SeedSequence_ para múltiples flujos: extraer dos estados en secuencia. Si es así, estaría bien descansando en la experiencia de C ++, tal vez con su implementación, y simplemente haciendo algunas pruebas empíricas con PractRand para todos nuestros algoritmos para mostrar que no están peor que sus contrapartes de flujo único.

Sería realmente bueno que el salto de hash funcionara porque eso abre algunos casos de uso para generar PRNG sin coordinación. El uso de ID de transmisión requiere cierta comunicación o asignación previa. dask ha solicitado algo como esto en el pasado.

Si hay buenas alternativas que solo se basan en una buena siembra que podemos hacer conveniente para hacer lo correcto, entonces probablemente deberíamos eliminar los flujos configurables como criterio para la selección de los predeterminados. Solo queremos un algoritmo predeterminado que tenga un espacio de estado suficientemente grande.

Dicho todo esto, parece que el uso de un hash de 63 bits para derivar el incremento PCG32 de las ID de secuencia secuenciales ( range(N) ) parece funcionar. 8192 flujos intercalados transfieren PractRand a 2 TiB. Si exponemos los ID de flujo para los generadores de PCG, es posible que deseemos usar esta técnica para derivar los incrementos, incluso si sugerimos que las personas usen otros medios para obtener flujos independientes de manera reproducible.

Lo que tengo menos claro es qué tan seguro es tomar el estado actual de un PRNG inicializado, mezcla de hash en la ID de transmisión para saltar a un nuevo estado en el ciclo, que es lo que pensé que estabas sugiriendo (y más tarde se me ocurrió que podría haberme equivocado en eso).

Probablemente me expresé de manera ambigua, pero no , nunca tuve la intención de sugerir que el estado actual del PRNG debería usarse en la auto-resiembra.

Pero, FWIW, SplitMix hace esto, es lo que hace la operación split() . Y no me gusta que lo haga.

Esto puede ser demasiada información, pero compartiré un poco acerca de por qué estaba horrorizado (quizás más horrorizado de lo que debería estar) por la función split() de SplitMix. Como nota histórica, SplitMix y PCG se diseñaron de forma independiente aproximadamente al mismo tiempo (SplitMix se publicó el 20 de octubre de 2014, mientras que pcg-random.org puso en marcha en agosto de 2014 y se vinculó al documento de PCG el 5 de septiembre de 2014 ). Existen algunos paralelismos entre PCG y SplitMix (y varios otros PRNG, incluidos xor shift * y xorshift + de Vigna, también lanzados al mundo en 2014). Todos tienen funciones de transición de estado bastante simples, no del todo buenas, arregladas por una función de salida de codificación. Cuando estaba escribiendo el artículo de PCG, una cosa que sabía que a algunas personas les gustaría era una función split() pero no podía encontrar una buena manera de hacerlo; en su lugar, desarrollé una prueba rápida de que si tenía un PRNG de _k_ bits en el que podía ir hacia la izquierda o hacia la derecha en cada paso, en _k_ pasos debe poder llegar a un estado en el que ha estado antes, probando así todo el concepto fue mal concebido. Esa observación no llegó al periódico. Pero como resultado de mis reflexiones antes de esa prueba, en una nota a pie de página en un borrador casi final del documento, sugerí, un tanto caprichosamente, porque la salida de PCG era un hash / scramble / permutación de su estado, si se sintiéndose travieso, podría reiniciar el generador con su propia salida y salirse con la suya. Lo saqué de la versión final porque pensé que tal fantasía sería una bandera roja demasiado grande para los revisores, dado que se consideraba que resembrar un PRNG con su propio estado era el tipo de mal uso de un PRNG, y el tipo de algo que vemos en personas sin experiencia en su uso.

Al leer el artículo de SplitMix, encontré muchas cosas que me gustaron, pero quedé muy desconcertado cuando vi split() . Hizo algo que básicamente consideré solo como una broma y lo convirtió en una característica de tienda de campaña. No fue hasta unos años después que comencé a escribir con más profundidad técnica sobre lo que sucede cuando tienes este tipo de operación.

La conclusión general es que si tiene un espacio de estado lo suficientemente grande (y SplitMix es apenas suficiente), es posible que pueda salirse con la suya mediante una función hash. Sigo sintiendo que esto no es una buena idea. Debido a que las asignaciones aleatorias (que es lo que estamos tratando en esta situación) tienen propiedades como "con una probabilidad asintótica distinta de cero, el árbol más alto en un gráfico funcional no tiene sus raíces en el ciclo más largo", afirmo que es difícil tener plena confianza a menos que el diseñador haya realizado el trabajo necesario para demostrar que tales patologías no están presentes en su diseño.

Para divertirse, aquí hay un volcado del espacio de estado de una versión pequeña de SplitMix que explora solo tres formas diferentes (y rígidamente fijadas) de combinar next() y split() :

Testing: SplitMix16: void advance() { rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 4, at 000043b0 after 516 steps
- state 00000050 -> new cycle 2, size 41, at 00002103 after 2 steps
- state 000000cd -> new cycle 3, size 4, at 0000681a after 6 steps
- state 00000141 -> new cycle 4, size 23, at 00004001 after 11 steps
- state 00000dee -> new cycle 5, size 7, at 00007436 after 4 steps
- state 00008000 -> new cycle 6, size 90278, at 5e5ce38c after 46472 steps
- state 00030000 -> new cycle 7, size 6572, at 12c65374 after 10187 steps
- state 00030016 -> new cycle 8, size 3286, at 65d0fc0c after 402 steps
- state 00058000 -> new cycle 9, size 17097, at 2a2951fb after 31983 steps
- state 08040000 -> new cycle 10, size 36, at 08040000 after 0 steps
- state 08040001 -> new cycle 11, size 218, at 08040740 after 360 steps
- state 08040004 -> new cycle 12, size 10, at 38c01b3d after 107 steps
- state 08040006 -> new cycle 13, size 62, at 38c013a0 after 39 steps
- state 08040009 -> new cycle 14, size 124, at 08045259 after 24 steps
- state 08040019 -> new cycle 15, size 32, at 38c06c63 after 151 steps
- state 08040059 -> new cycle 16, size 34, at 38c00217 after 17 steps
- state 08040243 -> new cycle 17, size 16, at 38c06e36 after 13 steps
- state 123c8000 -> new cycle 18, size 684, at 77d9595f after 194 steps
- state 123c8002 -> new cycle 19, size 336, at 5de8164d after 141 steps
- state 123c9535 -> new cycle 20, size 12, at 123c9535 after 0 steps
- state 139f0000 -> new cycle 21, size 545, at 743e3a31 after 474 steps
- state 139f0b35 -> new cycle 22, size 5, at 139f0b35 after 0 steps
- state 139f1b35 -> new cycle 23, size 5, at 68d3c943 after 8 steps

Cycle Summary:
- Cycle 1, Period 4, Feeders 32095
- Cycle 2, Period 41, Feeders 188
- Cycle 3, Period 4, Feeders 214
- Cycle 4, Period 23, Feeders 180
- Cycle 5, Period 7, Feeders 12
- Cycle 6, Period 90278, Feeders 1479024474
- Cycle 7, Period 6572, Feeders 102385385
- Cycle 8, Period 3286, Feeders 5280405
- Cycle 9, Period 17097, Feeders 560217399
- Cycle 10, Period 36, Feeders 413
- Cycle 11, Period 218, Feeders 51390
- Cycle 12, Period 10, Feeders 1080
- Cycle 13, Period 62, Feeders 4113
- Cycle 14, Period 124, Feeders 4809
- Cycle 15, Period 32, Feeders 2567
- Cycle 16, Period 34, Feeders 545
- Cycle 17, Period 16, Feeders 87
- Cycle 18, Period 684, Feeders 95306
- Cycle 19, Period 336, Feeders 100263
- Cycle 20, Period 12, Feeders 7
- Cycle 21, Period 545, Feeders 163239
- Cycle 22, Period 5, Feeders 12
- Cycle 23, Period 5, Feeders 34

- Histogram of indegrees of all 2147483648 nodes:
      0  529334272
      1 1089077248
      2  528875520
      3     131072
      4      65536
Testing: SplitMix16: void advance() { rng.next(); rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 36174, at 6b34fe8b after 21045 steps
- state 00000002 -> new cycle 2, size 4300, at 042a7c6b after 51287 steps
- state 0000000f -> new cycle 3, size 11050, at 0b471eb5 after 4832 steps
- state 0000001d -> new cycle 4, size 38804, at 2879c05c after 16280 steps
- state 00000020 -> new cycle 5, size 4606, at 46e0bdf6 after 7379 steps
- state 00046307 -> new cycle 6, size 137, at 0a180f87 after 89 steps
- state 00081c25 -> new cycle 7, size 16, at 177ed4d8 after 27 steps
- state 0044c604 -> new cycle 8, size 140, at 5e1f125b after 44 steps
- state 006e329f -> new cycle 9, size 18, at 006e329f after 0 steps
- state 13ebcefc -> new cycle 10, size 10, at 13ebcefc after 0 steps

Cycle Summary:
- Cycle 1, Period 36174, Feeders 975695553
- Cycle 2, Period 4300, Feeders 766130785
- Cycle 3, Period 11050, Feeders 110698235
- Cycle 4, Period 38804, Feeders 251133911
- Cycle 5, Period 4606, Feeders 43723200
- Cycle 6, Period 137, Feeders 4101
- Cycle 7, Period 16, Feeders 172
- Cycle 8, Period 140, Feeders 2310
- Cycle 9, Period 18, Feeders 124
- Cycle 10, Period 10, Feeders 2

- Histogram of indegrees of all 2147483648 nodes:
      0  529334272
      1 1089077248
      2  528875520
      3     131072
      4      65536
Testing: SplitMix16: void advance() { rng.next(); rng = rng.split(); rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 40959, at 0069b555 after 49520 steps
- state 00000031 -> new cycle 2, size 1436, at 5f619520 after 2229 steps
- state 000003a4 -> new cycle 3, size 878, at 18d1cb99 after 1620 steps
- state 0000046c -> new cycle 4, size 2596, at 46ba79c0 after 1591 steps
- state 0000c6e2 -> new cycle 5, size 24, at 0212f11b after 179 steps
- state 000af7c9 -> new cycle 6, size 61, at 40684560 after 14 steps
- state 00154c16 -> new cycle 7, size 110, at 29e067ce after 12 steps
- state 0986e055 -> new cycle 8, size 4, at 2b701c82 after 7 steps
- state 09e73c93 -> new cycle 9, size 3, at 352aab83 after 1 steps
- state 19dda2c0 -> new cycle 10, size 1, at 78825f1b after 2 steps

Cycle Summary:
- Cycle 1, Period 40959, Feeders 2129209855
- Cycle 2, Period 1436, Feeders 5125630
- Cycle 3, Period 878, Feeders 7077139
- Cycle 4, Period 2596, Feeders 5997555
- Cycle 5, Period 24, Feeders 24221
- Cycle 6, Period 61, Feeders 1774
- Cycle 7, Period 110, Feeders 1372
- Cycle 8, Period 4, Feeders 23
- Cycle 9, Period 3, Feeders 4
- Cycle 10, Period 1, Feeders 3

- Histogram of indegrees of all 2147483648 nodes:
      0  829903716
      1  684575196
      2  468475086
      3  132259769
      4   32192209
      5      58402
      6      17026
      7       1982
      8        261
      9          1
Testing: SplitMix16: void advance() { rng.next(); rng.next(); rng = rng.split();}

Finding cycles...
- state 00000000 -> new cycle 1, size 55038, at 3e57af06 after 30005 steps
- state 00000005 -> new cycle 2, size 376, at 4979e8b5 after 6135 steps
- state 0000001e -> new cycle 3, size 10261, at 0cd55c94 after 1837 steps
- state 0000002d -> new cycle 4, size 3778, at 7f5f6afe after 3781 steps
- state 00000064 -> new cycle 5, size 2596, at 3bc5404b after 5124 steps
- state 0000012b -> new cycle 6, size 4210, at 525cc9f3 after 397 steps
- state 00000277 -> new cycle 7, size 1580, at 410010c8 after 1113 steps
- state 00001394 -> new cycle 8, size 916, at 7b20dfb0 after 193 steps
- state 00063c2d -> new cycle 9, size 51, at 6e92350b after 121 steps
- state 058426a6 -> new cycle 10, size 8, at 058426a6 after 0 steps
- state 0e5d412d -> new cycle 11, size 1, at 0e5d412d after 0 steps
- state 4c2556c2 -> new cycle 12, size 1, at 4c2556c2 after 0 steps

Cycle Summary:
- Cycle 1, Period 55038, Feeders 2027042770
- Cycle 2, Period 376, Feeders 28715945
- Cycle 3, Period 10261, Feeders 49621538
- Cycle 4, Period 3778, Feeders 13709744
- Cycle 5, Period 2596, Feeders 15367156
- Cycle 6, Period 4210, Feeders 10418779
- Cycle 7, Period 1580, Feeders 1782252
- Cycle 8, Period 916, Feeders 744273
- Cycle 9, Period 51, Feeders 2351
- Cycle 10, Period 8, Feeders 24
- Cycle 11, Period 1, Feeders 0
- Cycle 12, Period 1, Feeders 0

- Histogram of indegrees of all 2147483648 nodes:
      0  529334272
      1 1089077248
      2  528875520
      3     131072
      4      65536

etc.

Ah, genial. Yo mismo rechacé un par de propuestas en ese sentido hace unos años, así que tenía miedo de perdernos algo bueno. :-)

¿Alguna idea final sobre el enfoque hash para derivar incrementos para transmisiones PCG? Dejando de lado si ese es el mecanismo principal para obtener transmisiones independientes. A falta de eliminar el acceso a esa función por completo, parece algo que nos gustaría hacer para evitar los identificadores de secuencias secuenciales fáciles de usar incorrectamente.

Por curiosidad, ¿hay alguna manera (fácil) de saber qué tan separados están dos estados en PCG64?

Por curiosidad, ¿hay alguna manera (fácil) de saber qué tan separados están dos estados en PCG64?

Sí, aunque no lo exponemos: http://www.pcg-random.org/useful-features.html#distance

En la fuente C ++, la función de distancia incluso le dirá la distancia entre corrientes, dando su punto de aproximación más cercana (donde la única diferencia entre las corrientes es una constante agregada).

Por cierto, para el LCG subyacente, podemos usar la distancia para calcular qué tan correlacionadas esperamos que estén las posiciones to. Una distancia corta es obviamente mala (y es mala para cualquier PRNG), pero una distancia con un solo conjunto de bits tampoco es buena, por lo que saltar a 2 ^ 64 ( 0x10000000000000000 ) con .jumped es una mala idea. En mi lista de tareas de PCG está escribir una función " independence_score " que mira la distancia entre dos estados y le dice qué tan aleatoria es la distancia (a través del peso de martillo, etc.) idealmente queremos aproximadamente la mitad del los bits deben ser ceros y medios unos y distribuirse generosamente).

Una forma de _ mantener_ jumped con PCG64 sería no saltar n * 0x10000000000000000 sino saltar n * 0x9e3779b97f4a7c150000000000000000 (truncado a 128 bits). Esto le dará todas las propiedades habituales que desea ( .jumped(3).jumped(5) == .jumped(8) ) sin ser patológico para la LCG subyacente.

(También soy consciente de que decir "no avance 0x10000000000000000 " es una especie de " bueno, no lo sostenga de esa manera " y no estoy muy satisfecho con eso. Claro, es Es genial que pueda existir independence_score , pero todo esto (y el problema con flujos similares), puede argumentar a favor de una función de salida predeterminada más fuerte, así que incluso si la gente hace cosas (raras) que son completamente patológicas LCG, no se hará ningún daño. PCG está llegando a los cinco años en este momento, y estoy considerando un aumento de la versión y ajustes este verano, por lo que este problema puede estar en la lista. Por supuesto, puede molestarlos si justo cuando pones PCG, hago una versión principal y la mejoro).

¿Alguna idea final sobre el enfoque hash para derivar incrementos para transmisiones PCG? Dejando de lado si ese es el mecanismo principal para obtener transmisiones independientes. A falta de eliminar el acceso a esa función por completo, parece algo que nos gustaría hacer para evitar los identificadores de secuencias secuenciales fáciles de usar incorrectamente.

Te recomiendo que lo pongas en el mezclador Murmur3 . Es probable que nadie cree accidentalmente flujos similares con eso sin un esfuerzo deliberado. (Editar: supongo que necesitas una versión de 128 bits, pero puedes mezclar las mitades superior e inferior. También agregaría una constante. A todos les encanta 0x9e3779b97f4a7c15f39cc0605cedc835 (parte fraccionaria de ϕ) pero 0xb7e151628aed2a6abf7158809cf4f3c7 (parte fraccionaria de e) también estaría bien, o _cualquier_ número de apariencia aleatoria).

Recomiendo wyhash (https://github.com/wangyi-fudan/wyhash) ya que es el más rápido y simple que pasó BigCrush y PractRand. El código c es tan simple como

inline  uint64_t    wyrand(uint64_t *seed){    
    *seed+=0xa0761d6478bd642full;    
    __uint128_t t=(__uint128_t)(*seed^0xe7037ed1a0b428dbull)*(*seed);    
    return  (t>>64)^t;    
}

@ wangyi-fudan, no puedo convencerme de que esto es una biyección.

Disculpe mi conocimiento limitado: ¿por qué es necesaria / favorecida la biyección para un PRNG?
Se agradecerá algunas explicaciones :-) @imneme

@ wangyi-fudan, si una función hash desde ints de 64 bits a ints de 64 bits no es una biyección (es decir, una función 1 a 1), algunos resultados se generan más de una vez y otros no. Eso es una especie de sesgo.

entiendo lo que dices. sin embargo, para un generador de números aleatorios de 64 bits R, esperaremos una colisión después de 1.2 * 2 ^ 32 números aleatorios (http://mathworld.wolfram.com/BirthdayAttack.html). con 2 ^ 64 números aleatorios es natural tener muchas colisiones. las colisiones son naturales, mientras que la biyección no es naturalmente aleatoria. Si sé que la mesa de juego (por ejemplo, un PRNG de 3 bits) se determina que tiene un valor de 0 dentro de 8 pistas, me atreveré a hacer una gran apuesta a cero después de observar 5 distintos de cero.

@ wangyi-fudan, en este contexto, estábamos hablando de formas de permutar el stream-id para que streams como 1,2,3 se conviertan en algo más aleatorio (también conocido como más normal). No hay ninguna virtud en las colisiones en este proceso.

Para los PRNG en general, debe leer sobre la diferencia entre los PRNG basados ​​en asignaciones aleatorias y los basados ​​en asignaciones invertibles aleatorias (funciones 1 a 1). He escrito sobre eso, pero también otros. En tamaños pequeños, los PRNG basados ​​en asignaciones aleatorias mostrarán sesgos y fallarán más rápidamente que los basados ​​en otras técnicas. En tamaños grandes, los defectos de todo tipo pueden ser más difíciles de detectar.

Podemos calcular la probabilidad de que si pedimos _n_ intervalos, uno se superponga con otro. Es una probabilidad bastante básica: no estoy seguro de que se pueda publicar un artículo al respecto porque (según tengo entendido) a nadie le entusiasman los artículos que contienen matemáticas elementales.

Creo que solo tienes que ser Pierre L'Ecuyer. ;-) página 15

Sí, cuando explica lo básico, ¡se considera que está bien!

@rkern @imneme La simplicidad es una característica, tanto en software como en matemáticas. El hecho de que algunos no se sientan impresionados por el simple trabajo no debe tomarse como una prueba contradictoria.

@lemire : Hay una pieza de humor que me gusta que creo que tiene mucho de verdad llamada _Cómo criticar a los científicos informáticos_ . La idea subyacente detrás de la pieza es que los teóricos favorecen la sofisticación y los experimentalistas favorecen la simplicidad. Entonces, si su audiencia es de experimentales, estarán encantados con la simplicidad, pero si su audiencia es de teóricos, no tanto.

El valor predeterminado BitGenerator es PCG64 . Gracias a todos por sus valiosas contribuciones. ¡Y aguante!

Muy inspirado por este hilo, tengo algunas novedades que informar ...

Antecedentes

En muchas medidas, pcg64 es bastante bueno; por ejemplo, bajo las medidas habituales de calidad estadística, obtiene un certificado de buena salud. Ha sido probado de varias formas; más recientemente, lo he ejecutado hasta medio petabyte con PractRand. Funciona bien en casos de uso normales.

PERO, las patologías que surgieron en este hilo no me sentaron bien. Claro, podría decir " bueno, no lo sostengas así ", pero el objetivo de un PRNG de propósito general es que debe ser robusto. Quería hacerlo mejor ...

Entonces, hace unos 25 días comencé a pensar en diseñar un nuevo miembro de la familia PCG ...

Objetivo

Mi objetivo era diseñar un nuevo miembro de la familia PCG que pudiera reemplazar la variante actual pcg64 . Como tal:

  • La función de salida debería codificar los bits más que XSL RR (porque hacerlo evitará los problemas que surgieron en este hilo).
  • El rendimiento debería ser tan rápido (o más rápido) que el actual pcg64 .
  • El diseño debe ser similar al PCG (es decir, no ser trivialmente predecible y, por lo tanto, no permitir que _cualquier_ trabajo de la función de salida se deshaga fácilmente).

Como siempre, existe una compensación, ya que tratamos de obtener la mejor calidad posible lo más rápido posible. Si no nos importara en absoluto la velocidad, podríamos tener más pasos en la función de salida para producir una salida más codificada, pero el punto de PCG era que el LCG subyacente era "casi lo suficientemente bueno" y por eso no necesitábamos hacer tanto esfuerzo como lo haríamos con algo así como un contador que se incrementa en 1.

Revelación

¡Me complace informar el éxito! Hace unos 25 días, cuando estaba pensando por primera vez en esto, estaba de vacaciones. Cuando regresé hace unos diez días, probé las ideas que tenía y me complació comprobar que funcionaban bien. El tiempo posterior se ha dedicado principalmente a varios tipos de pruebas. Ayer estaba lo suficientemente satisfecho como para insertar el código en la versión C ++ de PCG. Las pruebas en tamaños pequeños indican que es mucho mejor que XSL RR y competitivo con RXS M, pero en realidad brilla en tamaños más grandes. También cumple con todos los demás objetivos.

Detalles

FWIW, la nueva función de salida es (para el caso de salida de 64 bits):

uint64_t output(__uint128_t internal)
{
    uint64_t hi = internal >> 64;
    uint64_t lo = internal;

    lo |= 1;
    hi ^= hi >> 32;
    hi *= 0xda942042e4dd58b5ULL;
    hi ^= hi >> 48;
    hi *= lo;
    return hi;
}

Esta función de salida está inspirada en xorshift-multiply, por la cual se usa ampliamente. La elección de los multiplicadores es (a) para mantener bajo el número de constantes mágicas, y (b) para evitar que la permutación se deshaga (si no tiene acceso a bits de orden inferior), y también proporcionar el "randomized- por sí misma ”que suelen tener las funciones de salida de PCG.

Otros cambios

También es el caso de que 0xda942042e4dd58b5 es el multiplicador LCG para este PRNG (y todos los generadores PCG de estado de 128 bits con prefijo cm_ ). En comparación con 0x2360ed051fc65da44385df649fccf645 utilizado por pcg64 , esta constante sigue siendo bastante buena en términos de propiedades de prueba espectral, pero es más barata de multiplicar porque 128 bits × 64 bits es más fácil que 128 bits × 128 bits. He usado esta constante LCG durante varios años sin problemas. Cuando utilizo la variante de multiplicador barato, ejecuto la función de salida en el estado pre-iterado en lugar del estado post-iterado para un mayor paralelismo a nivel de instrucción.

Pruebas

Lo he probado a fondo (PractRand y TestU01) y estoy contento con él. Las pruebas incluyeron escenarios descritos en este hilo (por ejemplo, tomar un grupo de generadores ya sea en vapor secuencial o avanzado en 2 ^ 64 e intercalar su salida - probé un grupo de cuatro y un grupo de 8192 a 8 TB sin problemas, también como un arroyo y su contraparte en tierra opuesta).

Velocidad

Podría continuar con las pruebas de velocidad y los puntos de referencia. Hay todo tipo de factores que influyen en si un PRNG se ejecuta más rápido que otro en un punto de referencia determinado, pero en general, esta variante parece ser un poco más rápida, a veces mucho más rápida y, en ocasiones, un poco más lenta. Factores como el compilador y la aplicación tienen un impacto mucho mayor en la variabilidad del índice de referencia.

Disponibilidad

Los usuarios del encabezado C ++ pueden acceder a este nuevo miembro de la familia _ahora_ como pcg_engines::cm_setseq_dxsm_128_64 ; en algún momento en el futuro, cambiaré pcg64 de pcg_engines::setseq_xsl_rr_128_64 a este nuevo esquema. Mi plan actual es hacerlo este verano como parte de un aumento de la versión de PCG 2.0.

Anuncios formales

En general, estoy muy contento con este nuevo miembro de la familia y en algún momento más adelante en el verano, habrá publicaciones de blog con más detalles, probablemente haciendo referencia a este hilo.

Tus opciones...

Por supuesto, tienes que averiguar qué hacer con esto. Independientemente de si lo usaría o no, en realidad tendría bastante curiosidad por ver si funciona mejor o peor en sus puntos de referencia de velocidad.

@imneme ¿Se

@lemire :

Todavía es potencialmente mejor que 128 bits × 128 bits. Aunque cuánto mejor depende de qué tan bien vaya la programación de la instrucción en ese momento.

Tienes razón en que en ARM, el resultado de 64 bits × 64 bits → 128 bits son en realidad dos instrucciones.

(Es totalmente posible, por supuesto, agrupar dos LCG de 64 bits y mezclarlos. El espacio de todos los PRNG que podrían existir y funcionarían bien es bastante grande).

Una implementación rápida y sucia en nuestro marco sugiere una leve mejora del rendimiento, al menos en Linux de 64 bits:

Time to produce 1,000,000 64-bit unsigned integers
************************************************************
MT19937      5.42 ms
PCG64        2.59 ms
PCG64DXSM    2.41 ms
Philox       4.37 ms
SFC64        2.07 ms
numpy        5.41 ms
dtype: object

64-bit unsigned integers per second
************************************************************
MT19937      184.39 million
PCG64        386.01 million
PCG64DXSM    415.02 million
Philox       228.88 million
SFC64        483.94 million
numpy        184.79 million
dtype: object

Creo que está saliendo del estado pre-iterado que le da el golpe, al menos en este contexto. Dejé eso al principio y obtuve esencialmente el mismo rendimiento que PCG64 1.0. La verdadera ventaja sería la emulación de 128 bits, sospecho, pero no logré escribir eso, y no tengo una buena manera de probarlo en las plataformas que importan.

Supongo que la verdadera pregunta para ti, @imneme , es ¿qué tan molesto estarás con el nombre numpy.random.PCG64 implementando el algoritmo 1.0? El lanzamiento es inminente y ya está retrasado, así que no creo que vayamos a cambiar el algoritmo en este momento. Si el rendimiento en plataformas de 32 bits es particularmente bueno, entonces creo que podríamos agregar PCG64DXSM en una versión siguiente, y quizás reconsiderar el valor predeterminado en algunas versiones más adelante.

¡Es tu elección!

No tengo ningún problema con el envío de la versión 1.0 de PCG64. Muchas otras personas han usado esa variante.

Creo que la variante DXSM tiene la ventaja de evitar los problemas de uso de casos de borde que surgieron en este hilo (después de todo, esa es la razón por la que existe), pero por otro lado, tiene la desventaja de que es tarde para La fiesta. Incluso puede parecer bastante imprudente enviar un PRNG que tiene menos de un mes a los usuarios (aunque se basa en las mismas ideas que las variantes PCG más probadas).

(Dicho esto, si fuera mi elección, a pesar de las posibles acusaciones de imprudencia, probablemente enviaría la nueva; creo que la demora para ponerla en funcionamiento en Numpy es mínima. Y el riesgo es muy bajo, ya es probado exhaustivamente con BigCrush y probado con PractRand hasta 16 TB (incluido cm_mcg_dxsm_64_32 que es una cuarta parte del tamaño [sin transmisiones, salida de 32 bits]), y probablemente alcance los 32 TB en menos de una semana .)

[Me alegro de que el rendimiento haya mejorado un poco. Hace cinco años, el estado de uso pre-iterado era una pesimización para tamaños de 128 bits con multiplicadores de 128 bits. Pero eso fue entonces, en las máquinas en las que estaba probando, con los puntos de referencia que estaba usando].

Quise decir más sobre el uso del nombre PCG64 para la variante 1.0 cuando va a usar ese nombre para referirse a la variante 2.0.

@rkern Si es solo un problema de nombres, entonces PCG64DXSM y PCG64 los distinguen muy bien, ¿no?

Por numpy, ciertamente. Me pregunto si @imneme preferiría que no PCG64 cuando promocionará la variante 2.0 con ese nombre en la versión C ++. Soy sensible al hecho de que estar suelto con los nombres significa que algunas personas pueden probar el PCG64 numpy y compararlo con las afirmaciones que se harán en pcg-random.org sobre la versión 2.0. Véase casi cualquier conversación sobre los PRNG de Bob Jenkin.

En la Sección 6.3 del documento de PCG, dice:

Tenga en cuenta también que, aunque a los generadores se les presentan nombres nemotécnicos basados ​​en las permutaciones que realizan, los usuarios de la biblioteca PCG rara vez deben seleccionar miembros de la familia mediante estos mnemónicos. La biblioteca proporciona generadores con nombre basados ​​en sus propiedades, no en sus implementaciones subyacentes (por ejemplo, pcg32_unique para un generador de 32 bits de propósito general con una secuencia única). De esa manera, cuando se descubran y agreguen futuros miembros de la familia que se desempeñen aún mejor (con suerte debido a los descubrimientos de otros), los usuarios pueden cambiar sin problemas a ellos.

Y las bibliotecas C y C ++ están estructuradas de esa manera. Las bibliotecas proporcionan

  • una interfaz de bajo nivel que le permite elegir un miembro específico de la familia a través del nombre de su permutación, los tamaños de bits en los que opera y las características del LCG subyacente
  • una interfaz de alto nivel que proporciona alias convenientes como pcg64 que se conectan a un miembro de la familia de bajo nivel preseleccionado.

De esta manera, los alias se pueden actualizar para señalar a los miembros de la familia más nuevos, pero los usuarios que quieran reproducir exactamente los resultados más antiguos aún podrán hacerlo mediante el uso de la interfaz de bajo nivel para seleccionar el miembro de la familia al que anteriormente se podía acceder mediante un nivel alto conveniente. -alias de nivel.

Si va a enviar un PRNG llamado PCG64 , yo diría que es suficiente decir en su documentación qué variante específica de PCG es; en otras palabras, diga a qué miembro de la familia corresponde en el bajo -interfaz de biblioteca de nivel C o C ++.

El generador predeterminado se implementa se implementa como np.random.default_gen() en https://github.com/numpy/numpy/pull/13840. ( @rkern para referencia futura, probablemente sea bueno llamar explícitamente a los RP; es fácil

Una pequeña nit: ¿qué tal llamar a esto np.random.default_generator() lugar? gen parece demasiado corto / no obvio. Tendría curiosidad por lo que piensan los demás.

¿qué hay de llamar a este np.random.default_generator () en su lugar?

Tuve el mismo pensamiento, pero luego, np.random.default_generator() es un cabello largo, así que jugué con default_rng .

👍 También me gusta default_rng más que default_gen . Estaría feliz con cualquiera de estos, aunque todavía me inclinaría por default_generator .

: +1: por default_rng() .

¿Fue útil esta página
0 / 5 - 0 calificaciones