Numpy: Décidez de la nouvelle valeur par défaut de PRNG BitGenerator

Créé le 27 mai 2019  ·  166Commentaires  ·  Source: numpy/numpy

13163 apportera le remplacement tant attendu de l'infrastructure PRNG de numpy. Dans l'intérêt de garder ce PR gérable, nous le fusionnerons avec le master avant que toutes les décisions ne soient finalisées, comme par exemple BitGenerator sera nommé par défaut.

Nous devons prendre une décision avant la première version avec la nouvelle infrastructure. Une fois libérés, nous serons coincés dans notre choix pendant un certain temps, nous devons donc être sûrs que nous sommes à l'aise avec notre décision.

D'autre part, le choix de la valeur par défaut n'a pas que de nombreuses conséquences. Nous ne parlons pas de la valeur par défaut BitGenerator sous-jacente aux fonctions de commodité numpy.random.* . Selon NEP 19, ceux-ci restent des alias de l'ancien RandomState , dont BitGenerator reste MT19937 . Le seul endroit où la valeur par défaut entre en jeu est lorsque Generator() est instancié sans arguments; c'est-à-dire quand un utilisateur demande un Generator avec un état arbitraire, probablement pour appeler la méthode .seed() dessus. Cela pourrait probablement être assez rare, car il serait à peu près aussi facile de l'instancier explicitement avec le BitGenerator prédéfini qu'ils veulent réellement. Un choix légitime ici pourrait en fait être de ne nommer BitGenerator .

Néanmoins, nous aurons des recommandations sur les BitGenerator gens devraient utiliser la plupart du temps, et bien que nous puissions modifier les recommandations assez librement, celui qui occupe la première place sera probablement écrit sur la plupart des livres, blogs, tutoriels , et autres choses de ce genre.

OMI, il y a quelques options principales (avec mon commentaire, n'hésitez pas à ne pas être d'accord; je n'ai pas essayé de porter tous les commentaires pertinents de # 13163):

Aucun défaut

Exige toujours Generator(ChosenBitGenerator(maybe_seed)) . C'est un peu hostile, mais comme c'est un moyen assez pratique d'initialiser correctement le générateur pour la reproductibilité, les gens peuvent finir par le faire de toute façon, même si nous avons une valeur par défaut.

MT19937

Ce serait un bon choix conservateur. Ce n'est certainement pas pire que le statu quo. Comme le Mersenne Twister est encore largement considéré comme le choix «standard», il pourrait aider les utilisateurs universitaires qui ont besoin que leurs articles soient examinés par des personnes qui pourraient remettre en question des choix «non standard», quelles que soient les qualités spécifiques du PRNG. "Personne n'a jamais été licencié pour avoir embauché IBM." Les principaux inconvénients de MT19937 sont principalement qu'il est plus lent que certaines des alternatives disponibles, en raison de son très grand état, et qu'il échoue à certains tests statistiques de qualité. En choisissant un autre PRNG, nous avons une _opportunité_ (mais pas une _obligation_, IMO) d'être avisés ici et d'essayer de déplacer "la norme", si nous le souhaitons.

PCG64

C'est probablement celui que j'utiliserai le plus souvent, personnellement. Le principal inconvénient est qu'il utilise l'arithmétique d'entiers de 128 bits, qui est émulée en C si le compilateur ne fournit pas un tel type entier. Les deux principales plates-formes pour lesquelles c'est le cas sont les processeurs 32 bits et MSVC 64 bits, qui ne prennent tout simplement pas en charge les entiers 128 bits, même lorsque le processeur le fait. Personnellement, je ne suggère pas de laisser les processeurs 32 bits de plus en plus rares dicter nos choix. Mais les performances MSVC sont importantes, car nos versions Windows ont besoin de ce compilateur et non d'autres compilateurs Windows. Il peut probablement être résolu avec des éléments intrinsèques d'assembly / compilateur, mais quelqu'un devrait les écrire. Le fait que ce ne soit que MSVC pour lequel nous devons faire cela rend cela un peu plus acceptable que d'autres fois où nous sommes confrontés à l'assemblage.

Xoshiro256

Un autre choix moderne pour un petit PRNG rapide. Il a quelques bizarreries statistiques connues, mais il est peu probable qu'elles soient un facteur majeur pour la plupart des utilisations. Ces bizarreries me font hésiter, mais c'est mon choix personnel pour le code que j'écrirai.

15 - Discussion numpy.random

Commentaire le plus utile

Très inspiré par ce fil, j'ai quelques nouvelles à signaler…

Contexte

Selon de nombreuses mesures, pcg64 est assez bon; par exemple, selon les mesures habituelles de la qualité statistique, il obtient un bilan de santé propre. Il a été testé de différentes manières; plus récemment, je l'ai exécuté jusqu'à un demi-pétaoctet avec PractRand. Cela fonctionne bien dans les cas d'utilisation normaux.

MAIS, les pathologies qui sont apparues dans ce fil ne me convenaient pas. Bien sûr, je pourrais dire « eh bien, ne le maintenez pas ainsi », mais tout l'intérêt d'un PRNG à usage général est qu'il doit être robuste. Je voulais faire mieux ...

Donc, il y a environ 25 jours, j'ai commencé à penser à la conception d'un nouveau membre de la famille PCG…

Objectif

Mon objectif était de concevoir un nouveau membre de la famille PCG qui pourrait remplacer la variante pcg64 . En tant que tel:

  • La fonction de sortie devrait brouiller les bits plus que XSL RR (car cela évitera les problèmes qui se sont posés dans ce thread).
  • Les performances devraient être à peu près aussi rapides (ou plus rapides) que les pcg64 actuels.
  • La conception doit être PCG-ish (c'est-à-dire ne pas être trivialement prévisible, et donc ne pas permettre à _toute_ le travail de la fonction de sortie d'être facilement annulé).

Comme toujours, il y a un compromis, car nous essayons d'obtenir la meilleure qualité possible aussi rapidement que possible. Si nous ne nous soucions pas du tout de la vitesse, nous pourrions avoir plus d'étapes dans la fonction de sortie pour produire une sortie plus fortement brouillée, mais le point de vue de PCG était que le LCG sous-jacent était «presque assez bon» et que nous n'avions donc pas besoin pour faire autant d'efforts que nous le ferions avec quelque chose comme un compteur incrémenté de 1.

Divulgacher

Je suis heureux d'annoncer le succès! Il y a environ 25 jours, lorsque j'y ai pensé pour la première fois, j'étais en vacances. Quand je suis rentré il y a une dizaine de jours, j'ai essayé les idées que j'avais et j'étais heureux de constater qu'elles fonctionnaient bien. Le temps qui a suivi a été principalement consacré à divers types de tests. Hier, j'étais assez satisfait que j'ai poussé le code dans la version C ++ de PCG. Les tests effectués sur de petites tailles indiquent qu'il est bien meilleur que le XSL RR et compétitif avec le RXS M, mais qu'il brille en fait dans les plus grandes tailles. Il répond également à tous les autres objectifs.

Détails

FWIW, la nouvelle fonction de sortie est (pour le cas de sortie 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;
}

Cette fonction de sortie est inspirée de xorshift-multiply, qui est largement utilisée. Le choix des multiplicateurs est (a) de maintenir le nombre de constantes magiques vers le bas, et (b) d'éviter que la permutation ne soit annulée (si vous n'avez pas accès aux bits de poids faible), et également de fournir l'ensemble «randomisé- par elle-même »que les fonctions de sortie PCG ont généralement.

Autres changements

C'est aussi le cas que 0xda942042e4dd58b5 est le multiplicateur LCG pour ce PRNG (et tous les générateurs PCG à 128 bits avec préfixe cm_ ). Par rapport à 0x2360ed051fc65da44385df649fccf645 utilisé par pcg64 , cette constante est en fait encore assez bonne en termes de propriétés de test spectral, mais elle est moins chère à multiplier par car 128 bits × 64 bits est plus facile que 128 bits × 128 bits. J'ai utilisé cette constante LCG pendant plusieurs années sans problème. Lorsque vous utilisez la variante de multiplicateur bon marché, j'exécute la fonction de sortie sur l'état pré-itéré plutôt que sur l'état post-itéré pour un plus grand parallélisme au niveau des instructions.

Essai

Je l'ai testé à fond (PractRand et TestU01) et j'en suis satisfait. Les tests comprenaient des scénarios décrits dans ce fil de discussion (par exemple, prendre un générateur de gangs soit sur des vapeurs séquentielles, soit avancé de 2 ^ 64 et entrelacer leur sortie - j'ai testé un gang de quatre et un gang de 8192 à 8 To sans problème, ainsi comme un ruisseau et son homologue de l'autre côté).

La vitesse

Je pourrais parler longuement des tests de vitesse et des benchmarks. Il existe toutes sortes de facteurs qui influencent le fait qu'un PRNG fonctionne plus vite qu'un autre dans un benchmark donné, mais dans l'ensemble, cette variante semble souvent être un peu plus rapide, parfois beaucoup plus rapide et parfois un peu plus lente. Des facteurs tels que le compilateur et l'application ont un impact beaucoup plus grand sur la variabilité du benchmark.

Disponibilité

Les utilisateurs de l'en-tête C ++ peuvent accéder à ce nouveau membre de la famille _ maintenant_ en tant que pcg_engines::cm_setseq_dxsm_128_64 ; à un moment donné dans le futur, je passerai pcg64 de pcg_engines::setseq_xsl_rr_128_64 à ce nouveau schéma. Mon plan actuel est de le faire cet été dans le cadre d'un changement de version PCG 2.0.

Annonces officielles

Dans l'ensemble, je suis très content de ce nouveau membre de la famille et à un moment donné plus tard dans l'été, il y aura des articles de blog avec plus de détails, faisant probablement référence à ce fil.

Vos choix ...

Bien sûr, vous devez déterminer quoi faire avec cela. Que vous l'utilisiez ou non, je serais en fait assez curieux de voir si cela fait mieux ou moins bien dans vos repères de vitesse.

Tous les 166 commentaires

Que fait le compilateur Intel Windows pour les entiers 128 bits? À quel point PCG64 est-il compilé avec MSVC par rapport à MT1993 sous Windows? Je soupçonne que la fonction de saut en avant sera largement utilisée, il serait donc bon de l'avoir par défaut.

Que fait le compilateur Intel Windows pour les entiers 128 bits?

Pas tout à fait sûr; Je ne sais pas s'il y a des implications ABI par lesquelles ICC se soucierait d'être contraint. Si nous voulons juste avoir une idée de l'assembly généré que nous pourrions utiliser, alors c'est une ressource pratique: https://godbolt.org/z/kBntXH

Je soupçonne que la fonction de saut en avant sera largement utilisée, il serait donc bon de l'avoir par défaut.

Voulez-vous plutôt parler de flux réglables? C'est un bon point, mais je me demande si cela ne peut pas couper dans l'autre sens. Si notre choix de valeur par défaut est vraiment important, alors peut-être que si nous choisissons l'un de ces PRNG plus complets, les gens utiliseront ces fonctionnalités plus largement dans le code de la bibliothèque sans documenter qu'ils ont besoin de ces fonctionnalités "avancées", car après tout, ils sont disponibles "standard". Mais alors si un autre utilisateur essaie d'utiliser cette bibliothèque avec un BitGenerator pour la vitesse ou pour d'autres raisons, il heurtera un mur de briques. Dans un monde No default ou MT19937 , les bibliothèques seraient plus susceptibles de réfléchir et de documenter les fonctionnalités avancées dont elles ont besoin.

D'un autre côté, cette éventualité rendrait les BitGenerator s sans flux réglables moins désirables, et j'aime bien l'idée de faire progresser ce qui est considéré comme la meilleure pratique dans cette direction (purement personnellement; je ne me sens pas une obligation de faire partager à NumPy-le-projet cette notion). Cela pourrait aider à éviter certains des abus que je vois avec des personnes .seed() ing au milieu de leur code. Mais encore une fois, tout cela repose sur l'idée que le fait d'avoir une valeur par défaut changera considérablement les comportements des gens, de sorte que toutes ces préoccupations sont probablement assez atténuées.

À quel point PCG64 est-il compilé avec MSVC par rapport à MT1993 sous Windows?

Dans les benchmarks publiés par @bashtage dans # 13163, PCG64 est près de la moitié de la vitesse de MT19937, ce qui est assez décevant pour MSVC et ses amis. C'est par rapport à 23% plus rapide sous Linux.

Que fait le compilateur Intel Windows pour les entiers 128 bits?

D'autres compilateurs comme Clang, GCC et le compilateur Intel implémentent les entiers 128 bits sur les systèmes 64 bits de la même manière qu'ils implémentaient les entiers 64 bits sur les systèmes 32 bits. Toutes les mêmes techniques sans nouvelles idées nécessaires. Microsoft n'a pas pris la peine de le faire pour MSVC, il n'y a donc pas d'entiers 128 bits directement pris en charge par le compilateur.

En conséquence, pour MSVC, l'implémentation existante de PCG64 dans # 13163 implémente manuellement les mathématiques 128 bits en appelant les intrinsèques de Microsoft comme _umul128 dans x86_64 (et il pourrait vraisemblablement également utiliser des intrinsèques Intel équivalents et plus portables comme _mulx_u64 à la place), codant ainsi ce que GCC, Clang et le compilateur Intel feraient d'eux-mêmes. Le plus gros problème est probablement que le compilateur de Microsoft n'optimise pas très bien ces intrinsèques (j'espère qu'ils sont au moins intégrés?). Il est possible que l'assembleur codé à la main puisse aller plus vite, mais la solution appropriée serait que le compilateur ne soit pas si diaboliquement pauvre.

Je soupçonne que la fonction de saut en avant sera largement utilisée, il serait donc bon de l'avoir par défaut.

Je suis content que vous aimiez aller de l'avant, mais je suis curieux de savoir pourquoi vous pensez qu'il serait largement utilisé. (Personnellement, j'aime beaucoup distance , qui vous indique la distance entre deux PRNG. C'est dans la version C ++ de PCG, mais pas dans la version C. Ce serait assez trivial de l'ajouter s'il y en avait l'intérêt.)

Je suis content que vous aimiez aller de l'avant, mais je suis curieux de savoir pourquoi vous pensez qu'il serait largement utilisé.

Probablement pas familier avec la terminologie actuelle. Ce que je veux dire, ce sont des flux indépendants facilement obtenus qui peuvent être utilisés pour exécuter des simulations en parallèle. Je ne sais pas combien de problèmes de simulation peuvent être parallélisés, mais je soupçonne que c'est beaucoup et étant donné le nombre de cœurs que les gens ont sur une puce ces jours-ci, cela pourrait facilement compenser un inconvénient de vitesse.

Microsoft n'a pas pris la peine de le faire pour MSVC, il n'y a donc pas d'entiers 128 bits directement pris en charge par le compilateur.

Cela nuira à nos roues, OTOH, de nombreux utilisateurs de Windows obtiennent leurs packages d'Anaconda ou d'Enthought, qui utilisent tous deux Intel, et ceux qui se soucient vraiment des performances sont probablement sous Linux, Mac ou peut-être AIX.

EDIT: Et peut-être que si Microsoft est concerné, ils pourraient offrir une prime pour résoudre le problème.

FWIW, voici l'assembly que clang générerait pour la fonction critique, y compris les bits nécessaires pour décompresser / reconditionner le uint128_t dans la structure de uint64_t s: https: // godbolt.org/z/Gtp3os

Très cool, @rkern. Avez-vous une chance de faire de même pour voir ce que MSVC fait avec le code 128 bits écrit à la main?

Très cool, @rkern. Avez-vous une chance de faire de même pour voir ce que MSVC fait avec le code 128 bits écrit à la main?

Ce n'est pas joli. ~ https://godbolt.org/z/a5L5Gz~

Oups, oubliez d'ajouter -O3 , mais c'est toujours moche: https://godbolt.org/z/yiQRhd

Ce n'est pas si grave. Vous n'aviez pas d'optimisation, donc rien n'a été intégré. J'ai ajouté /Ox (peut-être y a-t-il une meilleure option?). J'ai également corrigé le code pour utiliser la rotation intrinsèque intégrée ( _rotr64 ) car apparemment MSVC est incapable de repérer l'idiome de rotation C.

Encore une sorte d'épave de train. Mais je pense qu'il est juste de dire qu'avec un peu d'attention, le code PCG64 pourrait être modifié pour compiler sur MSVC en quelque chose qui n'est pas totalement embarrassant pour tout le monde.

Afin de permettre la fusion de tout le reste, pourquoi ne pas choisir "pas de valeur par défaut" pour le moment? Cela nous laisse libre de prendre la décision sur la valeur par défaut plus tard (même après une ou plusieurs versions) sans rompre la compatibilité.

La plupart de nos utilisateurs ne sont pas des experts en nombres aléatoires, nous devrions leur fournir des valeurs par défaut.

Au-delà du prosaïque "maintenant ils doivent taper plus de code", que se passe-t-il quand on change quelque chose? Dans le cas où le BitGenerator est codé en dur (car nous n'avons pas fourni de valeur par défaut), chaque utilisateur non sophistiqué devra désormais refactoriser son code et, espérons-le, comprendre les nuances de son choix (notez que nous ne pouvons même pas être d'accord entre nous est le meilleur). Cependant, si nous fournissons une valeur par défaut, nous pourrions interrompre bruyamment leurs tests car la nouvelle version par défaut ou la nouvelle version n'est pas compatible avec le flux de bits.

Entre l'hypothèse que le flux binaire sera toujours constant par rapport à l'hypothèse que les développeurs NumPy savent ce qu'ils font et que les valeurs par défaut devraient être les meilleures de la marque, je me trompe du côté de la deuxième hypothèse, même si elle casse le premier.

Edit: clarifiez quels développeurs doivent savoir ce qu'ils font

La plupart de nos utilisateurs ne sont pas des experts en nombres aléatoires, nous devrions leur fournir des valeurs par défaut.

Eh bien, nous allons certainement documenter les recommandations, à tout le moins, que nous ayons ou non une valeur par défaut ou quelle est la valeur par défaut.

Au-delà du prosaïque "maintenant ils doivent taper plus de code", que se passe-t-il quand on change quelque chose?

A quoi pensez-vous? Je ne peux pas suivre votre argument.

Au-delà du prosaïque "maintenant ils doivent taper plus de code", que se passe-t-il quand on change quelque chose?

A quoi pensez-vous? Je ne peux pas suivre votre argument.

@mattip fait référence à la modification du générateur de bits par défaut.

Cela rendra fous les utilisateurs qui l'ont adopté et nécessitera un changement de code.

Par exemple, si vous avez utilisé

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

et le générateur de bits sous-jacent a été changé, alors ce serait faux.

Si tu as fait la chose la plus saine et utilisée

Generator(BitGenerator(1234))

alors vous ne le verriez pas.

IMO, Lors de l'examen du choix de la valeur par défaut, nous devrions penser qu'il est corrigé jusqu'à ce qu'une faille fatale soit trouvée dans le générateur de bits sous-jacent ou qu'Intel ajoute un QUANTUM_NI à ses puces, ce qui produit de nombreuses améliorations OOM dans les performances aléatoires.

Je me rends compte que je suis un peu étranger ici, mais je ne pense pas qu'il soit raisonnable de s'attendre à ce que PRNG soit le choix par défaut soit fixe pour toujours et ne change jamais. (En C ++, par exemple, std::default_random_engine est à la discrétion de l'implémentation et peut changer d'une version à l'autre.)

Il faut plutôt un mécanisme pour reproduire les résultats antérieurs. Ainsi, une fois qu'une implémentation particulière existe, il n'est pas cool de la changer (par exemple, le MT19937 _is_ MT19937, vous ne pouvez pas le modifier pour donner une sortie différente). [Et ce n'est pas non plus cool de supprimer une implémentation qui existe déjà.]

Lorsque la valeur par défaut change, les personnes qui souhaitent continuer à reproduire les anciens résultats devront demander la valeur par défaut précédente par nom. (Vous pouvez faire cela en fournissant un mécanisme pour sélectionner la valeur par défaut correspondant à une version précédente.)

Cela dit, même si vous êtes autorisé à remplacer le générateur par défaut par autre chose, il doit vraiment être strictement meilleur - toutes les fonctionnalités présentes dans le générateur par défaut représentent un engagement à prendre en charge cette fonctionnalité à l'avenir. Si votre générateur par défaut a advance efficace, vous ne pouvez pas vraiment l'enlever plus tard. (Vous pouvez potentiellement désactiver les fonctionnalités avancées du générateur par défaut pour éviter ce problème.)

En résumé, il existe des moyens de s'assurer que les utilisations peuvent avoir des résultats reproductibles sans essayer de vous enfermer dans un contrat où la valeur par défaut est inchangée pour toujours. Cela réduira également les enjeux du choix que vous faites.

(FWIW, c'est ce que j'ai fait dans PCG. Le PRNG 32 bits PCG par défaut est actuellement la variante XSH-RR [accessible comme pcg_setseq_64_xsh_rr_32_random_r dans la bibliothèque C et la classe pcg_engines::setseq_xsh_rr_64_32 dans le C ++ library], mais en principe si vous voulez vraiment une reproductibilité à l'épreuve du temps, vous devez spécifier explicitement XSH-RR, plutôt que d'utiliser pcg32_random_r ou pcg32 qui sont des alias et peuvent en principe être mis à niveau vers autre chose .)

Ce n'est pas vraiment pour toujours (ce projet entier est motivé à 90% par une promesse réelle, authentique et honorée à jamais faite il y a environ 14 ans), mais comme vous le dites, changer les besoins (a) une raison impérieuse de changer et (b) serait prendre au moins quelques années pour donner le cycle d'amortissement.

Il est préférable de faire tout ce qui est en son pouvoir aujourd'hui pour le rapprocher le plus possible.

Une chose qui n'est pas interdite, bien sûr, est d'améliorer le code PRNG après la publication en tant que logn car il produit les mêmes valeurs. Par exemple, si nous options pour un PRNG qui utilisait uint128, nous pourrions laisser MS ajouter le support uint128 (fat chance) ou ajouter l'assembly pour Win64 dans une version future.

Par exemple, si vous avez utilisé

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

et le générateur de bits sous-jacent a été changé, alors ce serait faux.

Bon, cela semble plaider, avec @ eric-wieser, pour l'option "Pas de valeur par défaut", que je ne peux pas faire correspondre avec la déclaration initiale "La plupart de nos utilisateurs ne sont pas des experts en nombres aléatoires, nous devrions leur fournir des valeurs par défaut . "

Entre aucun défaut et un amical, assumant pleinement le défaut, je choisirais toujours ce dernier:

Maintenant:

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

_Ma préférence:

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

Maintenant, je ne pense pas que cela va entrer, mais je pense qu'une façon de prolonger l'utilisation de RandomState est de le rendre disponible uniquement aux utilisateurs qui se sentent suffisamment experts pour choisir un générateur de bits.

En résumé, il existe des moyens de s'assurer que les utilisations peuvent avoir des résultats reproductibles sans essayer de vous enfermer dans un contrat où la valeur par défaut est inchangée pour toujours. Cela réduira également les enjeux du choix que vous faites.

Oui, nous l'avons. Les utilisateurs peuvent saisir les BitGenerator s par leur nom (par exemple MT19937 , PCG64 , etc.) et les instancier avec des graines. BitGenerator objets [0..1) float64 s et entiers (ainsi que toutes les fonctionnalités amusantes de saut / flux dont ils disposent) . La classe Generator dont nous parlons prend un objet BitGenerator fourni et s'enroule autour de lui pour fournir toutes les distributions non uniformes, les gaussiens, les gammas, les binômes, etc. des garanties strictes de compatibilité de flux pour les BitGenerator s. Nous ne nous débarrasserons pas de tout (qui le fera sortir), et ne les changerons pas non plus.

La question centrale à propos de la valeur par défaut est "Que fait le code g = Generator() , sans argument?" À l'heure actuelle, dans le PR, il crée un Xoshiro256 BitGenerator avec un état arbitraire (c'est-à-dire tiré d'une bonne source d'entropie comme /dev/urandom ). L'option "Pas de valeur par défaut" serait de faire cela une erreur; les utilisateurs devraient nommer explicitement le BitGenerator qu'ils veulent. Le point de @ eric-wieser est que "No default" est une option catégoriquement _safe_ pour la première version. Une version ultérieure fournissant une valeur par défaut ne causera pas de problèmes de la même manière que la modification d'une valeur par défaut existante.

@rkern , Si vous ne vous souciez que du cas sans arguments où la graine est générée automatiquement à partir de l'entropie disponible, alors peu importe le générateur sous-jacent - il pourrait changer toutes les heures car les résultats ne seraient jamais reproductibles ( différentes séries obtiendraient des graines différentes).

En revanche, @bashtage semble se soucier d'un générateur par défaut fourni avec une graine.

@rkern , Si vous ne vous souciez que du cas _no arguments_ où la graine est générée automatiquement à partir de l'entropie disponible, alors peu importe le générateur sous-jacent - il pourrait changer toutes les heures car les résultats ne seraient jamais reproductibles ( différentes séries obtiendraient des graines différentes).

Vous pouvez réamorcer le BitGenerator après sa création. Donc, si Generator() fonctionne, ce à quoi je m'attends vraiment, c'est que les gens qui veulent un PRNG prédéfini le @bashtage :

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

C'est un peu fastidieux, c'est pourquoi j'ai suggéré en haut que la plupart des gens opteraient peut-être pour Generator(PCG64(<seed>)) toute façon, car c'est à peu près aussi pratique en termes de frappe. Cependant, @bashtage note correctement une certaine résistance face à une décision supplémentaire.

Donc, je suppose que nous avons _aussi_ une question plus large devant nous: "De quelles façons nous voulons que les utilisateurs instancient l'un d'entre eux? Et si ces méthodes ont des paramètres par défaut, quelles devraient être ces valeurs par défaut?" Nous avons un espace de conception ouvert et la suggestion de @bashtage pour Generator(<seed>) ou Generator(DefaultBitGenerator(<seed>)) est toujours possible.

@bashtage Dans quelle mesure pensez-vous que la documentation aiderait? Autrement dit, si nous disions en haut " PCG64 est notre valeur par défaut préférée BitGenerator " et que nous utilisons Generator(PCG64(seed)) cohérente dans tous les exemples (lorsque vous ne démontrez pas spécifiquement d'autres algorithmes)?

Je serais peut-être plus convaincu d'avoir une fonction default_generator(<seed>) _fonction_ sur Generator(<seed>) ou g=Generator();g.seed(<seed>) . Ensuite, si nous avions vraiment besoin de le changer et que nous ne voulions pas casser des choses, nous pourrions simplement ajouter une nouvelle fonction et ajouter des avertissements à l'ancienne. Je pourrais recommander de le marquer experimental pour la première version, ce qui nous laisse le temps de regarder cette infrastructure dans la nature avant de prendre un engagement ferme.

Pourquoi ne pas créer un objet DefaultBitGenerator qui n'expose aucun détail sur son état interne? Ce serait un proxy pour l'un des autres objets générateurs de bits, mais pourrait en principe envelopper n'importe lequel d'entre eux - sauf bien sûr pour sa séquence spécifique de nombres générés. Nous espérons que cela découragerait les utilisateurs de faire des hypothèses programmatiques sur ce qu'ils peuvent faire avec le BitGenerator par défaut, tout en nous permettant de continuer à utiliser un algorithme amélioré.

Je suis d'accord avec @bashtage qu'il serait beaucoup plus convivial de prendre en charge directement les valeurs entières comme arguments de Generator , par exemple np.random.Generator(1234) . Cela utiliserait bien sûr DefaultBitGenerator .

Dans la documentation de Generator , nous pourrions donner un historique complet de ce qu'était le générateur de bits par défaut dans chaque version précédente de NumPy. C'est essentiellement la suggestion de @imneme , et je pense que cela suffirait à des fins de reproductibilité.

(Je viens de voir cette modification dans un commentaire précédent)

Oups, oubliez d'ajouter -O3 , mais c'est toujours moche: https://godbolt.org/z/yiQRhd

Pour MSVC, ce n'est pas -O3 , c'est /O2 ou /Ox (mais pas /O3 !).

Dans la documentation de Generator , nous pourrions donner un historique complet de ce qu'était le générateur de bits par défaut dans chaque version précédente de NumPy. C'est essentiellement la suggestion de @imneme , et je pense que cela suffirait à des fins de reproductibilité.

En fait, encore mieux serait d'inclure un argument version explicite, comme l'argument protocol pickle , dans Generator / DefaultBitGenerator . Ensuite, vous pouvez écrire quelque chose comme np.random.Generator(123, version=1) pour indiquer que vous voulez des nombres aléatoires de "version 1" (quel qu'il soit) ou np.random.Generator(123, version=np.random.HIGHEST_VERSION) (comportement par défaut) pour indiquer que vous voulez le dernier / plus grand générateur de bits (Quoique ce soit).

Vraisemblablement version=0 serait le MT19937 que NumPy a utilisé jusqu'à présent, et version=1 pourrait être la nouvelle valeur par défaut que nous choisirons.

Qu'en est-il de la création d'un objet DefaultBitGenerator qui n'expose aucun détail sur son état interne? Ce serait un proxy pour l'un des autres objets générateurs de bits, mais pourrait en principe envelopper n'importe lequel d'entre eux - sauf bien sûr pour sa séquence spécifique de nombres générés. Nous espérons que cela découragerait les utilisateurs de faire des hypothèses programmatiques sur ce qu'ils peuvent faire avec le BitGenerator par défaut, tout en nous permettant de continuer à utiliser un algorithme amélioré.

Hmmm. C'est séduisant. C'est comme si cela compliquait trop les choses et ajoutait une autre boucle à ce nœud gordien (et qu'il devrait y avoir un coup plus Alexander-esque disponible pour nous), mais c'est vraiment la seule mauvaise chose que j'ai à dire à ce sujet. Cela rend les décisions restantes plus faciles: nous pouvons nous concentrer sur la qualité et les performances statistiques.

En fait, il serait encore mieux d'inclure un argument version explicite, comme pickle , dans Generator / DefaultBitGenerator .

Je suis moins fan de ça. Contrairement au cas pickle , ces choses ont des noms significatifs que nous pouvons utiliser, et nous avons déjà le mécanisme implémenté.

Je suis moins fan de ça. Contrairement au cas pickle , ces choses ont des noms significatifs que nous pouvons utiliser, et nous avons déjà le mécanisme implémenté.

Considérez ce qui suit du point de vue d'un utilisateur NumPy typique:

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

Je pense qu'il est prudent de supposer que la plupart de nos utilisateurs en savent très peu sur les mérites relatifs des algorithmes RNG. Mais même sans lire aucun document, ils peuvent deviner en toute sécurité que version=1 (la nouvelle valeur par défaut) doit être meilleur dans la plupart des cas que version=0 . Pour la plupart des utilisateurs, c'est vraiment tout ce qu'ils doivent savoir.

En revanche, des noms comme MT19937 et PCG64 n'ont vraiment de sens que pour les experts ou les personnes qui ont déjà lu notre documentation :).

Dans votre cas d'utilisation, personne ne sélectionne les version qu'ils _ veulent_. Ils ne sélectionnent que les version dont ils ont besoin pour répliquer les résultats d'une version connue. Ils sont toujours à la recherche d'une valeur spécifique qui a été utilisée (implicitement, car nous lui avons permis d'être implicite) dans les résultats qu'ils veulent répliquer; ils n'ont pas besoin de raisonner sur la relation entre plusieurs valeurs.

Et dans tous les cas, ce niveau de reproductibilité des versions croisées est quelque chose que nous avons rejeté dans la NEP 19 . Les arguments contre le versionnage des distributions s'appliquent tout aussi bien ici.

Quelques réflexions sur la valeur par défaut:

  • 99,9% des utilisateurs ne se soucient pas ou ne veulent pas connaître les algorithmes sous-jacents, ils veulent juste des nombres aléatoires. Donc +1 pour avoir fait un choix avisé pour la valeur par défaut, veuillez ne pas faire choisir aux utilisateurs.
  • dSFMT semble être simplement une version plus rapide que MT19937 (ce serait bien d'indiquer dans la documentation à quelle vitesse et de supprimer "SSE2"). Comme nous ne garantissons de toute façon pas la reproductibilité du bitstream, les différences d'état internes ne sont pas très intéressantes et dSFTM devrait être préféré à MT19937 même si l'argument gagnant ici est "faciliter la vie lors de la révision de l'article" .
  • Les performances comptent pour une fraction significative de la base d'utilisateurs. Les propriétés statistiques des générateurs ne comptent que pour une très petite fraction d'utilisateurs. Tous les générateurs inclus conviennent aux cas d'utilisation normaux. Donc +1 pour avoir choisi le plus rapide par défaut.

Désolé de le dire - mais 32 bits compte toujours sous Windows - voir https://github.com/pypa/manylinux/issues/118#issuecomment -481404761

Je pense que nous devrions nous soucier beaucoup des propriétés statistiques, car nous sommes en train de passer à une plus grande utilisation des méthodes de rééchantillonnage dans l'analyse des données. Si Python a la réputation d'être un peu négligent sur cette question, même si ce n'est que par défaut, cela pourrait bien être un obstacle à l'adoption par les personnes qui envisagent Python pour l'analyse de données. Je serais très heureux si Python était le package de choix pour les personnes qui prennent la permutation et la simulation au sérieux.

Je pense que c'est bien d'offrir des algorithmes plus rapides et non à la pointe de la technologie, mais pas par défaut, dans la mesure où nous pouvons l'éviter et maintenir la rétro-compatibilité.

Pour quelques analyses et discussions, voir: https://arxiv.org/pdf/1810.10985.pdf

Les manuels donnent des méthodes qui supposent implicitement ou explicitement que les PRNG peuvent être substitués aux vraies variables IIDU [0,1) sans introduire d'erreur matérielle [20, 7, 2, 16, 15]. Nous montrons ici que cette hypothèse est incorrecte pour les algorithmes de nombreux packages statistiques couramment utilisés, notamment MATLAB, le module aléatoire de Python, R, SPSS et Stata.

@kellieotto , @pbstark -

Je pense que nous devrions nous soucier beaucoup des propriétés statistiques, car nous sommes en train de passer à une plus grande utilisation des méthodes de rééchantillonnage dans l'analyse des données

D'accord. Tant que ces propriétés sont pertinentes pour certains cas d'utilisation du monde réel, c'est très important. Les préoccupations qui sont généralement soulevées sont toujours extrêmement théoriques.

Pour quelques analyses et discussions, voir: https://arxiv.org/pdf/1810.10985.pdf

Article très intéressant. Il conclut que NumPy est à peu près la seule bibliothèque qui réussit (haut de la page 9), contrairement à R, Python stdlib & co.

Il serait très utile d'obtenir des exemples encore plus concrets que dans le document. Si notre générateur par défaut actuel tombe également en panne à un moment donné, quand est-ce? Des exemples comme la fonction sample R générant 40% de nombres pairs et 60% de nombres impairs lors du dessin d'environ 1,7 milliard d'échantillons. Quel est l'équivalent du bootstrap / rééchantillonnage ici?

La dernière version de R (3.6) corrige l'approche de la troncature par rapport aux bits aléatoires
pour générer des entiers aléatoires. Le Mersenne Twister reste la valeur par défaut
PRNG, cependant.

@Kellie Ottoboni [email protected] et je pense que le PRNG par défaut dans
les langages scientifiques et les progiciels de statistiques devraient être cryptographiquement
sécurisé (un CS-PRNG, par exemple, SHA256 en mode compteur), avec la possibilité de tomber
retour à quelque chose de plus rapide mais de qualité inférieure (par exemple, le Mersenne Twister)
si la vitesse l'exige.

Nous avons travaillé sur un CS-PRNG pour Python:
https://github.com/statlab/cryptorandom

Les performances ne sont pas (encore) excellentes. Le goulot d'étranglement semble être la conversion de type
dans Python pour convertir des chaînes binaires (sortie de hachage) en entiers. Étaient
travailler sur une implémentation qui déplace une plus grande partie du travail vers C.

À votre santé,
Philippe

Le lun 27 mai 2019 à 06:27 Ralf Gommers [email protected]
a écrit:

Je pense que nous devrions nous soucier beaucoup des propriétés statistiques, car nous sommes
dans le processus d'un grand changement vers une plus grande utilisation des méthodes de rééchantillonnage
l'analyse des données

D'accord. Tant que ces propriétés sont pertinentes pour une utilisation dans le monde réel
cas, c'est très important. Les préoccupations qui sont généralement soulevées sont
toujours extrêmement académique.

Pour quelques analyses et discussions, voir:
https://arxiv.org/pdf/1810.10985.pdf

Article très intéressant. Il conclut que NumPy est à peu près le seul
bibliothèque qui fait les choses correctement (haut de la page 9), contrairement à R, Python stdlib & co.

Il serait très utile d'obtenir des exemples encore plus concrets que dans le
papier. Si notre générateur par défaut actuel tombe également en panne à un moment donné,
c'est quand? Des exemples comme la fonction d'échantillon de R générant 40% même
nombres et 60% de nombres impairs lors du prélèvement d'environ 1,7 milliard d'échantillons. Quel est le
bootstrap / rééchantillonnage équivalent ici?

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWJEIA4CTLLHVGZVKBLPXPOUFA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWJZW4Y#issuecomment-496212851 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWJW445QDPGZDGXMPA3PXPOUFANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Les performances comptent pour une fraction significative de la base d'utilisateurs. Les propriétés statistiques des générateurs ne comptent que pour une très petite fraction d'utilisateurs. Tous les générateurs inclus conviennent aux cas d'utilisation normaux. Donc +1 pour avoir choisi le plus rapide par défaut

Premièrement, il n'y a tout simplement aucun moyen de choisir «le plus rapide». @bashtage a exécuté quelques tests sur le code actuel dans # 13163 et c'était partout sur la carte, avec dSFMT gagnant sous Windows et battu par PCG-64 et Xoroshiro 256 sous Linux. Et tout cela sur la même machine avec le même benchmark. Une architecture matérielle différente (même des révisions dans X86) fera une différence, tout comme différents benchmarks. (Comme déjà discuté dans ce fil, PCG fait mal dans les benchmarks Windows en raison de problèmes avec MSVC, qui est également susceptible d'être transitoire, car MSVC peut s'améliorer ou des personnes peuvent contourner ses problèmes. Probablement des problèmes MSVC similaires expliquent pourquoi Xoshiro a été battu.)

Je me demande également quelle est la taille de la «fraction significative» d'utilisateurs soucieux de la vitesse. Python lui-même est en moyenne environ 50 fois plus lent que C. Quelle fraction de la base d'utilisateurs NumPy l'exécute sur PyPy (ce qui donnerait une augmentation de vitesse 4 ×)? Certains, certes, mais je ne soupçonne pas un nombre très élevé.

Et pour cette «fraction significative» qui se soucie de la vitesse, étant donné toute la variabilité décrite ci-dessus, qui va vous croire sur parole que le PRNG par défaut fonctionnera le plus rapidement pour leur application? Une chose sensée à faire (qui est également assez amusante et à la portée de la plupart des utilisateurs) est de comparer les différents PRNG disponibles et de voir lequel est le plus rapide _pour eux_.

En revanche, bien qu'ils puissent trouver des indices dans la documentation, déterminer la qualité statistique des PRNG particuliers n'est, comme vous le remarquez, pas sur le radar de la plupart des utilisateurs (et est un défi même pour les experts). La plupart ne sauront même pas quand / s'ils devraient s'en soucier ou non. Je dirais que c'est un endroit pour un certain paternalisme - le fait que la plupart des utilisateurs ne se soucient pas de quelque chose ne signifie pas que les responsables ne devraient pas s'en soucier.

Il est vrai que tous les PRNG inclus conviennent à la plupart des cas d'utilisation, mais c'est une barre assez basse. Les systèmes Unix sont livrés avec une panoplie de PRNG de bibliothèques C qui sont tous statistiquement terribles et pourtant ils sont largement utilisés depuis des années sans que le monde ne se détourne de son axe.

Au-delà des propriétés statistiques, il existe d'autres propriétés que l'utilisateur ne sait peut-être pas vouloir pour lui-même, mais que je pourrais souhaiter pour elles. Personnellement, en tant que fournisseur de PRNG, je veux éviter une prévisibilité insignifiante - je ne veux pas que quelqu'un regarde quelques sorties du PRNG et puisse ensuite dire ce que seront toutes les sorties futures. Dans la plupart des contextes où NumPy est utilisé, la prévisibilité n'est pas un problème - il n'y a aucun adversaire qui bénéficiera d'être facilement capable de prédire la séquence. Mais quelqu'un quelque part va utiliser les PRNG de NumPy non pas parce qu'ils ont besoin de NumPy pour faire des statistiques, mais parce que c'est là qu'ils ont déjà trouvé des PRNG; ce code peut faire face à un adversaire réel qui bénéficiera de la capacité de prédire le PRNG. Payer beaucoup (par exemple, une perte de vitesse importante) pour assurer solidement contre cette situation aberrante n'en vaut pas la peine, mais une assurance modeste en vaut la peine.

Pour quelques analyses et discussions, voir: https://arxiv.org/pdf/1810.10985.pdf

Les manuels donnent des méthodes qui supposent implicitement ou explicitement que les PRNG peuvent être substitués aux vraies variables IIDU [0,1) sans introduire d'erreur matérielle [20, 7, 2, 16, 15]. Nous montrons ici que cette hypothèse est incorrecte pour les algorithmes de nombreux packages statistiques couramment utilisés, notamment MATLAB, le module aléatoire de Python, R, SPSS et Stata.

FWIW, il y a un joli article de @lemire sur la génération efficace d'un nombre dans une plage sans biais. J'ai utilisé cela comme un point de départ pour explorer et exécuter des repères dans mon propre article . (Lors de la génération de 64 bits, la méthode de Lemire utilise la multiplication de 128 bits pour éviter une division 64 bits lente, avec tous les problèmes familiers qui pourraient soulever les utilisateurs de MSVC.)

@pbstark @kellieotto J'ai lu votre article avec intérêt quand il est apparu sur arXiv. Je rendais visite à des amis de BIDS et ils avaient mentionné votre travail. La section Discussion note que "jusqu'à présent, nous n'avons pas trouvé de statistique avec un biais cohérent suffisamment grand pour être détecté dans les réplications O (10 ^ 5)" pour MT19937. En avez-vous déjà trouvé un? Avez-vous trouvé un exemple concret de PRNG à 128 bits comme PCG64? Cela me semble être un seuil raisonnable de pertinence pratique, où cette considération pourrait commencer à l'emporter sur d'autres (OMI), au moins aux fins de la sélection d'un défaut d'ordre général.

La fonctionnalité intéressante de notre nouveau framework PRNG # 13163 est qu'il permet à n'importe qui de fournir son propre BitGenerator qui peut simplement être branché. Il n'est même pas nécessaire que les gens l'utilisent dans leur numpy code. Je vous encourage à envisager d'implémenter cryptorandom tant que BitGenerator en C afin que nous puissions le comparer directement avec les autres options.

Personnellement, je m'attends à ce que ceux qui se soucient vraiment de la vitesse fassent un effort supplémentaire si nécessaire (ce n'est pas grand chose ici). Nous devrions fournir des valeurs par défaut
Cet article où Numpy a bien fait semble intéressant (bravo à Robert probablement pour avoir bien fait les choses!), Mais ce n'est en fait pas l'échantillonneur le générateur de bits.

@pbstark voudriez -vous peut-être l'implémenter en tant que BitGenerator compatible avec numpy / randomgen? C'est probablement à la fois le moyen le plus simple de l'accélérer et de le rendre disponible à un large public sous une forme beaucoup plus utile. Puisqu'il semble que vous et Kellie Ottoboni êtes à Berkeley, nous pourrions nous rencontrer un peu de temps pour faire avancer les choses? (Juste une offre, je devrais d'abord regarder le code de plus près).

En ce qui concerne le papier _Random Sampling: Practice Makes Imperfect_, c'est une bonne lecture, mais il convient de se rappeler que si nous avions 1 billion de cœurs produisant un nombre par nanoseconde pendant 100 ans, nous générerions moins de 2 ^ 102 nombres.

Pour les PRNGs trivialement prévisibles (même ceux avec de grands espaces d'état comme le Mersenne Twister), nous pouvons en fait savoir si une séquence de sortie spécifique peut jamais être produite (et trouver la graine qui la générerait si elle existe ou se sentir mélancolique si elle ne le fait pas ), mais pour d'autres PRNG non trivialement prévisibles, nous ne pouvons pas (facilement) savoir quelles séquences de sortie ne peuvent jamais être produites et lesquelles sont là, mais suffisamment rares pour que nous ne puissions jamais les trouver dans un éon de recherche. (Comme vous le savez peut-être, j'ai un PRNG qui, je le sais, crachera un fichier zip avec _Twelth Night_ dans les 2 ^ 80 sorties, mais bonne chance pour le trouver.)

Si vous voulez vraiment un cryptoprng, le seul choix sur le matériel moderne est
AES car il dispose d'une instruction dédiée. @lemire a une implémentation
ici https://github.com/lemire/testingRNG/blob/master/source/aesctr.h que
est aussi rapide que les générateurs non cryptés. Il y a aussi ChaCha20 qui peut aller
rapide avec SIMD. Les deux seront cependant très lents sur le vieux matériel. ThreeFry et
Les Philox sont déjà inclus et sont des programmes de compteur de cryptolite.

La crypto IMO est surestimée en termes de coûts-avantages. Je n'en connais aucun
rétraction importante en raison de problèmes de PRNG avec Mt, ce qui, je pense, a été
utilisé dans l'ordre de 10e6 articles publiés. Les seules applications que j'ai vues
où le PRNG était vraiment problématique étaient des cas où la période était
petit que le générateur a terminé le cycle complet. Même ici le seul
effet réduisait la taille de l'échantillon de l'étude, qui reproduisait les principaux
résultats une fois réexécutés sur un système avec une période plus longue.

Le lundi 27 mai 2019, 19:50, Robert Kern [email protected] a écrit:

@pbstark https://github.com/pbstark @kellieotto
https://github.com/kellieotto J'ai lu votre article avec intérêt quand il
est apparu sur arXiv. Je rendais visite à des amis à BIDS, et ils avaient
a mentionné votre travail. La section Discussion note que "jusqu'à présent, nous n'avons pas
trouvé une statistique avec un biais constant suffisamment grand pour être détectée dans
O (10 ^ 5) réplications "pour MT19937. En avez-vous déjà trouvé une? Avez-vous trouvé un
un exemple concret pour un PRNG à 128 bits comme PCG64? Cela me semble
être un seuil raisonnable de pertinence pratique, lorsque cette considération
pourrait commencer à l'emporter sur les autres (IMO), au moins dans le but de sélectionner
une valeur par défaut à usage général.

La fonctionnalité intéressante de notre nouveau framework PRNG # 13163
https://github.com/numpy/numpy/pull/13163 est qu'il permet à quiconque de
fournir leur propre BitGenerator qui peut simplement être branché. Il ne
doivent même être en numpy pour que les gens l'utilisent dans leur code numpy. je voudrais
vous encourageons à envisager la mise en œuvre de la cryptographie aléatoire en tant que BitGenerator en C
afin que nous puissions le comparer face à face avec les autres options.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRO5PKW4MRFSBGUFUNTPXQUOLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HSRO5PKW4MRFSBGUFUNTPXQUOLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HSRO5PKW4MRFSBGUFUNTPXQUOLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG63TUL52HSODFVREXG63TUL52HSODFVREXG63
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABKTSRMRIHC4OYDR52HLTHDPXQUOLANCNFSM4HPX3CHA
.

Je me demande également quelle est la taille de la «fraction significative» d'utilisateurs soucieux de la vitesse. Python lui-même est en moyenne environ 50 fois plus lent que C. Quelle fraction de la base d'utilisateurs NumPy l'exécute sur PyPy (ce qui donnerait une augmentation de vitesse 4 ×)? Certains, certes, mais je ne soupçonne pas un nombre très élevé.

Je suppose que vous n'êtes pas un utilisateur régulier :) NumPy est principalement C sous le capot, et est aussi rapide que de faire votre propre truc en C (enfin, plus rapide surtout). De plus, PyPy n'est pas prêt pour la production pour les applications scientifiques, et plus lent dans tous les cas (car il est limité à l'utilisation de l'API CPython utilisée par NumPy, il ne peut donc pas bénéficier des avantages de son JIT).

Quoi qu'il en soit, c'est hors sujet. Affirmer que la vitesse est importante n'est pas controversé.

@imneme nous utilisons la méthode lemires pour les entiers bornés. Depuis ce un
un nouveau départ sans héritage ni dépréciation avec lequel nous nous sommes efforcés de commencer
bons algorithmes.

Le lundi 27 mai 2019, 19:46 imneme [email protected] a écrit:

Pour quelques analyses et discussions, voir:
https://arxiv.org/pdf/1810.10985.pdf

Les manuels donnent des méthodes qui supposent implicitement ou explicitement que les PRNG peuvent
remplacer les vraies variables IIDU [0,1) sans introduire de matériel
erreur [20, 7, 2, 16, 15]. Nous montrons ici que cette hypothèse est incorrecte pour
algorithmes dans de nombreux progiciels statistiques couramment utilisés, y compris MATLAB,
Module aléatoire de Python, R, SPSS et Stata.

FWIW, il y a un joli article https://arxiv.org/abs/1805.10941 par @lemire
https://github.com/lemire sur la génération efficace d'un nombre dans une plage
sans parti pris. Je l'ai utilisé comme point de départ pour explorer et exécuter certains
benchmarks aussi dans mon propre article
http://www.pcg-random.org/posts/bounded-rands.html . (Lors de la génération
64 bits, la méthode de Lemire utilise une multiplication de 128 bits pour éviter
Division 64 bits, avec tous les problèmes familiers qui pourraient soulever pour MSVC
utilisateurs.)

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKNAQAK4WIXG5SVLO3PXQUA3A5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4VMVLDMZH0WWWK3TUL52HS4VMVLDMXG3TUL52HS4VMVLDMXGX63TUL52HS4DFVLDMZH0WWWK3TUL52HS4VMVLDMXGX63NVWWK3TUL52HS4DFVLDM
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABKTSRKV3KYKRLNMBKNU4JLPXQUA3ANCNFSM4HPX3CHA
.

Nous devrions fournir des valeurs par défaut _safe_, et ma meilleure estimation actuelle est que cela signifie des valeurs par défaut sûres à toutes fins, à l'exception de la cryptographie

C'est difficile à contester. Ma question est: qu'est-ce qui est sûr? Il y a juste différents degrés de quasi-aléatoire avec diverses propriétés. Jusqu'à présent, je n'ai vu personne donner un exemple concret, ni ici ni dans d'autres questions, PR ou discussions. Le simple fait de parler de propriétés statistiques abstraites n'est pas utile.

Mon sentiment est que PCG64 serait un bon défaut. L'inconvénient de vitesse sous Windows ne sera pas apparent pour les utilisateurs d'Anaconda et. al., et sera probablement corrigé à un moment donné. L'exécution parallèle étant la nouveauté de Python, je pense également qu'avoir des flux configurables est une propriété souhaitable.

Je suis très sceptique sur le fait que la pénalité de vitesse PCG64 sous Visual Studio est quelque chose qui ne peut pas être effacé.

Cela a-t-il été soigneusement évalué quelque part?

Affirmer que la vitesse est importante n'est pas controversé.

Ma question est: qu'est-ce qui est sûr?

Appliquer la logique de manière cohérente: «qu'est-ce qui est rapide»? Je n'ai pas une bonne idée de ce que les programmes numpy ont réellement les performances du BitGenerator comme goulot d'étranglement important. Si j'utilise un BitGenerator qui est deux fois plus rapide, vais-je obtenir une accélération de 5% dans mon calcul complet? Probablement même pas ça. Python-ne-pas-être-aussi-rapide-que-C n'est pas le problème; c'est juste que même les programmes lourds en PRNG qui sont réellement utiles ne passent pas beaucoup de temps dans le BitGenerator . L'un des choix disponibles est probablement suffisant.

Je suis très sceptique sur le fait que la pénalité de vitesse PCG64 sous Visual Studio est quelque chose qui ne peut pas être effacé.

Up-thread Je montre comment clang compile PCG64 en assemblage que nous pouvons voler pour MSVC 64 bits, donc non, je ne pense pas que MSVC sur Windows 64 bits soit un problème insurmontable.

Ce qui peut être plus délicat, c'est PCG64 sur les systèmes 32 bits, dont seul Windows 32 bits peut encore être pratiquement important pour nous. Dans ce cas, il s'agit moins de MSVC que de nous limiter à l'ISA 32 bits.

Ce que @Kellie Ottoboni [email protected] et moi
même pour des problèmes de taille modeste, l'espace d'états de MT est trop petit pour être approximé
permutations uniformes (n <2100) ou échantillons aléatoires uniformes (n = 4e8, k = 1000).

Cela affecte tout, du bootstrap aux tests de permutation en passant par MCMC.
La différence entre la distribution prévue et la distribution réelle
la distribution peut être arbitrairement grande (distance de variation totale approchant
2). C'est gros et c'est sérieux.

Nous n'avons fait aucun effort pour briser MT sur les fonctions "statistiques" dans un
quelques années. Je suis presque sûr qu'il y a un moyen systématique de le casser
(puisque les distances de distribution sont si grandes).

À votre santé,
Philippe

Le lun 27 mai 2019 à 12:26 Robert Kern [email protected]
a écrit:

Affirmer que la vitesse est importante n'est pas controversé.

Ma question est: qu'est-ce qui est sûr?

Appliquer la logique de manière cohérente: «qu'est-ce qui est rapide»? Je n'ai pas une super idée
quels programmes numpy ont réellement les performances du BitGenerator comme
un goulot d'étranglement important. Si j'utilise un BitGenerator deux fois plus rapide,
vais-je obtenir une accélération de 5% dans mon calcul complet? Probablement même pas ça.
Python-ne-pas-être-aussi-rapide-que-C n'est pas le problème; c'est juste que même
Les programmes chargés de PRNG qui sont réellement utiles ne dépensent pas énormément
heure dans BitGenerator. L'un des choix disponibles est probablement
suffisant.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWKSMPAG3GFUCUFRXCDPXQYVRA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWKMV3Q#issuecomment-496290542 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWIDCPAJJ6DJ3RO332LPXQYVRANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@pbstark Ce que j'aimerais voir, c'est une implémentation concrète du problème (qui pourrait être artificiel, mais pas trop artificiel) sur lequel MT ou un PRNG 128 bits échoue et sur lequel cryptorandom travaillerait. Pouvez-vous signaler un ensemble de données où la méthode de rééchantillonnage donne de mauvaises inférences avec un PRNG 128 bits et des inférences correctes avec cryptorandom ?

Le passage à PCG64 aggrave la limite inférieure de la taille du problème,
puisque son espace d'états est encore plus petit que celui de MT. Bien sûr, cela pourrait
produisent toujours un "meilleur" caractère aléatoire en ce sens qu'il pourrait échantillonner un sous-groupe de
groupe de permutation plus uniformément que MT. Mais ça doit s'effondrer avant
500 choisissez 10, et avant 21 !.

À votre santé,
Philippe

Le lun.27 mai 2019 à 12h30, Robert Kern [email protected]
a écrit:

Je suis très sceptique quant au fait que la pénalité de vitesse PCG64 sous Visual Studio est
quelque chose qui ne peut pas être effacé.

Up-thread je montre comment clang compile PCG64 en assemblage que nous pouvons voler
pour MSVC 64 bits, donc non, je ne pense pas que MSVC sur Windows 64 bits soit un
problème insurmontable.

Ce qui peut être plus délicat, c'est PCG64 sur les systèmes 32 bits, dont seulement 32 bits
Windows peut encore être pratiquement important pour nous. Dans ce cas, c'est moins
à propos de MSVC que de se limiter à l'ISA 32 bits.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWJFCINQCYGFCI7ULI3PXQZGLA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4DFVom3PNVWWK3TUL52HS4DFVOM12MZWWK3TUL52HS4DFVOMH12WWWK3TUL52HS4DFVom
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWK6QTB65Z4TJU76XKTPXQZGLANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Je ne sais pas assez sur les PRNG pour vraiment peser dans tous les cas, je veux juste que l'accent soit mis d'abord sur les propriétés statistiques (si la réponse est qu'elles sont toutes très très bonnes, très bien). Une chose que je me demande maintenant est l'équidistribution k-dimensionnelle. Utilisons-nous actuellement des variantes du PCG qui fonctionnent bien ici par rapport à MT? (Venant de dynamiques non linéaires, cela me rend un peu nerveux, mais je n'ai pas assez de vue d'ensemble sur les PRNG et je ne l'aurai pas dans les 2 prochains jours ...

Il semble peu probable que de nombreux utilisateurs de Windows 32 bits se soucient des performances de pointe. Le passage à 64 bits ne demande pas beaucoup d'efforts.

J'aimerais le voir aussi.

Nous savons - sur la base des calculs - qu'il doit y avoir beaucoup de gros problèmes,
mais nous ne pouvons pas encore citer d'exemple.

Le principe de précaution dirait que puisque nous savons qu'il y a
problèmes et nous savons comment les éviter (CS-PRNG), nous pourrions aussi bien faire
cela par défaut, et laissez les utilisateurs être moins prudents s'ils choisissent de l'être.

Le lun.27 mai 2019 à 12:39 PM Robert Kern [email protected]
a écrit:

@pbstark https://github.com/pbstark Ce que j'aimerais voir est un béton
mise en œuvre du problème (pourrait être artificiel, mais pas trop artificiel) sur
sur lequel MT ou un PRNG 128 bits échoue et sur lequel le cryptage aléatoire fonctionnerait. Peut tu
signaler un ensemble de données là où la méthode de rééchantillonnage donne une mauvaise
inférences avec un PRNG 128 bits et inférences correctes avec crypto-aléatoire?

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWODTUIPNMVOJB6QP3DPXQ2FPA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4VMXQ2FPA5CNFSM4HPX3CHKYY3PNVWWK3TUL52HS4VMFVOMBH24WWWK3TUL52HS4DFVOMX63NVWWK3TUL52HS4DFVOMX63NVWWWK3TUL52HS4VMFVom
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWITAGQFZDQSIFNEHETPXQ2FPANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

k-equidistribution est une propriété d'ensemble des sorties PRNG sur l'ensemble
période du PRNG. C'est une bonne chose, mais ça ne dit rien sur les autres
types d'échecs aléatoires, tels que la corrélation en série des sorties.
C'est une barre relativement basse.

Le lun 27 mai 2019 à 12 h 48, Sebastian Berg [email protected]
a écrit:

Je ne connais pas assez les PRNGs pour vraiment peser en tout cas, je veux juste
l'accent doit être mis d'abord sur les propriétés statistiques (si la réponse est que
ils sont tous très très bons, très bien). Une chose que je me demande maintenant est la
équidistribution k-dimensionnelle. Utilisons-nous actuellement des variantes de disons PCG
qui font bien ici par rapport à MT? (Venant de la dynamique non linéaire, que
me rend un peu nerveux, mais je n'ai pas assez de vue d'ensemble sur les PRNG et je
ne l'obtiendrai pas dans les 2 prochains jours ...

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWPOBTPYHC3XBINQYA3PXQ3HZA5CNFSM4HPX3CHKYY3PNVWWWK3TUL52HS4DFVMNVWWWK3TUL52HS4DFVMNVWWWK3TUL52HS4DFVMNVWWWK3TUL52HS4DFVMNVWWWK3TUL52HS4DFVMNVWWWK3TUL52HS4DFVREM
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWOEB7KR2YJZWHRRAHLPXQ3HZANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@pbstark MT échoue à un certain nombre de tests statistiques que PCG (et d'autres générateurs) réussit.

@rkern

Si l'on veut que MSVC génère l'instruction ror, je pense qu'il faut utiliser l'intrinsèque "_rotr64".

On peut également préférer l'indicateur '/ O2' pour l'optimisation.

En regardant cela, il serait peut-être préférable de l'écrire en assemblage, si l'on veut utiliser PCG64.

Pour @pbstark , voici une sortie du PCG-64 initialisée avec une graine inconnue de vous (en fait, je vais même vous dire le flux, c'est 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

Maintenant, supposons que vous initialisiez un autre générateur pcg avec une graine choisie au hasard. Prenons, par souci d'argumentation 0xb124fedbf31ce435ff0151f8a07496d3 . Combien de sorties devons-nous générer avant de découvrir cette sortie connue? Parce que je connais la graine que j'ai utilisée ci-dessus, je peux répondre que (via la fonction de distance de PCG), environ 2,5 × 10 ^ 38 (ou environ 2 ^ 127,5) sorties. Pour référence, 10 ^ 38 nanosecondes, c'est 230 milliards de fois l'âge de l'univers.

Il y a donc une séquence dans PCG-64 qui est vraiment là-dedans, mais, pratiquement parlant, vous ne la trouverez jamais à moins que je ne vous dise où chercher. (Et il y aurait encore plus de possibilités si nous varions le flux.)

Le PCG régulier n'a en fait aucune chance de produire une pièce de Shakespeare; le schéma de génération étendue PCG peut en fait produire un Shakespeare Play, mais la chance qu'il le fasse dans un scénario non artificiel est tellement infinitésimale qu'elle est essentiellement nulle également. À mes yeux, il y a très peu de valeur dans une propriété qui n'a aucune conséquence pratique.

(De plus, les PRNG sécurisés par cryptographie ne sont pas garantis d'être répartis équidistribués en K dimensions, ni ne sont-ils une solution magique pour les personnes qui veulent des PRNG capables de générer toutes les séquences possibles. Le moment où vous voulez plus de bits d'un PRNG seed et stocke comme son état, il y a forcément des séquences de bits qui ne peuvent pas être générées (preuve: par le principe du casier). Et si vous vous limitez à la même quantité de sortie que vous mettez en tant que seed, qu'est-ce que vous ' Vous recherchez vraiment une fonction de hachage, ou peut-être juste la fonction d'identité si votre entrée de départ est vraiment aléatoire, pas un PRNG.)

Par curiosité, j'ai terminé un générateur de bits de compteur AES en utilisant aesctr.h , temps en ns par valeur aléatoire:

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

Beau travail, @bashtage.

Quelques points à garder à l'esprit, l'un est que les instructions AES spécifiques peuvent varier selon les architectures et ne sont pas présentes dans tous les processeurs activement utilisés, il doit donc y avoir un chemin de secours (lent).

En outre, c'est un peu une comparaison de pommes à oranges. En plus d'utiliser des instructions spécialisées, le code AES obtient une partie de sa vitesse à partir du déroulement de la boucle - il génère en fait des nombres dans des blocs, puis les lit. Le déroulement peut potentiellement accélérer n'importe quel PRNG. FWIW, @lemire a en fait une version vectorisée de PCG qui utilise des instructions AVX pour générer plusieurs sorties à la fois.

Voyons si je peux résumer au moins un point de consensus: nous sommes tous d'accord pour dire que numpy devrait avoir une opinion sur l'algorithme BitGenerator à utiliser et pour en promouvoir un BitGenerator rapport aux autres.

Permettez-moi encore une fois d'esquisser une option "Pas de défaut" qui concorde avec ce consensus et évite certains des problèmes que certaines des autres options pourraient avoir. S'il n'y a pas de traction, je vais me taire.

Ce que je voulais vraiment dire par l'option "Aucune valeur par défaut" était "Aucune valeur par défaut anonyme". Il existe encore des moyens de concevoir l'API de telle sorte que le moyen le plus pratique d'obtenir un Generator amorcé soit celui qui nomme le PRNG que nous nommons. Par exemple, disons que nous _ n'incluons pas_ une gamme complète d'algorithmes BitGenerator . Nous essayons de garder numpy assez minime et de laisser le complétionnisme à scipy et à d'autres bibliothèques tierces, en général, et cela peut être une bonne idée de le faire ici. La beauté de l'architecture actuelle est qu'elle nous permet de déplacer ces BitGenerator vers d'autres bibliothèques. Disons donc que nous ne fournissons que MT19937 pour supporter l'héritage RandomState et celui BitGenerator que nous préférons utiliser. Par souci d'argumentation, disons que c'est Xoshiro256 . Faisons en sorte que le constructeur Generator.__init__() nécessite un BitGenerator . Mais aussi, définissons une fonction np.random.xoshiro256_gen(seed) qui renvoie Generator(Xoshiro256(seed)) sous les couvertures. Nous documentons cette fonction de commodité comme le moyen d'obtenir un Generator .

Maintenant, avancez rapidement quelques versions. Disons que nous avons repoussé PCG64 , ThreeFry , etc. à random-tng ou scipy ou un autre package, et l'un d'eux devient populaire en raison de les fonctionnalités supplémentaires ou les nouvelles failles statistiques se trouvent dans Xoshiro256 . Nous décidons de mettre à jour l'opinion de numpy sur les BitGenerator gens devraient utiliser pour PCG64 . Ensuite, nous ajoutons la classe PCG64 BitGenerator et ajoutons la fonction np.random.pcg64_gen(seed) . Nous ajoutons un avertissement d'obsolescence à np.random.xoshiro256_gen(seed) pour dire que ce n'est plus l'algorithme préféré: nous recommandons que le nouveau code utilise np.random.pcg64_gen(seed) , mais de continuer à utiliser l'algorithme Xoshiro256 sans avertissements, ils doivent utiliser explicitement Generator(Xoshiro256(seed)) .

Je pense que cela évite certains des problèmes que j'ai avec une API "anonyme" (ie Generator(seed) , Generator(DefaultBitGenerator(seed)) , np.random.default_gen(seed) ). Nous pouvons prendre en charge les algorithmes qui ne sont plus préférés à perpétuité. Nous n'aurons jamais besoin de faire faire quelque chose de différent à notre constructeur "préféré" lorsque nous changeons d'avis. Parce que nous utilisons les vrais noms pour distinguer les choses plutôt que les numéros de version, vous savez toujours comment mettre à jour le code pour reproduire les anciens résultats (si vous ne pouvez pas supporter les avertissements inoffensifs pour une raison quelconque). Vous pouvez même récupérer du code sans provenance ou des numéros de version numpy enregistrés et faire cette mise à jour. En même temps, en limitant le nombre d'algorithmes à un minimum absolu et en faisant des meilleures pratiques la manière la plus simple de travailler avec la bibliothèque, nous sommes en mesure d'exprimer l'opinion de numpy de manière efficace.

Comment ça sonne? Nous devrions encore faire beaucoup d'efforts pour faire un choix solide pour ce BitGenerator pour la première version. C'est toujours conséquent.

Il semble peu probable que de nombreux utilisateurs de Windows 32 bits se soucient des performances de pointe. Le passage à 64 bits ne demande pas beaucoup d'efforts.

Je suis d'accord. Comme le problème avec PCG64 sur Win32 est simplement la performance (et que nous pouvons probablement améliorer avec quelques efforts), conviendrez-vous que ce n'est pas un bloqueur?

Si l'on veut que MSVC génère l'instruction ror, je pense qu'il faut utiliser l'intrinsèque "_rotr64".

On peut également préférer l'indicateur '/ O2' pour l'optimisation.

Merci! @imneme m'a signalé toutes ces erreurs. :-)

@seberg Avez-vous des citations de votre domaine qui ont conduit à votre méfiance? Par exemple, un article qui a montré que la propriété d'équidistribution k = 623 de MT19937 résolvait des problèmes dans les simulations dynamiques non linéaires qu'un PRNG plus petit provoquait? Je pourrais peut-être fournir une assurance plus précise avec cela comme référence. En _ général_, mon point de vue sur l'équidistribution est que vous voulez généralement que l'équidistribution du PRNG soit proche du maximum autorisé par la taille de l'état du PRNG. En _pratique_, si votre PRNG est assez grand pour vos besoins à d'autres égards (passe PractRand, a une période plus grande que le carré du nombre d'échantillons que vous prévoyez de dessiner, etc.), je n'ai jamais vu beaucoup de raisons de s'inquiéter du précis k . D'autres peuvent avoir des opinions différentes et il y a peut-être des problèmes spécifiques dans votre domaine dont je ne suis pas au courant. Si tel est le cas, il existe des solutions spécifiques!

Par curiosité, j'ai mis au point un générateur de bits de compteur AES en utilisant aesctr.h

Je peux me tromper, mais je ne pense pas que cela aiderait à répondre aux préoccupations de k! pour des k . Le compteur est toujours un nombre de 128 bits et une fois qu'il est retourné, vous avez atteint la fin de la période. @pbstark préconise des

En général, mon avis sur l'équidistribution est que vous voulez généralement que l'équidistribution du PRNG soit proche du maximum autorisé par la taille de l'état du PRNG.

Bien que certains considèrent l'équidistribution maximale comme une propriété souhaitable, elle peut également être considérée comme un défaut (et il existe des articles qui le disent). Si nous avons un PRNG _k_ bits et que chaque séquence _k_ bits se produit exactement une fois, cela finira par violer le problème d'anniversaire qui dit que nous nous attendons à voir une sortie se répéter après environ 2 ^ (k / 2) sorties. ( J'ai écrit un test statistique de problème d'anniversaire basé sur ces idées . Il a correctement détecté l'absence statistiquement invraisemblable de toute répétition dans SplitMix, un PRNG 64 bits en sortie 64 bits, et Xoroshiro64 +, 32 bits en sortie 64 bits -État PRNG équidistribué en 2 dimensions, entre autres.)

Fait intéressant, bien qu'il soit très pratique d'écrire un test statistique qui échouera à un PRNG faute de répétitions 64 bits (ou trop de répétitions - nous nous attendons à une distribution de Poisson), il n'est au contraire pas pratique d'écrire un test qui va détecter l'omission de 36,8% de toutes les valeurs 64 bits si nous ne savons pas lesquelles sont omises.

Évidemment, tester la faille de manque de répétitions attendues commence à être impossible à exécuter lorsque _k_ devient plus grand, mais à mesure que nous arrivons à une taille d'état (et à une période) de plus en plus grande, la taille ajoutée signifie qu'il est à la fois impossible de montrer que un PRNG distribué au maximum équidistribué est défectueux pour ne pas se répéter et tout aussi impraticable pour montrer qu'un PRNG non distribué équidistiblement au maximum est défectueux pour répéter certaines séquences de _k_ bits (d'une manière statistiquement plausible) et en omettre complètement d'autres. Dans les deux cas, le PRNG est trop grand pour que nous puissions distinguer les deux.

J'aimerais le voir aussi. Nous savons - sur la base des calculs - qu'il doit y avoir beaucoup de gros problèmes, mais nous ne pouvons pas encore donner d'exemple. Le principe de précaution dirait que puisque nous savons qu'il y a de gros problèmes et que nous savons comment les prévenir (CS-PRNG), nous pourrions aussi bien le faire par défaut, et laisser les utilisateurs être moins prudents s'ils choisissent de l'être.

Je dois dire que je ne suis pas convaincu par cette argumentation et cette preuve. En tant que tel, je ne me sentirais pas à l'aise devant les utilisateurs engourdis et leur dire qu'ils devraient changer pour cette raison. Je ne suis pas équipé pour défendre cette déclaration, c'est pourquoi je demande ces exemples. Celles-ci seraient convaincantes et me prépareraient à défendre cette position que vous recommandez que nous adoptions.

Il existe de nombreuses propriétés par lesquelles les PRNG finis sont en deçà des vrais RNG. Et il y a beaucoup de calculs que nous voulons faire qui, en théorie, dépendent de ces propriétés des vrais RNG (ou du moins, nous n'avons pas rigoureusement prouvé à quel point nous pouvons les détendre). Mais bon nombre de ces lacunes n'ont qu'un très petit effet pratiquement imperceptible sur les résultats des calculs réels que nous effectuons. Ces violations ne sont pas des choses décisives, tout ou rien, de type «go / no go». Ils ont une taille d'effet, et nous pouvons tolérer plus ou moins de l'effet.

Vous montrez, de manière convaincante, bien sûr, que les PRNG de certaines tailles ne peuvent pas générer uniformément toutes les permutations k! pour certains k . L'étape qui me manque est de savoir comment cet échec de pouvoir générer toutes ces permutations affecte un calcul concret que je serais intéressé à effectuer. Je manque le test que vous recommanderiez d'ajouter à PractRand ou TestU01 qui démontrerait le problème aux gens.

L'une des lignes d'analyse que j'ai trouvées très informatives dans l'article PCG de @imneme était de dériver plusieurs versions de plus petits états de chaque PRNG et de voir exactement où elles ont commencé à échouer TestU01. Cela nous donne un moyen de comparer normalement les architectures PRNG plutôt que de simplement dire, tout ou rien, "X PRNG réussit" ou échoue. Cela nous permet également d'estimer la marge de sécurité dont nous disposons aux tailles d'état utilisées (qui réussissent TestU01 pour un grand nombre de Gio d'échantillons). Existe-t-il des calculs concrets que vous pouvez faire qui démontrent un problème avec les PRNG 8 bits? PRNG 16 bits? Ensuite, nous pouvons voir si ce nouveau test nous donnerait des informations sur le PRNG plus tôt que TestU01 / PractRand ne le fait actuellement. Et ce serait très utile.

D'un autre côté, s'il faut plus de données tirées du PRNG pour montrer un échec basé sur ces permutations que le point d'échec des petits PRNG pour la suite de tests actuelle dans PractRand, alors je conclurais que ce problème n'est pas pratique inquiétude, et nous pourrions utiliser "passe PractRand" comme un bon proxy pour savoir si le problème de permutation aurait ou non un effet pratique sur mes calculs.

Mais tant que je n'ai pas en main un programme que je peux exécuter et montrer aux gens le problème, je ne suis pas à l'aise pour pousser pour un CS-PRNG sur cette base. Je serais incapable d'expliquer le choix de manière convaincante.

Pour ceux qui l'exigent en mélangeant 32 éléments, tous les 32! les mélanges (c'est-à-dire tous les 263130836933693530167218012160000000 d'entre eux) devraient être générables, plutôt qu'un PRNG qui ne fournit qu'un échantillon aléatoire parmi ces 32! shuffles, je dirais en fait que si vous exigez des nombres énormes, vous ne pensez pas assez grand .

Je dirais donc (facétieusement) que l'ordre dans lequel ces shuffles sortent ne doit pas non plus être prédéterminé! De toute évidence, vous devriez exiger qu'il produise tous les 32! mélange dans tous les ordres possibles - (32!)! c'est ce dont vous avez besoin! Bien sûr, cela nécessitera 3,8 × 10 ^ 18 exaoctets pour l'état et une quantité similaire d'entropie pour initialiser, mais cela vaut sûrement la peine de savoir que tout est là.

... Nous ajoutons un avertissement d'obsolescence à np.random.xoshiro256_gen(seed) pour dire que ce n'est plus l'algorithme préféré: nous recommandons que le nouveau code utilise np.random.pcg64_gen(seed) , mais pour continuer à utiliser l'algorithme Xoshiro256 sans avertissement, ils doivent explicitement utiliser Generator(Xoshiro256(seed))

Cela dérange toujours les utilisateurs à la fois avec une dépréciation et avec des noms étranges qu'ils ne veulent vraiment pas connaître.

Le NEP dit: Deuxièmement, la rupture de la compatibilité des flux afin d'introduire de nouvelles fonctionnalités ou d'améliorer les performances sera autorisée avec prudence.

S'il y a une raison suffisante pour mettre à jour notre valeur par défaut, alors simplement faire cela et rompre la reproductibilité bit à bit pour les utilisateurs qui n'ont pas spécifié explicitement un algorithme est la meilleure option à mon humble avis (et celle que vous aviez auparavant).

Ce que je voulais vraiment dire par l'option "Aucune valeur par défaut" était "Aucune valeur par défaut anonyme".

Alors, est-ce que vous voulez que les utilisateurs connaissent le nom du PRNG qu'ils utilisent?

Regardez cela du point de vue des utilisateurs. Il est déjà assez difficile de les faire passer de np.random.rand & co à np.random.RandomState() , puis d'utiliser des méthodes. Nous allons maintenant introduire un meilleur système, et ce qu'ils voient est np.random.xoshiro256_gen() ? Ce serait une grande utilisabilité de régression.

Alors, est-ce que vous voulez que les utilisateurs connaissent le nom du PRNG qu'ils utilisent?

Non, c'est pour atténuer les problèmes d'avoir une API "cible mobile désignée" comme default_generator(seed) laquelle les gens travaillaient (par exemple l' argument version @shoyer).

Le maintien de la compatibilité des flux (ce que NEP 19 rejette) est secondaire à la rupture de l'API. Différents BitGenerator s ont des API efficaces différentes, en fonction de leurs ensembles de fonctionnalités (flux réglables, jumpahead, principalement, bien qu'il puisse y en avoir d'autres, selon le degré de paramétrage du PRNG). Ainsi, certains changements dans notre sélection PRNG par défaut briseraient en fait le code (c'est-à-dire ne s'exécuteraient plus ou ne fonctionneraient plus correctement), et ne changeraient pas simplement les valeurs qui sortent.

Par exemple, disons que nous choisissons d'abord PCG64 . Il a un état de 128 bits, 2 ^ 127 flux réglables et implémente jumpahead; agréable et complet. Alors les gens commencent à écrire default_generator(seed, stream=whatever) . Supposons maintenant que les travaux futurs y trouvent une faille statistique majeure qui nous donne envie de passer à autre chose. Le prochain PRNG que nous promouvons par défaut doit avoir un état> = 128 bits (facile; je ne recommanderais rien de plus petit comme valeur par défaut à usage général), jumpahead (difficile!),> = 2 ^ 127 flux réglables (whoo, boy!), afin de ne pas casser les utilisations de default_generator() qui existent déjà dans le code. Maintenant, peut-être pouvons-nous vivre avec ce cliquet.

@shoyer a suggéré que nous pourrions peut-être faire en sorte que la valeur par défaut BitGenerator toujours délibérément entravée uniquement par les caractéristiques les moins communes. Ça marcherait! Mais cela manquerait également l'occasion de promouvoir des flux réglables pour résoudre le problème des flux parallèles comme @charris aimerait le faire.

Nous allons maintenant introduire un meilleur système, et ce qu'ils voient est np.random.xoshiro256_gen() ? Ce serait une grande utilisabilité de régression.

Si le nom au son étrange est le problème, je serais heureux d'utiliser un nom générique plus convivial, tant que la politique est par ailleurs la même (nous ajoutons une nouvelle fonction et commençons à avertir de l'ancienne). Je considérerais cet équivalent. Nous ne devrions pas faire cela trop souvent.

Je vais bien aussi si nous décidons de vivre avec le cliquet et d'éviter un mécanisme version .

Mon point de vue sur un "défaut" est que nous pourrions le laisser comme détail d'implémentation, de sorte que Generator() fonctionnerait toujours. Je voudrais souligner cela avec une forte mise en garde sur le fait que la seule façon d'obtenir des résultats toujours reproductibles (jusqu'aux changements dans Generator) est d'utiliser la syntaxe Generator(BitGenerator(kwarg1=1,kwargs2=b,...))

Il n'est pas pratique de vraiment masquer les détails de l'implémentation car l'accès à l'état est un décapage obligatoire.

L'alternative consiste simplement à la traiter comme n'importe quelle autre fonction - et à la génération aléatoire en général - et à passer par un cycle de dépréciation standard en cas de besoin impérieux de changement. Cela n'affectera jamais les utilisateurs qui font les choses correctement, et avec suffisamment d'avertissements dans la documentation, il pourrait être possible d'obtenir un taux de réussite décent à ce sujet, du moins parmi les grands projets. Ce que je suggère ici, c'est que l'on pourrait oublier que la garantie de compatibilité de flux n'a jamais été faite en pensant à la nouvelle API.

@bashtage dans # 13650 J'ai interdit l'accès à Generator().bit_generator d'une manière qui permet toujours le décapage sans accès direct à state . Il transmet le test_pickle légèrement réécrit d'une manière qui permettrait une utilisation à travers python Thread s

Ma question est: qu'est-ce qui est sûr? Il y a juste différents degrés de quasi-aléatoire avec diverses propriétés. Jusqu'à présent, je n'ai vu personne donner un exemple concret, ni ici ni dans d'autres questions, PR ou discussions.

"Passe PractRand à _N_ Gio" pour certains _N_ (512, 1024) est une définition passable si vous voulez un critère de réussite / échec clair et clair. Si vous voulez un exemple concret, MT et ses variantes seront exclus sur la base de ce critère. Nous avons également retiré certains des membres plus âgés de la famille Xoroshiro du PR à cause de cela.

Si vous voulez une vue plus sophistiquée de la qualité statistique qui permet un classement ordinal des algorithmes, je vous recommande la section 3 de l'article PCG de @imneme , qui utilise des profils de variantes à état réduit des algorithmes pour avoir une idée de comment beaucoup de "marge de manœuvre" pour chaque algorithme complet. Ceci est assez similaire à la façon dont les cryptographes analysent différents algorithmes de cryptographie. Toute option viable examinée doit passer le critère de base de «ne pas être brisé», mais cela ne vous aide pas à classer les candidats. Au lieu de cela, ils créent des versions à cycle réduit des algorithmes des concurrents et voient à quel point vous devez obtenir une réduction avant de pouvoir le casser. Si un algorithme complet à N rounds est cassé en N-1, alors il y a très peu de marge et les cryptographes l'éviteraient probablement. De même, si un BitGenerator 128 bits passe PractRand mais que sa version 120 bits échoue, c'est probablement assez risqué.

@mattip Cela semble raisonnable. Bien que quelqu'un quelque part le fera

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

s'ils veulent approfondir cela, c'est très bien. Je veux juste aider l'utilisateur non expert

Il transmet le test_pickle légèrement réécrit d'une manière qui permettrait une utilisation à travers python Thread s

Il est à noter qu'il s'agit d'un problème en suspens (# 9650) - idéalement Generator() réensemencerait dans les threads enfants. IIRC c'est seulement pratique en Python> = 3.7

Mon point de vue sur un "défaut" est que nous pourrions le laisser comme détail d'implémentation, de sorte que Generator() fonctionnerait toujours. Je voudrais souligner cela avec une forte mise en garde sur le fait que la seule façon d'obtenir des résultats toujours reproductibles (jusqu'aux changements dans Generator) est d'utiliser la syntaxe Generator(BitGenerator(kwarg1=1,kwargs2=b,...))

Il y a deux types de reproductibilité que nous devons distinguer. La première est que j'exécute mon programme deux fois avec la même graine et que j'obtiens les mêmes résultats. C'est celui que nous devons soutenir. L'autre est la reproductibilité entre les versions de numpy, que nous avons rejetée, du moins dans le sens le plus strict.

Sans argument Generator() , c'est-à-dire "donnez-moi un PRNG à départ arbitraire que numpy recommande" n'est pas le cas d'utilisation principal. Cela ne nécessite pas beaucoup de soutien. "Donnez-moi le PRNG que numpy recommande avec _this_ seed" est, et c'est ce pour quoi nous discutons des options. Nous avons besoin d'un moyen pour numpy d'exprimer une opinion sur la façon d'obtenir un PRNG prédéfini, et cette manière doit être facile et pratique pour les utilisateurs (sinon ils ne l'utiliseront pas). J'aime nommer l'algorithme (bien que par le biais d'une fonction plus pratique), mais @rgommers pense que c'est un pas trop loin, et je suis sensible à cela.

Le générateur sans argument (), c'est-à-dire "donnez-moi un PRNG à départ arbitraire que numpy recommande" n'est pas le cas d'utilisation principal. Cela ne nécessite pas beaucoup de soutien. "Donnez-moi le PRNG que numpy recommande avec cette graine" est, et c'est ce pour quoi nous discutons des options. Nous avons besoin d'un moyen pour numpy d'exprimer une opinion sur la façon d'obtenir un PRNG prédéfini, et cette manière doit être facile et pratique pour les utilisateurs (sinon ils ne l'utiliseront pas). J'aime nommer l'algorithme (bien que par le biais d'une fonction plus pratique), mais @rgommers pense que c'est un pas trop loin, et je suis sensible à cela.

Je dirais que les utilisateurs sont en fait mal équipés pour fournir de bonnes semences. Par exemple, combien d'utilisateurs connaissent la bonne façon de semer le Mersenne Twister? Ce n'est pas aussi facile que vous le pensez - si vous ne lui donnez pas 624 entiers 32 bits aléatoires (pour fournir 19937 bits d'état), vous le faites mal.

Donc, en fait, je dirais que la bonne façon pour l'utilisateur d'obtenir des résultats reproductibles est de créer le PRNG (sans fournir de graine, en le laissant être bien ensemencé automatiquement), puis de le décaper.

Si la discussion porte uniquement sur la bonne voie alors je suis pour
Generator(BitGenerator(**kwargs)) car il ne sera utilisé que par
utilisateurs de semi-logiciels soucieux de reproduire.

Je pense que la valeur par défaut utilisée pour Generator() compte car ce sera
interprété autant comme un choix réfléchi et donc le prendre comme un
recommandation lors de l'utilisation de la forme ensemencée.

Juste pour en jeter un de plus, une méthode de classe 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 seeded`.

Le mar 28 mai 2019, 16:38, Robert Kern [email protected] a écrit:

Mon avis sur un "défaut" est que nous pourrions le laisser comme implémentation
détail, de sorte que Generator () fonctionne toujours. Je voudrais parler avec un
forte mise en garde que le seul moyen d'obtenir des résultats toujours reproductibles
(jusqu'aux changements dans Generator) est d'utiliser la syntaxe
Générateur (BitGenerator (kwarg1 = 1, kwargs2 = b, ...))

Il y a deux types de reproductibilité que nous devons distinguer. L'un est
que j'exécute mon programme deux fois avec la même graine et que j'obtiens les mêmes résultats.
C'est celui que nous devons soutenir. L'autre est la reproductibilité à travers
versions de numpy, que nous avons rejetées, du moins dans le sens le plus strict.

Générateur sans argument (), c'est-à-dire "donnez-moi un PRNG arbitrairement amorcé qui
numpy recommande "n'est pas le cas d'utilisation principal. Il ne nécessite pas beaucoup
soutien. "Donnez-moi le PRNG que numpy recommande avec cette graine" est,
et c'est pour cela que nous discutons des options. Nous avons besoin d'un moyen pour que numpy
exprimer une opinion sur la façon d'obtenir un PRNG semé, et de cette façon doit être
facile et pratique pour les utilisateurs (sinon ils ne l'utiliseront pas). J'aime
nommer l'algorithme (bien que par une fonction plus pratique), mais
@rgommers https://github.com/rgommers pense que c'est un pas trop loin, et
Je suis sympathique à cela.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKA4SSNW6XZEVFUMCDPXVGW5A5CNFSM4HPX3CHKYY3PNVWWWK3TUL52HS4DFVomDH7GVWWK3TUL52HS4DFVom
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABKTSROCMLHG6E6BLWI6TWDPXVGW5ANCNFSM4HPX3CHA
.

Le mar 28 mai 2019, 16:38, Robert Kern [email protected] a écrit:

Mon avis sur un "défaut" est que nous pourrions le laisser comme implémentation
détail, de sorte que Generator () fonctionne toujours. Je voudrais parler avec un
forte mise en garde que le seul moyen d'obtenir des résultats toujours reproductibles
(jusqu'aux changements dans Generator) est d'utiliser la syntaxe
Générateur (BitGenerator (kwarg1 = 1, kwargs2 = b, ...))

Il y a deux types de reproductibilité que nous devons distinguer. L'un est
que j'exécute mon programme deux fois avec la même graine et que j'obtiens les mêmes résultats.
C'est celui que nous devons soutenir. L'autre est la reproductibilité à travers
versions de numpy, que nous avons rejetées, du moins dans le sens le plus strict.

Générateur sans argument (), c'est-à-dire "donnez-moi un PRNG arbitrairement amorcé qui
numpy recommande "n'est pas le cas d'utilisation principal. Il ne nécessite pas beaucoup
soutien. "Donnez-moi le PRNG que numpy recommande avec cette graine" est,
et c'est pour cela que nous discutons des options. Nous avons besoin d'un moyen pour que numpy
exprimer une opinion sur la façon d'obtenir un PRNG semé, et de cette façon doit être
facile et pratique pour les utilisateurs (sinon ils ne l'utiliseront pas). J'aime
nommer l'algorithme (bien que par une fonction plus pratique), mais
@rgommers https://github.com/rgommers pense que c'est un pas trop loin, et
Je suis sympathique à cela.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=ABKTSRKA4SSNW6XZEVFUMCDPXVGW5A5CNFSM4HPX3CHKYY3PNVWWWK3TUL52HS4DFVomDH7GVWWK3TUL52HS4DFVom
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABKTSROCMLHG6E6BLWI6TWDPXVGW5ANCNFSM4HPX3CHA
.

Du point de vue de la convivialité, je pense que nous devons vraiment prendre en charge Generator(seed) . Sinon, face à un choix qu'ils ne sont pas prêts à faire, les utilisateurs vont simplement s'en tenir à RandomState .

Pour le versionnage du générateur de bits par défaut dans Generator , nous pourrions utiliser bit_version=1 au lieu de version=1 , même si je suis également d'accord pour abandonner l'idée version . Je ne pense pas que les utilisateurs auront besoin de définir des générateurs de bits explicitement très souvent.

Ma préférence pour la résolution de cas d'utilisation spécifiques nécessitant des fonctionnalités de générateur particulières serait de concevoir de nouvelles API BitGenerator génériques qui masquent les détails de mise en œuvre. Ceux-ci peuvent être ajoutés à DefaultBitGenerator ou placés dans de nouvelles classes si leur utilisation implique des compromis, par exemple ParallelBitGenerator .

Je voudrais certainement éviter les avertissements concernant les changements futurs dans le flux RNG en raison de la modification du générateur de bits par défaut. Ces avertissements ne feraient que du bruit pour la grande majorité des utilisateurs qui ne se fient pas à de tels détails, mais qui définissent un seed dans Generator juste pour empêcher leurs nombres aléatoires de changer spontanément.

les utilisateurs vont simplement s'en tenir à RandomState.

C'est bien, ils ne sont pas les premiers à adopter. Je pousse fort (peut-être trop dur?) Pour l'API viable minimale possible, car nous pouvons toujours élargir l'API mais il est beaucoup plus difficile de la réduire. Les nuances entre Generator(Philox()) , Generator(seed(3)) et Generator(bit_version=1) sont un peu difficiles à voir jusqu'à ce que cela parvienne aux utilisateurs finaux.

Sortons une première version sans Generator(seed) et obtenons quelques commentaires.

Sortons une première version sans Generator(seed) et obtenons quelques commentaires.

OK, je n'ai aucune objection sérieuse ici. Dans ce cas, nous pourrions tout aussi bien exiger de spécifier le BitGenerator complet pour le moment.

Donc, en fait, je dirais que la bonne façon pour l'utilisateur d'obtenir des résultats reproductibles est de créer le PRNG (sans fournir de graine, en le laissant être bien ensemencé automatiquement), puis de le décaper.

Je dis la même chose, mais je reçois très peu de traction avec ça. Comme vous le dites, "Eh bien, ce n'est pas parce que quelque chose est une mauvaise idée que les gens ne voudront pas le faire!"

Une partie du problème est exacerbée par l'état énorme de MT, qui nécessite vraiment une sérialisation vers un fichier. Il est tout simplement difficile de faire de cette danse basée sur des fichiers l'API la plus simple disponible pour que les utilisateurs veuillent l'utiliser. Les choses iront mieux avec un PRNG par défaut avec un état beaucoup plus petit. 128 bits est la taille d'un UUID, qui est à peu près assez petit pour être imprimé en hexadécimal et copier-coller. Donc, un bon modèle pourrait être d'écrire votre programme de telle sorte qu'il par défaut sur une bonne source d'entropie, puis imprime son état de manière à pouvoir le copier-coller la prochaine fois que vous exécutez le programme.

❯ 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

Je ne sais pas si un modèle comme celui-ci fournirait à la fois la simplicité et la preuve future.

NumPy 1. suivant

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

NumPy 1.20.x

`` python
Générateur de classe:
def __init __ (bitgen_or_seed = None, *, bit_generator = None, inc = None):
si bit_generator n'est pas None ou inc n'est pas None:
warn ("La valeur par défaut passe de PCG64 à AESCtr. Le mot clé inc"
`` l'argument est obsolète et augmentera à l'avenir '', FutureWarning)
`` ``

NumPy 1.22

`` python
Générateur de classe:
def __init __ (bitgen_or_seed = None, *, bit_generator = 'aesctr', inc = None, counter = 0):
si bit_generator == 'pcg64' ou inc n'est pas None:
lever l'exception ('PCG n'est plus pris en charge et inc a été supprimé')
`` ``

Je ne sais pas si un modèle comme celui-ci fournirait à la fois la simplicité et la preuve future.

Comme je l'ai noté ci-dessus dans https://github.com/numpy/numpy/issues/13635#issuecomment -496589421, je pense que ce serait surprenant et frustrant pour la plupart des utilisateurs. Je préfère exiger de fournir un objet BitGenerator explicite que de prévoir de commencer à émettre des avertissements si les utilisateurs n'ont pas défini tous les arguments facultatifs. Cela devrait vraiment être un dernier recours, pour les cas où nous découvrons que les API sont cassées d'une manière que nous n'avions pas anticipée.

Le problème, c'est la période de transition. Toute personne utilisant les valeurs par défaut recevra soudainement des avertissements sans aucun moyen de basculer pendant la période de transition. Ou du moins, vous les faites passer d'un "état de basse énergie" de Generator(seed) à un "état de haute énergie" moins pratique de Generator(seed, bit_generator='aesctr') . Étant donné que l'objectif de cette API était de fournir un «état de basse énergie» pratique, nous avons échoué dans notre objectif pendant cette transition. Nous l'avons fait une fois avec l'une de nos fonctions d'histogramme, l'IIRC, et ce fut un cauchemar.

Ceci est commun à toutes les dépréciations qui tentent de changer la signification des arguments en place. Les dépréciations qui vous permettent de passer d'une fonction à une autre sont beaucoup plus faciles à gérer, et c'est ce que je préconisais.

Sortons une première version sans Generator(seed) et obtenons quelques commentaires.

Par «première version», voulez-vous dire une version complètement numpy? Ou simplement faire fusionner le PR (ce qui s'est produit depuis)?

S'il s'agit d'une version complètement numpy, nous avons encore des choses à déterminer, comme le nombre de BitGenerator s que nous incluons. Si nous incluons le complément actuel complet, nous avons divulgué certaines des options.

Les dépréciations qui vous permettent de passer d'une fonction à une autre sont beaucoup plus faciles à gérer, et c'est ce que je préconisais.

+1 d'accord

Non, c'est pour atténuer les problèmes d'avoir une API "cible mobile désignée" comme default_generator (seed) sur laquelle les gens travaillaient (par exemple l'argument de version de @shoyer ).

Le maintien de la compatibilité des flux (ce que NEP 19 rejette) est secondaire à la rupture de l'API. Différents BitGenerators ont différentes API efficaces

Ah ok, maintenant ça a plus de sens pour moi.

Si le nom au son étrange est le problème, je serais heureux d'utiliser un nom générique plus convivial, tant que la politique est par ailleurs la même (nous ajoutons une nouvelle fonction et commençons à avertir de l'ancienne). Je considérerais cet équivalent. Nous ne devrions pas faire cela trop souvent.

Cela semble être la meilleure solution à ce jour. Il devrait être possible de choisir un nom sain d'esprit ici. Et il y a un grand espace d'autres noms sains - dont nous n'aurons probablement jamais besoin.

Quelque chose comme np.random.generator ou np.random.default_generator .

combien de BitGenerators nous incluons

Pourriez-vous ouvrir un autre problème avec une proposition visant à supprimer ceux que vous pensez que nous devrions supprimer des actuellement inclus (MT19937, DSFMT, PCG32, PCG64, Philox, ThreeFry, Xoshiro256, Xoshiro512)?

Nous n'avons toujours pas résolu le problème ici: quel BitGenerator devrait être par défaut (actuellement Xoshiro256 )

Eh bien, ce problème concerne davantage "lequel doit numpy promouvoir comme le distingué BitGenerator ", qui alimente le choix de la valeur par défaut, mais également ceux qui doivent être inclus ou supprimés. La mécanique par laquelle nous fournissons des valeurs par défaut (si nous fournissons des valeurs par défaut) ajoute des contraintes, donc ce sont toutes des choses qui doivent plus ou moins être décidées ensemble. C'est un gros gâchis poilu, et après tout le travail que vous avez accompli pour guider ce PR, je suis sûr que vous êtes épuisé de voir un autre méga-fil sans aucun code contribuant, donc vous avez mes sympathies. :-)

En ce qui concerne les algorithmes en soi, j'ai déjà donné mes recommandations: nous devons garder MT19937 pour RandomState et à des fins de comparaison, et j'aime PCG64 à des fins de recommandation.

J'ai un peu dérangé l'explorateur du compilateur et je pense avoir implémenté PCG64 pour MSVC 64 bits en utilisant des éléments intrinsèques d'une manière qui oblige le compilateur à générer un assemblage proche de celui de clang uint128_t math: https: // godbolt .org / z / ZnPd7Z

Je n'ai pas d'environnement de développement Windows configuré pour le moment, donc je ne sais pas si c'est réellement _correct _... @bashtage pourriez -vous

Sans patch:

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

Avec patch:

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

Le patch passe les tests, notamment en générant le même ensemble de 1000 valeurs uint64 pour 2 graines différentes.

Pour la comparaison avec GCC natif et en mode de comparaison:

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

wow, cela semble vraiment mauvais. Peut-être devrions-nous étendre les informations de la page de comparaison pour démontrer les performances de msvc2017-on-win {64, 32} par rapport à gcc7.4-on-linux {64, 32} sur la même machine (je suppose simplement que vous utilisez msvc2017, vous devriez probablement inclure cette information quelque part).

Win32 est sans espoir ici. Je soupçonne que Linux 32 bits sera également assez terrible, mais je n'ai pas de système Linux 32 bits pour tester facilement.

Je peux certainement voir l'intérêt de faire une recommandation pour les personnes qui s'en tiennent à une machine 32 bits (probablement Windows en raison des politiques informatiques de l'entreprise). Cette recco est claire: DSFMT pour 32 bits (ou MT19937 est également bon). Les repères seraient cependant bons.

Pour ce que cela vaut, je suis plutôt sceptique quant à l'affirmation souvent répétée de PCG selon laquelle plusieurs flux aléatoires indépendants . Quelqu'un a-t-il fait une analyse statistique sérieuse pour étayer la revendication d'indépendance? (En fait, je pense que l'article d'O'Neill ne fait référence qu'à des courants "distincts", sans aucune revendication d'indépendance.)

Je pense qu'il y a de bonnes raisons d'être sceptique: pour un multiplicateur LCG donné, tous ces flux distincts sont simplement liés via la mise à l'échelle [*]. Donc, étant donné deux flux LCG avec le même multiplicateur, l'un d'entre eux sera simplement un multiple constant (modulo 2**64 ou 2**32 selon le cas) de l'autre, mais avec des points de départ différents. La partie permutation du PCG aidera à cacher cela un peu, mais il ne serait vraiment pas surprenant qu'il y ait des corrélations statistiquement détectables.

Des flux si distincts , bien sûr, mais je ne prendrais pas la prétention de flux indépendants pour argent comptant sans quelques tests sérieux.

[*] Exemple: supposons que x[0], x[1], x[2], ... est un flux LCG 64 bits standard, avec x[i+1] := (m*x[i] + a) % 2**64 . Définissez y[i] := 3*x[i] % 2**64 pour tout i . Alors y[i] est un flux LCG avec y[i+1] := (m*y[i] + 3*a) % 2**64 , donc en mettant simplement à l'échelle le flux d'origine, vous avez produit l'un de ces flux LCG distincts avec le même multiplicateur mais une constante additive différente. En utilisant d'autres multiplicateurs impairs à la place de 3 , et en supposant que nous ne sommes intéressés que par les LCG à période complète (et donc a est impair), vous obtiendrez tout le plein possible période LCG avec ce multiplicateur.


EDIT: Correction d'une erreur sur le nombre de classes de conjugaison.

Je pense que l'analyse publique la plus approfondie des flux PCG est ici: http://www.pcg-random.org/posts/critiquing-pcg-streams.html

@imneme Pouvez-vous développer vos derniers conseils? "La correspondance avec David Blackman montre qu'il est peut-être plus facile que je ne le pensais de créer des flux" à proximité "avec des initialisations corrélées comme une valeur de départ constante et des flux de 1,2,3,4. Si vous comptez utiliser plusieurs flux à en même temps, pour l'instant, je recommande que l'identifiant de flux et la graine soient distincts et ne présentent pas de corrélations évidentes entre eux, ce qui signifie ne pas les rendre tous les deux 1,2,3,4. "

Cela signifie-t-il que vous pensez qu'il est normal d'avoir une seule bonne graine (par exemple dérivée d'une source d'entropie) et ensuite des ID de flux 1,2,3,4? Ou est-ce que l'ID de départ et de flux doit être choisi au hasard à partir d'une bonne source d'entropie?

Quelques numéros 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

Il est donc deux fois plus rapide que Win-32 mais lent. Les 4 minutages ont été effectués sur la même machine

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 est le NumPy 1.16.4. DSFMT est le seul générateur avec de bonnes performances sur 32 bits (x86). Cela devrait être clairement documenté pour tous les utilisateurs 32 bits. MT19937 est également un choix relativement bon pour un utilisateur 32 bits.

Nous avons donc besoin de MT19937 à des fins d'héritage. Si nous voulons être minimes sur les PRNG que nous incluons (c'est- MT19937 dire MT19937 seront toujours disponibles, et ce n'est pas pire que ce qu'ils ont actuellement. Et des packages tiers seront disponibles pour les utilisations les plus niches.

Bien sûr, si nous voulons inclure un ensemble plus complet de PRNG pour d'autres raisons, nous pouvons faire toutes sortes de recommandations spécifiques dans la documentation.

J'étais curieux de voir à quel point la partie «P» de PCG atténuait les problèmes potentiels des flux corrélés.

Voici donc (peut-être) le pire cas possible pour les LCG: où la constante additive d'un flux LCG est la négation exacte de la constante additive pour l'autre. Ensuite, avec des choix de semences terribles, nous nous retrouvons avec l'un des flux LCG étant la négation exacte de l'autre.

Mais maintenant, si nous utilisons les deux flux pour générer une série de flottants, la partie permutation du PCG et la conversion en float64 devraient nous aider un peu.

Voici un graphique qui montre à quel point la permutation aide:

streams

C'est un nuage de points de 10000 flotteurs d'un de ces flux, contre 10000 de son jumeau annulé. Pas terrible, mais pas génial non plus: il y a des artefacts clairs.

Je ne sais pas trop quoi en conclure: c'est absolument un exemple artificiel, dans lequel il est peu probable (j'espère) de tomber par accident. D'un autre côté, cela démontre qu'une certaine réflexion et une certaine prudence sont nécessaires si vous avez vraiment besoin de plusieurs flux non corrélés.

Pour mémoire, voici la source:

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 est le générateur qu'O'Neill appelle PCG-XSL-RR (section 6.3.3 du document PCG). Le package pcgrandom vient d' ici

J'ai pensé que le moyen standard d'obtenir des flux indépendants était d'utiliser jumpahead ().
Le réensemencement pour obtenir des flux «indépendants» est en général dangereux.

Les générateurs de compteur / hachage ont un jumpahead () trivial. Est-ce que PCG?

Aussi un plaidoyer d'un utilisateur: veuillez fournir au moins un flux binaire
qualité cryptographique, avec un espace d'états illimité.

À votre santé,
Philippe

(EDIT by seberg: citation par e-mail supprimée)

@pbstark : Il ne s'agit pas seulement de réensemencer: les deux générateurs LCG sous-jacents sont en fait distincts: x ↦ mx + a (mod 2 ^ 128) et x ↦ mx + b (mod 2 ^ 128) pour différents incréments a et b. L'article PCG d'O'Neill vend l'idée de pouvoir créer différents flux en modifiant cet incrément LCG (voir la section 4.3.2 de l'article).

Mais la simplicité de LCG signifie que changer cette constante additive équivaut simplement à un saut en avance d'une quantité inconnue dans le générateur d'origine, combiné à une simple transformation linéaire (multiplication par une constante, ou simplement addition d'une constante dans certains cas).

Ce n'est pas une raison pour ne pas utiliser PCG, et je ne dis pas un instant qu'il ne convient pas au nouveau PRNG principal de NumPy; Je ne veux tout simplement pas que les gens soient séduits par la promesse de flux aléatoires "indépendants". Au mieux, l'idée de flux réglables pour PCG offre un moyen pratique de faire quelque chose d'équivalent à un saut rapide plus une transformation multiplicative ou additive supplémentaire.

Nous avons discuté un peu de la cryptographie dans l'appel de la communauté. Je pense que nous étions un peu prudents à ce sujet. Cela semble être une bonne idée, mais si nous incluons un RNG à son cryptographique, nous devons également faire face aux problèmes de sécurité qui surviennent, car nous ne savons pas si les utilisateurs les utilisent à des fins de cryptographie réelles.

Sur la note du nombre à inclure: Le consensus avait tendance à dire qu'il était bien d'avoir quelques générateurs de bits supplémentaires (une petite documentation serait bien sûr). La charge de maintenance ne semble pas trop lourde. En fin de compte, je suppose que nous irions avec tout ce que Kevin et Robert suggèrent.

Sur la note des noms: personnellement, cela ne me dérange pas d'utiliser les noms RNG et de forcer les utilisateurs à les utiliser, le seul inconvénient est que nous devrons peut-être rechercher le nom lors du codage. Nous devrions simplement essayer d'avoir le moins d'avertissements de dépréciation possible. J'aime l'API minimale exposée pour le RNG par défaut sans ensemencement.

@mdickinson , j'ai essayé de reproduire moi-même votre graphique et j'ai échoué. J'ai utilisé la version canonique C ++ du code avec ce programme qui devrait être l'équivalent moral du vôtre.

#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";
    }
}

Quand je l'exécute, il génère (pour permettre la reproductibilité):

RNG1: 47026247687942121848144207491837523525 203756742601991611962280963671468648533 41579532896305845786243518008404876432
RNG2: 47026247687942121848144207491837523525 136525624318946851501093643760299562925 52472962479578397910044896975270170620

et des points de données qui peuvent tracer le graphique suivant:

corr1

Si vous pouvez comprendre ce que je fais différemment, ce serait utile.

(Je ne dis pas cela pour réfuter l'idée que les corrélations sont possibles, parce qu'elles le sont, et j'écrirai un commentaire séparé sur le sujet, mais c'est en écrivant ce commentaire que j'ai réalisé que je ne pouvais pas reproduire votre résultat en utilisant mes outils habituels.)

@mdickinson a poinçonné dans l'état calculé et incrémenté directement dans les éléments internes, en contournant la routine d'initialisation habituelle , qui comporte deux étapes.

J'ai écrit un petit script de pilote rapide qui entrelace plusieurs flux PCG32, construits de différentes manières, pour alimenter PractRand. Il utilise master de numpy sur Python 3. Lorsque je saisis directement les états / incréments internes contradictoires, PractRand échoue rapidement. Je ne sais pas si nous pouvons trouver des moyens raisonnables de trouver des graines contradictoires (qui passent réellement par la routine d'initialisation) pour atteindre les états contradictoires.

Comme indiqué dans mon article de blog mentionné précédemment, les flux de PCG ont beaucoup en commun avec ceux de SplitMix.

En ce qui

La bonne façon de penser aux flux est juste un état plus aléatoire qui doit être amorcé. L'utilisation de petites valeurs telles que 1,2,3 est généralement une mauvaise idée pour tous les besoins d'amorçage de _any_ PRNG (car si tout le monde favorise ces graines, leurs séquences initiales correspondantes seront surreprésentées).

Nous pouvons choisir de ne pas l'appeler du tout un flux et de l'appeler simplement state. C'est ce que Marsaglia a fait dans XorWow . Si vous regardez le code, la séquence de Weyl counter n'interagit pas du tout avec le reste de l'état et, comme les LCG, et les variations de la valeur initiale ne représentent en réalité qu'une constante ajoutée.

Les flux de SplitMix, PCG et XorWow sont ce que nous pourrions appeler des flux «stupides». Ils constituent un reparamétrage trivial du générateur. Il y a cependant de la valeur à cela. Supposons que sans flux, notre PRNG aurait une répétition proche intéressante de 42, où 42 apparaît plusieurs fois en succession rapide et ne le fait que pour 42 et aucun autre nombre. Avec des flux stupides «juste un incrément» ou «juste un xor», nous éviterons en fait de câbler l'étrange répétition à 42; tous les nombres ont un flux dans lequel ils se répètent bizarrement. (Pour cette raison, le correctif que j'appliquerais pour réparer les problèmes de répétition rapprochée dans Xoshiro 256 est de mélanger dans une séquence de Weyl.)

Je ne suis pas un expert, mais du côté de la cryptographie, ce qui est proposé n'est pas disponible en:
https://cryptography.io/en/latest/ de l' autorité cryptographique Python ?

Leur page sur la génération de nombres aléatoires mentionne également:

À partir de Python 3.6, la bibliothèque standard comprend le module secrets , qui peut être utilisé pour générer des nombres aléatoires cryptographiquement sécurisés, avec des aides spécifiques pour les formats basés sur du texte.

Je suppose que peut-être ajouter des tableaux à la génération. Je me demande si le fardeau de maintenance potentiel associé à la robustesse cryptographique en vaut vraiment la peine et est approprié dans NumPy par rapport à la communication avec le pyca et peut-être en pensant à un générateur / plugin tiers pour cela. Je pense que Nathaniel a déjà mentionné une préoccupation similaire.

En effet, il me semble que des éléments tels que le refactor / amélioration potentiel de dtype sont également conçus pour fournir une infrastructure API sans nécessairement assumer le fardeau de maintenir une grande variété de nouvelles applications spécialisées.

BTW, il y a aussi plus sur la création d'états PRNG corrélés dans ma réponse à la critique PCG de Vigna [spécifiquement, cette section ]. Quelque chose que j'ai observé là-bas, c'est qu'avec le PCG, car il a une fonction de distance, vous pouvez en fait vérifier avec la fonction de distance pour détecter les graines artificielles. Dans les PRNG sans fonction de distance, les gens peuvent toujours créer des paires d'ensemencement qui sont de mauvais choix (surtout si elles contournent l'API publique pour l'ensemencement) mais il n'y a aucun mécanisme fourni qui puisse détecter même les artifices les plus flagrants.

D'une part, c'est amusant de se demander s'il est possible de prendre votre PRNG préféré (ou le moins préféré) et de lui fabriquer des graines qui lui permettent de faire des choses amusantes ou terribles (par exemple, cette pathologie ) ...

Mais en regardant la situation dans son ensemble, je pense qu'il est logique d'examiner les problèmes auxquels les utilisateurs sont confrontés dans la pratique. Quels conseils nous leur donnons, etc. La plupart des utilisateurs ne se rendent pas compte que _pour tous les PRNG (passés et futurs) _, une graine 32 bits est une idée absolument terrible et entraîne un biais trivialement détectable quel que soit le PRNG en jeu. Bien sûr, nous pouvons effacer cela et passer notre temps à nous demander si quelqu'un pourrait réussir à initialiser le Mersenne Twister à un état principalement à zéros (ou à l'état tout à zéro où les LFSR ne fonctionnent pas du tout!), Ou si quelqu'un pourrait initialisez Xoshiro à proximité du point où il répète sept fois la même sortie en l'espace de onze sorties, ou créez deux flux PCG similaires, ou autre chose, mais tous ces artifices ont fondamentalement une chance infinitésimale (en pratique zéro) de se produire si le générateur est alimenté par des données aléatoires. Aussi intéressantes sur le plan intellectuel et académique que soient ces diversions, y penser tout en ignorant le fait que les utilisateurs ont généralement peu d'idée de ce qu'ils font en matière d'ensemencement, c'est tripoter pendant que Rome brûle.

Si inc=1,2,3,4 est une mauvaise idée, cela ne suggérerait-il pas qu'elle devrait être documentée très clairement, ou peut-être devrions-nous avoir une API légèrement différente? Peut-être même new_generator = (Bit)Generator().independent() , nous pouvons y mettre un avertissement si le générateur de bits (sous-jacent) ne fournit pas un excellent moyen d'y parvenir.

En outre, en fonction de la mauvaise qualité de l'ensemencement 32 bits. Pouvons-nous penser à une belle API pour créer et stocker une graine pour la geler? Je ne sais pas. Peut-être même un "créer un fichier de cache de semences gelé s'il n'existe pas".

Pour PCG pourrait juste semer -> uint64_t [2] -> splitmix64 (seed_by_array) -> uint128 ce qui garantirait que les graines basses et consécutives soient réparties.

Pour PCG pourrait juste semer -> uint64_t [2] -> splitmix64 (seed_by_array) -> uint128 ce qui garantirait que les graines basses et consécutives soient réparties.

Ou utilisez simplement n'importe quel bon hachage entier. (Cela devrait être une bijection.). Il y en a plein de petits et bon marché. Quelques tours de Multiply – XorShift sont très bien.

Pour le point de @mdickinson , je pense qu'il veut encore un peu convaincre que la dépendance au flux est limitée à un petit ensemble de paramètres artificiels / contradictoires. Et si tel est le cas, nous pouvons le résoudre avec des pratiques appropriées pour éviter de tels cas. Avec le code actuel que nous avons, il y a quelques mauvais états dans lesquels les utilisateurs pourraient facilement tomber avec les API actuelles. Je peux confirmer la découverte de David Blackman selon laquelle définir à la fois seed=1 et inc=0,1,2,... crée des corrélations. Mon dernier pilote PractRand pour les flux PCG32 entrelacés peut être utilisé pour le démontrer.

❯ ./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

Je n'ai pas encore rencontré d'échec avec une graine aléatoire mais les mêmes incréments proches. Je vais vérifier avec vous demain.

Je remarque que nous n'utilisons pas l'incrément par défaut recommandé quand aucun n'est spécifié dans le constructeur. Nous devrions probablement résoudre ce problème. Peut-être que ce serait un bon numéro de base à partir duquel nous dérivons l'incrément réel de l'ID de flux donné au lieu de 2*inc + 1 .

Nous pouvons essayer de créer des outils pour aider les gens à utiliser les graines d'entropie par défaut et à les sauver. Une question que je me pose est de savoir si les incréments pour plusieurs flux peuvent être générés simplement ou si nous devons également les échantillonner par entropie et les enregistrer également. Il est vraiment pratique de pouvoir encoder "l'état initial" d'une simulation sous la forme d'un numéro unique qui peut être copié-collé à partir du courrier électronique d'un collègue plutôt que d'un fichier opaque. Avec ces PRNG plus petits avec seulement 128 ou 256 bits d'état, je peux facilement l'imprimer en hexadécimal dans mon fichier journal, puis le copier-coller dans ma ligne de commande lorsque je veux le reproduire. C'est plus grand qu'un entier 32 bits, mais c'est gérable. Si je dois également échantillonner par entropie tous mes ID de flux, je dois y renoncer et m'assurer que j'enregistre tout dans un fichier d'état quelque part. Cela pourrait exclure certains cas d'utilisation dont nous avons discuté où nous voulons générer dynamiquement de nouveaux flux. Si je peux simplement incrémenter un compteur pour obtenir un bon ID de flux (peut-être en le dérivant via un hachage du compteur, ou autre), alors je n'ai besoin que d'enregistrer la graine initiale et non les ID de flux.

IIRC, le module secrets appelle la source d'entropie du système d'exploitation, qui peut être assez
mauvais dans certains systèmes, et n'est pas réplicable / reproductible malgré tout.

Le mer 29 mai 2019 à 15 h 19, Tyler Reddy [email protected]
a écrit:

Je ne suis pas un expert, mais du côté de la cryptographie, ce qui est proposé est
non disponible en:
https://cryptography.io/en/latest/ de l'autorité cryptographique Python
https://github.com/pyca ?

Leur page sur la génération de nombres aléatoires
https://cryptography.io/en/latest/random-numbers/ mentionne également:

À partir de Python 3.6, la bibliothèque standard inclut les secrets
https://docs.python.org/3/library/secrets.html module, qui peut être
utilisé pour générer des nombres aléatoires cryptographiquement sécurisés, avec des
helpers pour les formats basés sur du texte.

Je suppose que peut-être ajouter des tableaux à la génération. Je dois me demander si le
le fardeau de maintenance potentiel d'être associé à la cryptographie
la robustesse en vaut vraiment la peine et convient dans NumPy vs.
communiquer avec la pyca et peut-être penser à un tiers
générateur / plugin pour cela. Je pense que Nathaniel a mentionné une préoccupation similaire
précédemment.

En effet, il me semble que des choses comme le refactor potentiel dtype /
Les améliorations sont également conçues pour fournir une infrastructure API sans
assumer nécessairement le fardeau de maintenir une grande variété de
nouvelles applications spécialisées.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWKXSSTX6QI7HJ65GYTPX36O3A5CNFSM4HPX3CHKYY3PNVWWWK3TUL52HS4DFVMNVWWWK3TUL52HS4DFVMDMHZWWWK3TUL52HS4DFVMD5HZWWWK3TUL52HS4DFVMD3GNVWWK3TUL52HS4DFVMDMHZWWWK3TUL52HS4DFVMT
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWKGZMUB67VPCMFZYGTPX36O3ANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

@tylerjereddy Ce sont pour obtenir une petite quantité de bits aléatoires d'une source d'entropie physique qui sont imprévisibles pour un attaquant (et vous!). Ils sont utilisés en cryptographie pour des choses comme les vecteurs d'initialisation, les nonces, les clés, qui sont tous courts. L'intérêt de ceux-ci est qu'il n'y a aucun moyen de les reproduire, ce qui est en contradiction avec les objectifs de simulation numérique de np.random . Cette page ne parle pas de PRNG _reproductibles_ cryptographiquement sécurisés, qui sont également des éléments qui existent et _pourraient_ être construits à partir des primitives disponibles dans le package cryptography . Dans _practice_, cependant, nous avons de meilleures implémentations de ces algorithmes déjà disponibles pour nous dans un code C efficace, du moins ceux qui ont été formulés et testés à des fins de simulation. @bashtage en a implémenté quelques-uns pour ce framework.

Je veux également être clair pour l'équipe numpy que ce que @pbstark propose n'est pas n'importe quel PRNG basé sur la cryptographie. Au contraire, il en veut un avec un état non limité, qui lui fournirait la propriété mathématique qu'il recherche.

La plupart des PRNG basés sur la cryptographie qui sont couramment considérés pour la simulation n'ont _pas_ l'état illimité que @pbstark veut. Ils sont généralement basés sur le cryptage d'un compteur fini. Une fois que ce compteur roule, vous avez atteint la période finie. Techniquement, son cryptorandom est également lié à des conditions initiales uniques 2**(256+64) raison de l'état de résumé de taille fixe de 256 bits et du compteur de longueur de 64 bits de taille fixe. Cela indique probablement la voie à suivre pour implémenter un PRNG véritablement illimité en faisant du compteur de longueur une taille arbitraire, mais je n'ai jamais vu un tel algorithme publié ou testé.

D'un autre côté, si vous voulez juste un algorithme PRNG qui a un état de taille arbitraire, juste un qui est fixé au début à quelque chose au-dessus de ce dont vous avez besoin, alors les générateurs étendus de PCG fonctionneraient bien pour cette tâche. Ce ne sont clairement pas des CS-PRNG, mais ils satisferaient en fait le désir de

Il y a d'autres propriétés que les CS-PRNG standard, bornés, ont que nous pouvons souhaiter, mais ce ne sont pas des valeurs par défaut évidentes, IMO.

L'espace d'état de Cryptorandom n'est pas le hachage de 256 bits: il est illimité. le
l'état de départ est une chaîne de longueur arbitraire, et chaque mise à jour ajoute un zéro
à l'état actuel. Incrémenter un compteur entier illimité
accomplir la même chose. Nous l'avons initialement implémenté, mais nous sommes passés à
ajouter plutôt que d'incrémenter, car cela permet une mise à jour plus efficace
le condensé que le hachage de chaque état à partir de zéro (accélération notable).

Le mer 29 mai 2019 à 19:26 Robert Kern [email protected]
a écrit:

@tylerjereddy https://github.com/tylerjereddy Ce sont pour obtenir un
petite quantité de bits aléatoires provenant d'une source d'entropie physique qui sont
imprévisible pour un attaquant (et vous!). Ils sont utilisés en cryptographie pour
des choses comme les vecteurs d'initialisation, les nonces, les clés, qui sont tous courts. le
tout l'intérêt de ceux-ci est qu'il n'y a aucun moyen de les reproduire, ce qui est à
est en contradiction avec les objectifs de simulation numérique de np.random. Cette page est
ne parle pas de PRNGs reproductibles sécurisés cryptographiquement, qui
sont aussi des choses qui existent et pourraient être construites à partir des primitifs
disponible dans le package de cryptographie. En pratique , cependant, nous avons
de meilleures implémentations de ces algorithmes déjà disponibles pour nous en
code C efficace, du moins ceux qui ont été formulés et testés
à des fins de simulation. @bashtage https://github.com/bashtage implémenté
quelques https://github.com/numpy/numpy/issues/13635#issuecomment-496287650
pour ce cadre.

Je veux aussi être clair pour l'équipe numpy que ce que @pbstark
https://github.com/pbstark propose n'est pas n'importe quel crypto-basé
PRNG. Au contraire, il en veut un avec un état illimité , qui fournirait
la propriété mathématique qu'il recherche.

La plupart des PRNG basés sur la cryptographie qui sont couramment considérés pour la simulation
n'ont pas l'état illimité que @pbstark
https://github.com/pbstark veut. Ils sont généralement basés sur
crypter un compteur fini. Une fois que ce compteur roule, vous avez frappé le
période finie. Techniquement, sa cryptographie
https://statlab.github.io/cryptorandom/ est également lié à 2 ** (256 + 64)
conditions initiales uniques en raison de l'état de résumé de 256 bits de taille fixe et
compteur de longueur 64 bits de taille fixe. Cela indique probablement la voie à
mettre en œuvre un PRNG vraiment illimité en faisant le compteur de longueur
de taille arbitraire, mais je n'ai jamais vu un tel algorithme publié ou
testé.

D'un autre côté, si vous voulez juste un algorithme PRNG qui a un
état de taille arbitraire, juste un qui est fixé au début pour
quelque chose au-dessus de tout ce dont vous avez besoin, puis les générateurs étendus de PCG
http://www.pcg-random.org/party-tricks.html fonctionnerait bien pour cela
tâche. Ce ne sont clairement pas des CS-PRNG, mais ils satisferaient en fait
@pbstark https://github.com/pbstark Le désir de disposer d'énormes espaces d'état
à la demande. Néanmoins, je ne recommande pas que nous les incluions dans numpy.

Il y a d'autres propriétés que les CS-PRNG standard, bornés ont
nous pouvons vouloir, mais ils ne sont pas un défaut évident, l'OMI.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWLGAF6YVIWXYZ2LTT3PX43ONA5CNFSM4HPX3CHKYY3PNVWWWK3TUL52HS4DFVMD17GWWWK3TUL52HS4DFVMB17GWWWK3TUL52HS4DFVMD17GWWWK3TUL52HS4DFVMD17GWWWK3TUL52HS4DFVMD17GWWWWK3TUL52HS4DFVMB17GWWWK3TUL52HS4DFVM
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWLIJD3UCVY3NXCLPKDPX43ONANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

J'ai bien peur que ce ne soit pas ainsi que fonctionne la mise à jour en ligne de SHA-256. L'état qu'il maintient n'est que le résumé de 256 bits et le compteur de longueur de 64 bits de taille fixe qu'il ajoute lorsqu'il calcule la mise à jour. Il ne contient pas tout le texte. Les hachages se compressent. C'est ainsi qu'il est capable de faire la mise à jour efficace de chaque octet. Fondamentalement, il existe de nombreuses initialisations / historiques passés qui correspondent au même état interne SHA-256, qui est fini. Bien que les cycles soient certainement longs, peut-être plus longs que 2**(256+64) , ils existent certainement. Et dans tous les cas, vous n'avez que moins de 2**(256+64) conditions initiales possibles (pour chaque longueur de texte 0 à 2**64-1 , vous pouvez avoir au plus 2**256 états de hachage interne; une fois le la longueur du texte est supérieure à 32 octets, il doit y avoir des collisions à la caserne). Il n'y a tout simplement plus de bits dans la structure de données.

Merci beaucoup; compris. Je l'exprimerais différemment: l'État
l'espace est illimité, mais (par casier) de nombreux états initiaux distincts doivent
produisent des séquences de sortie indiscernables.

Le mer 29 mai 2019 à 20:21 Robert Kern [email protected]
a écrit:

J'ai bien peur que ce ne soit pas ainsi que fonctionne la mise à jour en ligne de SHA-256. L'état
qu'il maintient n'est que le résumé de 256 bits et le 64 bits de taille fixe
compteur de longueur qu'il ajoute lorsqu'il calcule la mise à jour. Ça ne tient pas
tout le texte. Les hachages se compressent. Voilà comment il est capable de faire
mise à jour sur chaque octet. Fondamentalement, il existe de nombreuses initialisations / anciennes
historiques qui correspondent au même état interne SHA-256, qui est fini.
Bien que les cycles soient certainement longs, peut-être plus longs que 2 (256 + 64), ilsexistent certainement.
conditions initiales possibles (pour chaque longueur de texte 0 à 2 64-1, vous pouvezavoir au plus 2 256 états de hachage internes; une fois que la longueur du texte dépasse 32
octets, il doit y avoir des collisions à la caserne). Il n'y en a tout simplement pas
plus de bits dans la structure de données.

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/numpy/numpy/issues/13635?email_source=notifications&email_token=AANFDWO6HRJZHTVBF2TLK3LPX5B3TA5CNFSM4HPX3CHKYY3PNVHWWWK3TUL52HS4DFVH18WWWWWK3TUL52HS4DFVH18WWWWK3TUL52HS4DFVH18WWWWK3TUL52HS4DFVHVWWWWK3TUL52HS4DFVH18WWWK3TUL52HS4DFV
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AANFDWM56HCQRZXDO3BAQHDPX5B3TANCNFSM4HPX3CHA
.

-
Philip B. Stark | Doyen associé, Sciences mathématiques et physiques |
Professeur, Département de statistique |
Université de Californie
Berkeley, CA 94720-3860 | 510-394-5077 | statistics.berkeley.edu/~stark |
@philipbstark

Il est également vrai qu'il n'y a que des états 2**(256+64) qu'il peut éventuellement passer. Puisque la mise à jour prend la même forme à chaque fois, vous finissez par atteindre un état que vous avez vu auparavant et entrez dans une boucle de période inconnue (pour moi) mais finie. Que ce soit le nombre fini d'états initiaux ou une période finie, cryptorandom a les deux, et je pense qu'ils sont même plus petits que MT19937 .

Non pas que je pense que ce soit un problème avec cryptorandom , en soi. Je n'ai pas été convaincu qu'avoir un ensemble illimité d'états initiaux ou une période illimitée soit quelque chose qui soit réellement nécessaire dans un sens pratique.

Je n'ai pas encore rencontré d'échec avec une graine aléatoire mais les mêmes incréments proches. Je vais vérifier avec vous demain.

Toujours aussi fort à 512 Go:

❯ ./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 nous faisons plus de 2 flux avec des incréments séquentiels, nous voyons rapidement des échecs. Nous devrons chercher à dériver l'incrément réel à partir de l'entrée utilisateur avec une sorte de bijection pour le répandre dans l'espace.

❯ ./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 vous pouvez comprendre ce que je fais différemment, ce serait utile.

Je pense que vous devez remplacer la ligne pcg64 rng2(-seed,-stream); dans votre code par pcg64 rng2(-seed,-1-stream); , pour permettre la transformation increment = 2 * stream + 1 . La négation de l'incrément correspond à la négation au niveau du bit de l'indice de flux. Si je fais ce changement et exécute votre code, je vois quelque chose qui ressemble beaucoup à mon intrigue précédente. (Et je confirme que si je ne fais pas ce changement, tout va bien visuellement.)

@imneme

La bonne façon de penser aux flux est juste un état plus aléatoire qui doit être amorcé.

D'accord. Je pense que cela donne une image très claire pour les LCG: pour un LCG 64 bits avec un multiplicateur fixe bien choisi a , nous avons alors un espace d'états de taille 2^127 , composé de toutes les paires (x, c) d'entiers mod 2 ^ 64, où c est l'incrément impair. La fonction de mise à jour d'état est next : (x, c) ↦ (ax+c, c) , divisant l'espace d'états en 2^63 cycles disjoints de longueur 2^64 chacun. L'amorçage consiste simplement à choisir un point de départ dans cet espace d'états.

Il y a alors une action de groupe évidente qui facilite l'analyse et clarifie les relations entre les différents flux: le groupe de transformations affines inversibles 1-d sur Z / 2^64Z a l'ordre exactement 2^127 , et agit de manière transitoire (et donc aussi fidèlement) sur l'espace d'état: la transformation affine y ↦ ey + f mappe la paire (x, c) à (ex + f, ec + (1-a)f) . Cette action de groupe commute avec la fonction next , donc l'élément de groupe unique qui transforme un point (x, c) dans l'espace d'états en un autre, (x2, c2) , mappe également la séquence générée par (x, c) à la séquence générée par (x2, c2) .

tl; dr: pour un coefficient multiplicateur fixe, les deux séquences d'LCG avec le même multiplicateur (que ce soit en utilisant le même incrément, comme dans le cas d'anticipation, ou incréments différents) sont reliées par une transformation affine. Dans les cas malheureux que nous voulons éviter, cette transformation affine est quelque chose d'horriblement simple, comme ajouter 2 ou multiplier par -1 . Dans le cas général, nous espérons que la transformation affine est suffisamment compliquée pour que les tests statistiques standard ne puissent pas détecter la relation entre les deux flux.

@mdickinson couvre bien la situation. Les permutations de PCG changeront un peu les choses par rapport au cas LCG, mais pas beaucoup. L'intérêt des permutations PCG est que nous pouvons choisir le niveau de brouillage à effectuer. Parce que les LCG 128 bits tronqués passent déjà BigCrush, lorsque j'ai choisi une permutation pour pcg64 j'ai choisi une quantité modeste de brouillage pour cette taille LCG (XSL RR). En revanche, les LCG 64 bits échouent rapidement à plusieurs tests statistiques, donc pcg32 utilise un peu plus de brouillage, mais ce n'est toujours pas la permutation la plus forte du papier PCG. Comme je l'ai mentionné dans le thread pull-request, j'ai commencé à me pencher vers une permutation PCG plus forte (RXS M) pour le cas d'utilisation pcg32 . Ce n'est pas encore la valeur par défaut, vous devez demander cette version explicitement, mais il y a de fortes chances que je change la valeur par défaut lorsque je fais un changement de version majeur pour PCG. (RXS M est la moitié haute du RXS M XS, que Vigna a largement testé à cette taille et aussi à la permutation que David Blackman aime).

Nous pouvons visualiser la différence qu'une version mise à jour du programme de test de quasi-flux utilise les deux schémas pour pcg32 (XSH RR et RCS M [et le LCG brut sous-jacent, aussi]):

#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";
    }
}

Avant de commencer, regardons le graphique dessiné par @mdickinson mais pour juste un LCG sans permutation, juste une troncature:

corr-truncated-lcg

Notez que cela concerne le LCG pathologique avec des états corrélés. Si à la place, nous venions de choisir deux LCG avec des constantes additives choisies au hasard (mais la même valeur de départ), cela ressemblerait à ceci:

corr-truncated-lcg-good

Passant aux fonctions de sortie de PCG, si nous utilisons XSH RR sur le cas pathologique, cela ressemble à ceci - c'est une grande amélioration sur le graphique ci-dessus mais clairement cela n'obscurcit pas complètement l'horrible:

corr-pcg32-current

et c'est RXS M avec la même paire LCG sous-jacente (mal corrélée):

corr-pcg32-future

Mais ce n'est que quelque chose que je réfléchis pour pcg32 . La pénalité de performance est minime, et pcg32 est assez petit pour que je puisse imaginer un utilisateur lourd s'inquiéter de créer une tonne de générateurs pcg32 graines aléatoires et de leur demander une tonne de nombres et avoir une chance pas assez infinitésimale de corrélations. Je suis, franchement, dans deux esprits à ce sujet, car cet utilisateur mythique de puissance utiliserait-il pcg32 en premier lieu.

Une des raisons pour lesquelles je ne me soucie pas trop de rendre les flux de pcg64 plus indépendants est que je ne suis pas sûr de voir un cas d'utilisation où il serait judicieux de garder tous les autres états identiques et de basculer le flux sur une valeur différente (par exemple, à une valeur aléatoire, et encore moins à une valeur proche). Pour à peu près tous les PRNG, la bonne façon d'en créer un deuxième est de l'initialiser avec une nouvelle entropie.

En conclusion, pour NumPy, je pense qu'il peut être plus logique de considérer simplement que PCG64 veut deux 256 bits d'état (techniquement, c'est 255 puisque le bit haut du flux est ignoré) et de l'appeler terminé. Cela évitera également les problèmes liés à l'API, car ce sera une fonctionnalité de moins que les gens auront dans un BitGenerator et pas dans un autre.

(Mais vous voudrez peut-être basculer la variante PCG 32 bits vers la variante RXS M. Pour la source C, vous avez besoin d'une version récente car je n'ai pas pris la peine de fournir RXS M explicitement dans le code C, le rendant uniquement disponible dans le C ++ incarnation.)

[Désolé si c'est plus que ce que vous avez jamais voulu savoir! Eh bien, pas désolé. ;-)]

Une des raisons pour lesquelles je ne me soucie pas trop de rendre les flux de pcg64 plus indépendants est que je ne suis pas sûr de voir un cas d'utilisation où il serait judicieux de garder tous les autres états identiques et de basculer le flux sur une valeur différente (par exemple, à une valeur aléatoire, et encore moins à une valeur proche). Pour à peu près tous les PRNG, la bonne façon d'en créer un deuxième est de l'initialiser avec une nouvelle entropie.

J'ai décrit le cas d'utilisation plus tôt. Il y a de fortes raisons UX pour écrire un programme stochastique qui accepte une seule entrée "graine" courte (c'est-à-dire quelque chose sur la taille qu'ils peuvent copier-coller d'un e-mail sur une ligne de commande) qui rend ensuite la sortie du programme déterministe. @stevenjkern m'a fait remarquer lors d'une conversation hors ligne que ce type d'interaction était essentiel pour travailler avec les agences de régulation qui devaient valider son logiciel. Si vous deviez utiliser le fichier _output_ d'une exécution d'un programme pour répliquer le résultat, cela semble un peu suspect dans de telles circonstances. Le régulateur devrait faire une analyse approfondie du code (qui peut ne pas être disponible pour lui) pour s'assurer que les informations contenues dans le fichier sont vraiment casher.

Nous avons maintenant de bons outils en Python pour faire tourner N processus parallèles de manière dynamique pour faire une partie du travail, puis collecter les résultats et passer au processus principal (puis lancer M processus plus tard, etc.). Contrairement aux schémas plus anciens et moins flexibles comme MPI, nous ne faisons pas que lancer N processus fixes au début. Dans ces cas, je pouvais voir l'entropie ensemencer chacun des N PRNG et les enregistrer dans un fichier car il n'y a qu'un seul endroit dans le programme qui fait cela. Au moins du point de vue de la programmation, ce n'est pas trop difficile. Nous avons maintenant des outils beaucoup plus flexibles pour le parallélisme. L'amorçage des PRNG est maintenant le goulot d'étranglement qui nous empêche d'utiliser cette flexibilité dans les programmes stochastiques. Il n'y a plus un seul point de responsabilité où nous pouvons mettre la comptabilité basée sur des fichiers.

Le besoin de dériver de manière reproductible N flux est suffisamment fort pour que les gens fassent des choses étranges pour l'obtenir avec notre algorithme MT actuel. J'ai dû abattre un tas de stratagèmes risqués, et j'espérais que les flux de PCG nous aideraient à y arriver.

Que pensez-vous d'utiliser un bon hachage bijectif sur 2**63 / 2**127 pour dériver des incréments d'une séquence de compteurs 0,1,2,3, ... tout en gardant l'état le même? Prévoyez-vous des problèmes avec cela? Que pensez-vous de la combinaison de l'incrément haché suivi d'une grande avance pour déplacer l'état dans une partie éloignée du nouveau cycle? Peut-être pouvons-nous déplacer cette sous-discussion vers un e-mail ou un autre problème et faire un rapport.

@rkern , il y a peut-être quelque chose de bien dans le fait de pouvoir donner des graines très courtes aux PRNG, mais (quel que soit le PRNG), c'est une idée terrible. Si vous fournissez _k_ bits d'entrée de départ, puis demandez _k_ bits (soit immédiatement, soit sautez d'abord _j_ bits [pour certains _j_ arbitraires] puis lisez _k_ bits), même si les 2 ^ _k_ entiers sont des entrées valides, pas les 2 ^ _k_ sorties peuvent être observées (parce que la fonction de bits entrants à bits sortants n'est pas garantie d'être [et ne peut vraiment pas être] une bijection). La distribution attendue est un binôme (2 ^ k, 2 ^ -k), que nous pouvons approximer comme une distribution de Poisson et donc les valeurs de 2 ^ k / e ne seront pas observées. Cela est vrai quel que soit le PRNG. Tout ce que nous pouvons faire, c'est que _k_ soit suffisamment grand pour qu'il soit totalement impossible de comprendre ce qui manque.

Le problème est aggravé lorsque tout le monde utilise, par exemple, le même PRNG (par exemple le Mersenne Twister) et cueille des graines dans le même petit ensemble (par exemple, des nombres inférieurs à 10000), car plutôt qu'un biais arbitraire particulier par programme, c'est un biais pour tout le monde. Par exemple, supposons que vous choisissiez une graine à quatre chiffres, puis que vous récupériez un nombre raisonnable de nombres dans le Mersenne Twister, (disons moins d'un million). Dans cette situation, je peux vous assurer que le nombre malchanceux 13 _ ne s'affichera jamais_ comme l'un des 10 milliards de sorties (en fait, environ 10% des entiers 32 bits seront absents), et le nombre 123580738 est surreprésenté d'un facteur de 16. C'est exactement ce à quoi nous nous attendrions pour un échantillon aléatoire de dix milliards d'entiers 32 bits, mais c'est un vrai problème _si tout le monde utilise le même échantillon_. Nous aurions un problème exactement analogue si tout le monde choisissait des graines à neuf chiffres et ne tirait que 10000 numéros.

Le fait que beaucoup de gens veuillent faire quelque chose n'en fait pas une bonne idée. (Cela ne veut pas dire qu'il est acceptable de dire simplement aux gens qu'ils le font mal ou qu'ils veulent la mauvaise chose. Vous devez déterminer ce dont ils ont réellement besoin (par exemple, des résultats reproductibles à partir d'un argument de ligne de commande court - peut-être le la bonne chose est d'autoriser l'ensemencement à partir d'un UUID et d'un petit entier; quelques idées sur la façon de brouiller ces choses pour créer des données de départ peuvent être trouvées dans ce billet de blog et faire leur chemin dans randutils .)

(Voici le code avec lequel jouer, car il est assez court ...)

// 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";
}

Comme je l'ai déjà dit, je pense que les semences de 128 bits sont suffisamment courtes pour cela, et je peux créer des outils pour aider les gens à écrire des programmes qui font ce qu'il faut. À savoir, les échantillonner par entropie par défaut, les imprimer ou les enregistrer d'une autre manière, puis leur permettre d'être transmis plus tard. Votre recommandation de générer un UUID pour chaque programme et de mélanger une graine éventuellement plus petite fournie par l'utilisateur par exécution est également bonne.

Supposons que je puisse amener les gens à utiliser de bonnes graines de 128 bits pour la partie état de PCG64 , dans un sens ou dans l'autre. Avez-vous des commentaires sur la dérivation de flux à partir du même état? Je ne cherche pas à tirer plus de nombres, dans l'ensemble, que nous le ferions à partir d'un seul flux PCG64 . Je veux juste pouvoir dessiner ces nombres dans différents processus sans coordination à chaque tirage. L'utilisation d'un hachage ad hoc

@imneme

Il pourrait être utile de définir ce que nous entendons par court.

Je pense que @rkern a écrit "quelque chose sur la taille qu'ils peuvent copier-coller d'un e-mail sur une ligne de commande". Je peux représenter des nombres assez importants en quelques caractères comme 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff.

Je veux juste pouvoir dessiner ces nombres dans différents processus sans coordination à chaque tirage. L'utilisation d'un hachage ad hoc

Avez-vous essayé d'entrelacer des flux n utilisant une seule graine de qualité en faisant avancer l'état d'un nombre suffisamment grand (disons 2 ** 64, ce qui est utilisé par PCG64.jumped )? Cela semble être le moyen le plus simple de coordonner de manière lâche de gros flux n travers un cluster en utilisant quelque chose comme PCG64(seed).jumped(node_id)

node_id vaut 0,1,2, ...

Existe-t-il un PRNG qui est vraiment bon pour produire des flux indépendants simples en utilisant quelque chose comme un index? Je crois qu'un MLFG peut le faire, mais je n'ai pas aimé cela car il s'agissait d'un générateur 63 bits.

@bashtage , ce n'est vraiment pas la bonne façon de le faire. La bonne façon est de prendre une graine, si vous voulez ajouter un petit entier, utilisez une fonction de hachage pour le hacher. Comme mentionné précédemment, j'ai déjà (indépendamment de PCG) écrit une fonction de mixage sérieuse [edit: fix link au bon poste] pour mélanger divers types d'entropie, grands et petits. Vous n'êtes pas obligé d'utiliser le mien, mais je vous recommande de faire quelque chose dans ce sens.

Idéalement, vous voulez un mécanisme qui n'est pas spécifique au PCG. PCG n'est peut-être pas votre choix par défaut, et même si c'était le cas, vous voulez que les gens fassent des choses similaires avec tous les générateurs. Je ne pense pas que vous devriez vouloir un système pour créer plusieurs PRNG indépendants qui dépendent de flux ou de sauts d'avance.

(oups, j'ai lié au mauvais article de blog; j'ai modifié le message précédent, mais au cas où vous lisez par e-mail, je voulais créer un lien vers ce billet de blog)

@imneme À l'heure actuelle, tous les générateurs que nous avons supportent un saut (dont certains sont vraiment des appels de type avancé). Je n'ai aucun doute qu'un ensemencement soigneux est une bonne idée, je soupçonne que de nombreux utilisateurs seront tentés d'utiliser l'appel PRNG.jumped() . Est-ce quelque chose qui devrait être dissuadé?

En ce qui concerne l'ensemencement, les générateurs MT utilisent tous les routines d'initialisation de l'auteur, PCG utilise les vôtres, et le reste donc quelque chose comme

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)

J'imagine que cela pourrait être amélioré.

Est-ce quelque chose qui devrait être dissuadé?

Je n'avais pas vu jumped . C'est assez horrible pour le LCG sous-jacent.

Supposons que nous ayons un multiplicateur, M, de 0x96704a6bb5d2c4fb3aa645df0540268d . Si nous calculons M ^ (2 ^ 64), nous obtenons 0x6147671fb92252440000000000000001 qui est un terrible multiplicateur LCG. Ainsi, si vous preniez tous les 2 ^ 64ème élément d'un LCG 128 bits, ce serait terrible (les bits de poids faible ne sont qu'un compteur). Les fonctions de permutation standard de PCG sont conçues pour brouiller la sortie normale d'un LCG, pas pour brouiller les compteurs.

PCG64 est actuellement testé jusqu'à un demi-pétaoctet avec Practrand et une analyse plus approfondie montre que vous pouvez lire de nombreux pétaoctets sans problèmes liés aux puissances de deux. Hélas, si vous sautez en avant pour sauter d'énormes puissances exactes de deux, les permutations habituelles (quelque peu modestes) de PCG ne peuvent pas suffisamment compenser la séquence pathologique du saut des énormes distances LCG sous-jacentes comme celle-ci. Vous pouvez augmenter la force de permutation pour résoudre ce problème, et en fait, I et Vigna ont comparé indépendamment les permutations standard de PCG aux fonctions de hachage d'entiers standard qui le feraient probablement (après tout, ils sont la base de SplitMix qui _is_ juste un compteur). Quand je l'ai regardé en 2014 avec Fast Hash, la vitesse ne semblait pas si grande, mais quand Vigna l'a fait plus récemment avec murmurhash, il a affirmé que les performances avaient battu le standard PCG (!).

Si vous voulez vraiment avoir une avance de 2 ^ 64, je pense que vous devez passer à une permutation de fonction de sortie plus forte (ce qui, comme nous l'avons vu, peut être fait à faible coût). Mais si vous pensez que le PCG n'est plus vraiment «standard» et que vous voulez conserver la permutation de sortie habituelle, alors jumped() doit probablement partir.

(BTW, le saut pathologique s'applique également aux autres PRNG. SplitMix est connu pour avoir de mauvais incréments et il est raisonnable de supposer que même si l'incrément habituel (a.ka. «gamma») de 0xbd24b73a95fb84d9 est correct, il avance de 2 ^ 32 vous donnera un incrément de 0x95fb84d900000000, ce qui n'est pas si bon. Pour les LFSR, le mauvais saut en avant n'est probablement pas une puissance de deux, mais je suis assez sûr qu'il y aura des sauts là où la matrice sous-jacente finit pathologiquement clairsemé.)

Je peux confirmer, au moins avec PCG32 , 4 flux entrelacés en utilisant .jumped() échouent très rapidement.

❯ ./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

Idéalement, vous voulez un mécanisme qui n'est pas spécifique au PCG. PCG n'est peut-être pas votre choix par défaut, et même si c'était le cas, vous voulez que les gens fassent des choses similaires avec tous les générateurs.

Eh bien, c'est ce que nous essayons de décider ici. :-) Nous nous sommes contentés d'exposer simplement les fonctionnalités fournies par chaque PRNG, en supposant que les propriétés exposées par chaque algorithme sont bien étudiées. Nous sommes également raisonnablement satisfaits de dire "voici le PRNG par défaut que nous recommandons; il a un tas de fonctionnalités qui sont utiles; les autres peuvent ne pas les avoir".

La notion d'utiliser un hachage pour dériver un nouvel état à partir d'un état donné et d'un identifiant de flux, pour tout algorithme, est intéressante. Savez-vous à quel point cela est bien étudié? Cela ressemble à un problème de recherche pour vérifier que cela fonctionne bien pour tous les algorithmes. J'hésiterais à prétendre "voici la procédure générale pour dériver des flux indépendants pour tous nos PRNG". Je suis plus content avec "voici une API commune pour dériver des flux indépendants; chaque PRNG l'implémente de la manière qui convient à l'algorithme et peut ne pas l'implémenter si l'algorithme ne le supporte pas bien".

D'un autre côté, s'il s'agit simplement d'allouer suffisamment de cycles de processeur pour tester les flux entrelacés pour chaque BitGenerator vers N Gio sur PractRand, ce n'est pas trop onéreux.

La notion d'utiliser un hachage pour dériver un nouvel état à partir d'un état donné et d'un identifiant de flux, pour tout algorithme, est intéressante. Savez-vous à quel point cela est bien étudié? Cela ressemble à un problème de recherche pour vérifier que cela fonctionne bien pour tous les algorithmes. J'hésiterais à prétendre "voici la procédure générale pour dériver des flux indépendants pour tous nos PRNG". Je suis plus content avec "voici une API commune pour dériver des flux indépendants; chaque PRNG l'implémente de la manière qui convient à l'algorithme et peut ne pas l'implémenter si l'algorithme ne le supporte pas bien".

Je ne sais pas si vous pouvez l'appeler exactement "un problème de recherche" (et donc "bien étudié"), mais C ++ 11 (qui consistait principalement à utiliser des techniques éprouvées et établies de longue date) fournit le concept _SeedSequence_ (et l'implémentation spécifique std::seed_seq ) dont le travail est de fournir des données d'amorçage à des PRNG complètement arbitraires.

En général, presque tous les PRNG s'attendent à être initialisés / amorcés avec des bits aléatoires. Il n'y a rien de particulièrement magique dans les bits aléatoires sortant de (disons) random.org et les bits aléatoires sortant de quelque chose de plus algorithmique (CS PRNG, fonction de hachage, etc.).

Il est assez simple de penser à une collection de PRNG du même schéma, tous dotés de leurs propres bits aléatoires. Vous pouvez considérer ce que nous faisons comme des points de sélection (ou en fait des intervalles jusqu'à une certaine longueur maximale correspondant à la quantité de nombres aléatoires que nous nous attendons à demander de manière plausible, par exemple, 2 ^ 56) sur une ligne (par exemple, un ligne avec 2 ^ 255 points). Nous pouvons calculer la probabilité que si nous demandons _n_ intervalles, l'un se chevauchera. C'est une probabilité assez basique - je ne suis pas sûr que vous puissiez faire publier un article à ce sujet parce que (si je comprends bien) personne n'est jamais enthousiasmé par les articles contenant des mathématiques élémentaires. ( @lemire pourrait ne pas être d'accord!)

[Je dirais que ce que vous ne devriez généralement pas faire est de semer un PRNG avec des bits aléatoires sortant de lui-même. Cela me semble beaucoup trop incestueux.]

Bon, il est clair pour moi que l'utilisation de quelque chose comme une _SeedSequence_ bien conçue serait un moyen de prendre une graine initiale arbitraire et de dessiner plusieurs points de départ dans le cycle de notre algorithme qui ne devraient pas se chevaucher. Et si c'est le seul véritable moyen d'obtenir des flux indépendants, qu'il en soit ainsi. Ce sera une question de conception d'API pour rendre cela pratique.

Ce sur quoi je suis moins clair, c'est à quel point il est sûr de prendre l'état actuel d'un PRNG initialisé, d'un hachage dans l'ID de flux pour passer à un nouvel état dans le cycle, ce que je pensais que vous suggériez (et il m'est venu plus tard que je me suis peut-être trompé). Être bien séparé dans le cycle n'est pas le seul facteur, comme le montre l'échec de jumped() . jumped() assure également que vous êtes envoyé à une partie éloignée de la séquence qui ne se chevauchent pas; c'est juste une partie qui peut être très fortement corrélée à la partie initiale si le saut n'est pas bien choisi. Cela peut prendre une certaine connaissance des éléments internes de chaque algorithme pour savoir ce qui est et n'est pas un bon saut. Nous ne l'avons évidemment pas fait pour le cas du PCG.

Fondamentalement, si nous considérons les PRNG comme des fonctions de transition et des fonctions de sortie, ce new_state = seed_seq(old_state||streamID) n'est qu'une autre fonction de transition que nous introduisons pour une étape. Nous devons être sûrs que les opérations impliquées dans ce seed_seq sont suffisamment différentes des fonctions de transition dans chaque algorithme PRNG (ou leurs inverses) et devons peut-être être sûrs d'autres choses. Je ne voudrais pas utiliser quelque chose construit à partir de, disons, wyhash pour initialiser wyrand . Comme vous le dites, vous ne voulez pas utiliser le PRNG lui-même pour fournir les bits pour lui-même. C'est pourquoi je pense qu'il faut une étude pour s'assurer que pour tous nos PRNG (étude que j'espérais ne pas avoir à faire moi-même).

D'un autre côté, new_state = seed_seq(old_state||streamID) n'est probablement pas pire qu'à cet égard que l'utilisation prévue de _SeedSequence_ pour plusieurs flux: extraire deux états en séquence. Si tel est le cas, je serais d'accord pour me reposer sur l'expérience de C ++, peut-être avec votre implémentation, et simplement faire quelques tests empiriques avec PractRand pour tous nos algorithmes pour montrer qu'ils ne sont pas moins bien lotis que leurs homologues à flux unique.

Ce serait vraiment bien de faire fonctionner le hash-jump car cela ouvre des cas d'utilisation pour générer des PRNG sans coordination. L'utilisation des ID de flux nécessite une certaine communication ou une pré-allocation. dask a demandé quelque chose comme ça dans le passé.

S'il existe de bonnes alternatives qui ne reposent que sur un bon amorçage que nous pouvons rendre pratique pour faire la bonne chose, alors nous devrions probablement supprimer les flux réglables comme critère de sélection de la valeur par défaut. Nous voulons juste un algorithme par défaut qui a un espace d'états suffisamment grand.

Cela dit, il semble que l'utilisation d'un hachage de 63 bits pour dériver l'incrément PCG32 partir des ID de flux séquentiels ( range(N) ) semble fonctionner. 8192 flux entrelacés transmettent PractRand à 2 Tio. Si nous exposons les ID de flux pour les générateurs PCG, nous souhaitons peut-être utiliser cette technique pour dériver les incréments, même si nous suggérons aux gens d'utiliser d'autres moyens pour obtenir de manière reproductible des flux indépendants.

Ce sur quoi je suis moins clair, c'est à quel point il est sûr de prendre l'état actuel d'un PRNG initialisé, d'un hachage dans l'ID de flux pour passer à un nouvel état dans le cycle, ce que je pensais que vous suggériez (et il m'est venu plus tard que je me suis peut-être trompé).

Je me suis probablement exprimé de manière ambiguë, mais non , je n'ai jamais eu l'intention de suggérer que l'état actuel du PRNG devrait être utilisé dans l'auto-réensemencement.

Mais, FWIW, SplitMix fait cela, c'est ce que fait l'opération split() . Et je n'aime pas que ça fasse ça.

C'est peut-être trop d'informations, mais je vais partager un peu pourquoi j'ai été horrifié (peut-être plus horrifié que je devrais l'être) par la fonction split() de SplitMix. À titre de note historique, SplitMix et PCG ont été conçus indépendamment à peu près à la même époque (SplitMix a été publié le 20 octobre 2014, tandis que le pcg-random.org mis en ligne en août 2014 et lié au document PCG le 5 septembre 2014. ). Il existe des parallèles entre PCG et SplitMix (et divers autres PRNG, y compris xor shift * et xorshift + de Vigna - également publiés dans le monde en 2014). Tous ont des fonctions de transition d'état assez simples, pas assez bonnes, fixées par une fonction de sortie de brouillage. Quand j'écrivais le papier PCG, une chose que je savais que certains aimeraient était une fonction split() mais je ne savais pas comment le faire; au lieu de cela, j'ai développé une preuve rapide que si vous aviez un PRNG _k_ bits où vous pouviez aller à gauche ou à droite à chaque étape, en _k_ étapes, vous devez être en mesure d'arriver à un état dans lequel vous étiez auparavant, prouvant ainsi tout le concept était mal conçu. Cette observation n'a pas été publiée dans le journal. Mais à la suite de mes idées réfléchies avant cette preuve, dans une note de bas de page dans une ébauche presque finale de l'article, j'ai suggéré, quelque peu fantasque, parce que la sortie de PCG était un hachage / brouillage / permutation de son état, si vous étiez vous sentez méchant, vous pouvez réensemencer le générateur avec sa propre sortie et vous en sortir. Je l'ai retiré de la version finale parce que je pensais qu'une telle fantaisie serait un drapeau rouge trop important pour les critiques, étant donné que le réensemencement d'un PRNG avec son propre état était largement considéré comme le type d'utilisation abusive d'un PRNG, et le genre des choses que nous voyons des personnes inexpérimentées dans leur utilisation.

En lisant l'article de SplitMix, j'ai trouvé beaucoup de choses à aimer, mais j'ai été très surpris quand j'ai vu split() . Il a fait quelque chose que je considérais essentiellement comme une blague et en a fait une fonctionnalité de tente. Ce n'est que quelques années plus tard que j'ai commencé à écrire de manière plus technique sur ce qui se passe lorsque vous avez ce genre d'opération.

Le résultat global est que si vous avez un espace d'états suffisamment grand (et celui de SplitMix est à peine suffisant), vous pourrez peut-être vous en sortir avec un réensemencement automatique via une fonction de hachage. J'ai toujours l'impression que ce n'est pas une bonne idée. Parce que les mappages aléatoires (ce à quoi nous avons affaire dans cette situation) ont des propriétés telles que "avec une probabilité asymptotique non nulle, l'arbre le plus grand d'un graphe fonctionnel n'est pas enraciné sur le cycle le plus long", je prétends qu'il est difficile d'avoir une confiance totale à moins que le concepteur n'ait effectué les travaux nécessaires pour montrer que de telles pathologies ne sont pas présentes dans leur conception.

Pour le plaisir, voici un vidage de l'espace d'états d'une toute petite version de SplitMix explorant seulement trois des manières différentes (et rigoureusement fixées) de combiner next() et 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, super. J'ai rédigé moi-même quelques propositions dans ce sens il y a quelques années, alors j'avais peur que nous manquions quelque chose de bon. :-)

Une dernière réflexion sur l'approche de hachage pour dériver des incréments pour les flux PCG? En laissant de côté si c'est le mécanisme principal pour obtenir des flux indépendants. À moins de supprimer complètement l'accès à cette fonctionnalité, cela semble être quelque chose que nous voudrions faire pour empêcher les ID de flux séquentiels faciles à utiliser à mauvais escient.

Par curiosité, existe-t-il un moyen (facile) de savoir à quelle distance se trouvent deux états dans PCG64?

Oui, bien que nous ne l'exposions pas: http://www.pcg-random.org/useful-features.html#distance

Par curiosité, existe-t-il un moyen (facile) de savoir à quelle distance se trouvent deux états dans PCG64?

Oui, bien que nous ne l'exposions pas: http://www.pcg-random.org/useful-features.html#distance

Dans la source C ++, la fonction de distance vous indiquera même la distance entre les flux, en donnant leur point d'approche le plus proche (où la seule différence entre les flux est une constante ajoutée).

Incidemment, pour le LCG sous-jacent, nous pouvons utiliser la distance pour déterminer à quel point nous nous attendons à ce que les positions soient corrélées. Une courte distance est évidemment mauvaise (et est mauvaise pour n'importe quel PRNG), mais une distance avec un seul bit n'est pas génial non plus, c'est pourquoi passer à 2 ^ 64 ( 0x10000000000000000 ) avec .jumped est une mauvaise idée. Sur ma liste de tâches PCG, il y a l'écriture d'une fonction « independence_score » qui regarde la distance entre deux états et vous indique à quel point la distance est aléatoire (via un poids de frappe, etc.) - idéalement, nous voulons environ la moitié du les bits doivent être des zéros et des demi-uns et qu'ils doivent être généreusement dispersés).

Une façon de _keep_ jumped avec PCG64 serait de ne pas sauter de n * 0x10000000000000000 mais de sauter de n * 0x9e3779b97f4a7c150000000000000000 (tronqué à 128 bits). Cela vous donnera toutes les propriétés habituelles que vous voudriez ( .jumped(3).jumped(5) == .jumped(8) ) sans être pathologique pour le LCG sous-jacent.

(Je suis également conscient que dire "ne pas avancer de 0x10000000000000000 " est une sorte de réponse " independence_score puisse exister, mais tout cela (et le problème avec des flux similaires), peut plaider pour une fonction de sortie par défaut plus forte, de sorte que même si les gens font des choses (rares) qui sont totalement pathologiques pour le sous-jacent LCG, aucun mal ne sera fait. PCG va bientôt avoir cinq ans à ce stade, et j'envisage un changement de version et des ajustements cet été, donc ce problème pourrait faire partie de la liste. Bien sûr, il peut vous ennuyer si tout comme vous mettez PCG, je crée une version majeure et l'améliore.)

Une dernière réflexion sur l'approche de hachage pour dériver des incréments pour les flux PCG? En laissant de côté si c'est le mécanisme principal pour obtenir des flux indépendants. À moins de supprimer complètement l'accès à cette fonctionnalité, cela semble être quelque chose que nous voudrions faire pour empêcher les ID de flux séquentiels faciles à utiliser à mauvais escient.

Je vous recommande de le mettre dans le mélangeur Murmur3 . Personne n'est susceptible de créer accidentellement des flux similaires avec cela sans effort délibéré. (Edit: Je suppose que vous avez besoin d'une version 128 bits, mais vous pouvez simplement mélanger les moitiés supérieure et inférieure. J'ajouterais également une constante. Tout le monde aime 0x9e3779b97f4a7c15f39cc0605cedc835 (partie fractionnaire de ϕ) mais 0xb7e151628aed2a6abf7158809cf4f3c7 (partie fractionnaire de e) conviendrait aussi, ou _toute_ nombre aléatoire.)

Je recommande wyhash (https://github.com/wangyi-fudan/wyhash) car c'est le plus rapide et le plus simple qui a dépassé BigCrush et PractRand. Le code c est aussi simple que

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

@ wangyi-fudan, je ne peux pas me convaincre que c'est une bijection.

Désolé pour mes connaissances limitées: pourquoi la bijection est-elle nécessaire / privilégiée pour un PRNG?
sera apprécié pour quelques explications :-) @imneme

@ wangyi-fudan, si une fonction de hachage de 64 bits à 64 bits n'est pas une bijection (c'est-à-dire une fonction 1 à 1), alors certains résultats sont générés plus d'une fois et d'autres pas du tout. C'est une sorte de partialité.

je comprends ce que tu veux dire. cependant, pour un générateur de nombres aléatoires de 64 bits R, nous nous attendons à une collision après 1,2 * 2 ^ 32 nombres aléatoires (http://mathworld.wolfram.com/BirthdayAttack.html). avec 2 ^ 64 nombres aléatoires, il est naturel d'avoir de nombreuses collisions. les collisions sont naturelles alors que la bijection n'est pas naturellement aléatoire. Si je savais que la table de jeu (par exemple un PRNG 3 bits) est déterminée comme ayant une valeur de 0 dans 8 pistes, j'oserai faire un gros pari sur zéro après avoir observé 5 non-nul.

@ wangyi-fudan, Dans ce contexte, nous parlions de moyens de permuter le stream-id afin que les flux comme 1,2,3 deviennent quelque chose de plus aléatoire (aka plus normal). Il n'y a aucune vertu dans les collisions dans ce processus.

Pour les PRNG en général, vous devriez lire la différence entre les PRNG basés sur des mappages aléatoires et ceux basés sur des mappages inversibles aléatoires (fonctions 1 à 1). J'ai écrit à ce sujet, mais d'autres aussi. Dans de petites tailles, les PRNG basés sur des mappages aléatoires montreront des biais et échoueront plus rapidement que ceux basés sur d'autres techniques. Dans les grandes tailles, les défauts de toutes sortes peuvent être plus difficiles à détecter.

Nous pouvons calculer la probabilité que si nous demandons _n_ intervalles, l'un se chevauchera. C'est une probabilité assez basique - je ne suis pas sûr que vous puissiez faire publier un article à ce sujet parce que (si je comprends bien) personne n'est jamais enthousiasmé par les articles contenant des mathématiques élémentaires.

Je pense que tu dois juste être Pierre L'Ecuyer. ;-) page 15

Oui, quand il explique les bases, c'est considéré comme bien!

@rkern @imneme La simplicité est une fonctionnalité, à la fois en logiciel et en mathématiques. Le fait que certains ne soient pas impressionnés par un travail simple ne doit pas être considéré comme une preuve contradictoire.

@lemire : Il y a un morceau d'humour que j'aime et qui, je pense, a beaucoup de vérité appelé _Comment critiquer les informaticiens_ . L'idée sous-jacente derrière la pièce est que les théoriciens favorisent la sophistication et les expérimentateurs privilégient la simplicité. Donc, si votre public est un des expérimentateurs, ils seront ravis par la simplicité, mais si votre public est un des théoriciens, pas tellement.

La valeur par défaut BitGenerator est PCG64 . Merci à tous pour vos contributions réfléchies. Et l'endurance!

Très inspiré par ce fil, j'ai quelques nouvelles à signaler…

Contexte

Selon de nombreuses mesures, pcg64 est assez bon; par exemple, selon les mesures habituelles de la qualité statistique, il obtient un bilan de santé propre. Il a été testé de différentes manières; plus récemment, je l'ai exécuté jusqu'à un demi-pétaoctet avec PractRand. Cela fonctionne bien dans les cas d'utilisation normaux.

MAIS, les pathologies qui sont apparues dans ce fil ne me convenaient pas. Bien sûr, je pourrais dire « eh bien, ne le maintenez pas ainsi », mais tout l'intérêt d'un PRNG à usage général est qu'il doit être robuste. Je voulais faire mieux ...

Donc, il y a environ 25 jours, j'ai commencé à penser à la conception d'un nouveau membre de la famille PCG…

Objectif

Mon objectif était de concevoir un nouveau membre de la famille PCG qui pourrait remplacer la variante pcg64 . En tant que tel:

  • La fonction de sortie devrait brouiller les bits plus que XSL RR (car cela évitera les problèmes qui se sont posés dans ce thread).
  • Les performances devraient être à peu près aussi rapides (ou plus rapides) que les pcg64 actuels.
  • La conception doit être PCG-ish (c'est-à-dire ne pas être trivialement prévisible, et donc ne pas permettre à _toute_ le travail de la fonction de sortie d'être facilement annulé).

Comme toujours, il y a un compromis, car nous essayons d'obtenir la meilleure qualité possible aussi rapidement que possible. Si nous ne nous soucions pas du tout de la vitesse, nous pourrions avoir plus d'étapes dans la fonction de sortie pour produire une sortie plus fortement brouillée, mais le point de vue de PCG était que le LCG sous-jacent était «presque assez bon» et que nous n'avions donc pas besoin pour faire autant d'efforts que nous le ferions avec quelque chose comme un compteur incrémenté de 1.

Divulgacher

Je suis heureux d'annoncer le succès! Il y a environ 25 jours, lorsque j'y ai pensé pour la première fois, j'étais en vacances. Quand je suis rentré il y a une dizaine de jours, j'ai essayé les idées que j'avais et j'étais heureux de constater qu'elles fonctionnaient bien. Le temps qui a suivi a été principalement consacré à divers types de tests. Hier, j'étais assez satisfait que j'ai poussé le code dans la version C ++ de PCG. Les tests effectués sur de petites tailles indiquent qu'il est bien meilleur que le XSL RR et compétitif avec le RXS M, mais qu'il brille en fait dans les plus grandes tailles. Il répond également à tous les autres objectifs.

Détails

FWIW, la nouvelle fonction de sortie est (pour le cas de sortie 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;
}

Cette fonction de sortie est inspirée de xorshift-multiply, qui est largement utilisée. Le choix des multiplicateurs est (a) de maintenir le nombre de constantes magiques vers le bas, et (b) d'éviter que la permutation ne soit annulée (si vous n'avez pas accès aux bits de poids faible), et également de fournir l'ensemble «randomisé- par elle-même »que les fonctions de sortie PCG ont généralement.

Autres changements

C'est aussi le cas que 0xda942042e4dd58b5 est le multiplicateur LCG pour ce PRNG (et tous les générateurs PCG à 128 bits avec préfixe cm_ ). Par rapport à 0x2360ed051fc65da44385df649fccf645 utilisé par pcg64 , cette constante est en fait encore assez bonne en termes de propriétés de test spectral, mais elle est moins chère à multiplier par car 128 bits × 64 bits est plus facile que 128 bits × 128 bits. J'ai utilisé cette constante LCG pendant plusieurs années sans problème. Lorsque vous utilisez la variante de multiplicateur bon marché, j'exécute la fonction de sortie sur l'état pré-itéré plutôt que sur l'état post-itéré pour un plus grand parallélisme au niveau des instructions.

Essai

Je l'ai testé à fond (PractRand et TestU01) et j'en suis satisfait. Les tests comprenaient des scénarios décrits dans ce fil de discussion (par exemple, prendre un générateur de gangs soit sur des vapeurs séquentielles, soit avancé de 2 ^ 64 et entrelacer leur sortie - j'ai testé un gang de quatre et un gang de 8192 à 8 To sans problème, ainsi comme un ruisseau et son homologue de l'autre côté).

La vitesse

Je pourrais parler longuement des tests de vitesse et des benchmarks. Il existe toutes sortes de facteurs qui influencent le fait qu'un PRNG fonctionne plus vite qu'un autre dans un benchmark donné, mais dans l'ensemble, cette variante semble souvent être un peu plus rapide, parfois beaucoup plus rapide et parfois un peu plus lente. Des facteurs tels que le compilateur et l'application ont un impact beaucoup plus grand sur la variabilité du benchmark.

Disponibilité

Les utilisateurs de l'en-tête C ++ peuvent accéder à ce nouveau membre de la famille _ maintenant_ en tant que pcg_engines::cm_setseq_dxsm_128_64 ; à un moment donné dans le futur, je passerai pcg64 de pcg_engines::setseq_xsl_rr_128_64 à ce nouveau schéma. Mon plan actuel est de le faire cet été dans le cadre d'un changement de version PCG 2.0.

Annonces officielles

Dans l'ensemble, je suis très content de ce nouveau membre de la famille et à un moment donné plus tard dans l'été, il y aura des articles de blog avec plus de détails, faisant probablement référence à ce fil.

Vos choix ...

Bien sûr, vous devez déterminer quoi faire avec cela. Que vous l'utilisiez ou non, je serais en fait assez curieux de voir si cela fait mieux ou moins bien dans vos repères de vitesse.

@imneme Contourne- t-il le besoin d'avoir un multiplicateur 64 bits complet et rapide? (Ce qui est super rapide sur x64 mais un peu plus lent sur certaines architectures plus faibles.)

@lemire :

C'est encore potentiellement meilleur que 128 bits × 128 bits. Bien que tout dépend de la manière dont la planification des instructions se déroule à ce moment-là.

Vous avez raison de dire que sur ARM, le résultat 64 bits × 64 bits → 128 bits est en fait deux instructions.

(Il est tout à fait possible, bien sûr, de regrouper deux LCG 64 bits et de les mélanger. L'espace de tous les PRNG qui pourraient exister et fonctionneraient bien est assez énorme.)

Une implémentation rapide et sale dans notre framework suggère une légère amélioration des performances, au moins sur Linux 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

Je pense que c'est la sortie de l'état pré-itéré qui lui donne la bosse, du moins dans ce contexte. J'ai laissé cela de côté au début et j'ai obtenu essentiellement les mêmes performances que PCG64 1.0. La vraie victoire serait sous l'émulation 128 bits, je suppose, mais je n'ai pas réussi à l'écrire, et je n'ai pas de bon moyen de le tester sur les plates-formes qui comptent.

Je suppose que la vraie question pour vous, @imneme , est de savoir à quel point serez-vous ennuyé par le nom numpy.random.PCG64 implémentant l'algorithme 1.0? La sortie est imminente et déjà retardée, donc je ne pense pas que nous allons changer l'algorithme pour le moment. Si les performances sur les plates-formes 32 bits sont particulièrement bonnes, alors je pense que nous pourrions ajouter PCG64DXSM dans une version suivante, et peut-être reconsidérer la valeur par défaut dans quelques versions.

C'est votre choix à faire!

Je n'ai aucun problème avec votre envoi de la version 1.0 de PCG64. Beaucoup d'autres personnes ont utilisé cette variante.

Je pense que la variante DXSM a l'avantage d'éviter les problèmes d'utilisation des cas de bord qui se sont posés dans ce fil (après tout, c'est à peu près pourquoi elle existe), mais d'un autre côté, elle a l'inconvénient qu'il est tard la fête. Il peut même sembler assez imprudent d'expédier un PRNG datant de moins d'un mois aux utilisateurs (même s'il est basé sur les mêmes idées que les variantes PCG les plus éprouvées).

(Cela dit, si c'était mon choix, malgré d'éventuelles accusations d'imprudence, j'enverrais probablement le nouveau; je pense que le délai pour le mettre en service dans Numpy est assez minime. Et le risque est très faible - c'est déjà entièrement testé avec BigCrush, et testé avec PractRand jusqu'à 16 To (dont cm_mcg_dxsm_64_32 qui est un quart de la taille [pas de flux, sortie 32 bits]), et atteindra probablement 32 To en moins d'une semaine .)

[Heureux que la performance se soit un peu améliorée. Il y a cinq ans, l'utilisation de l'état pré-itéré était une pessimisation pour des tailles de 128 bits avec des multiplicateurs de 128 bits. Mais c'était alors, sur les machines sur lesquelles je testais, avec les benchmarks que j'utilisais.]

Je voulais dire plus sur l'utilisation du nom PCG64 pour la variante 1.0 lorsque vous allez utiliser ce nom pour faire référence à la variante 2.0.

@rkern S'il s'agit simplement d'un problème de dénomination, alors PCG64DXSM et PCG64 les distinguent bien, non?

Pour numpy, certainement. Je me demande simplement si @imneme préférerait que nous ne PCG64 lorsqu'elle va promouvoir la variante 2.0 sous ce nom dans la version C ++. Je suis sensible au fait qu'être lâche avec les noms signifie que certaines personnes pourraient tester PCG64 numpy et comparer cela aux affirmations qui seront faites sur pcg-random.org à propos de la version 2.0. Cf à peu près n'importe quelle conversation sur les PRNG de Bob Jenkin.

Dans la section 6.3 du document PCG, il est dit:

Notez également que bien que les générateurs soient présentés avec des noms mnémotechniques basés sur les permutations qu'ils effectuent, les utilisateurs de la bibliothèque PCG devraient rarement sélectionner les membres de la famille par ces mnémoniques. La bibliothèque fournit des générateurs nommés basés sur leurs propriétés, pas sur leurs implémentations sous-jacentes (par exemple, pcg32_unique pour un générateur 32 bits à usage général avec un flux unique). De cette façon, lorsque les futurs membres de la famille qui fonctionnent encore mieux sont découverts et ajoutés (espérons-le en raison des découvertes d'autres personnes), les utilisateurs peuvent passer facilement à eux.

Et les bibliothèques C et C ++ sont structurées de cette façon. Les bibliothèques fournissent

  • une interface de bas niveau qui vous permet de choisir un membre de la famille spécifique via le nom de sa permutation, les tailles de bits sur lesquelles il opère et les caractéristiques du LCG sous-jacent
  • une interface de haut niveau qui fournit des alias pratiques comme pcg64 qui se connectent à un membre de la famille de bas niveau pré-choisi.

De cette façon, les alias peuvent être mis à jour pour pointer vers les nouveaux membres de la famille, mais les utilisateurs qui souhaitent reproduire exactement les résultats plus anciens pourront toujours en utilisant l'interface de bas niveau pour sélectionner le membre de la famille qui était auparavant accessible par un haut -niveau alias.

Si vous allez expédier un PRNG appelé PCG64 , je dirais qu'il suffit de dire dans votre documentation quelle variante PCG spécifique c'est - en d'autres termes, dites à quel membre de la famille il correspond dans le bas -interface de bibliothèque de niveau C ou C ++.

Le générateur par défaut est implémenté sous la forme np.random.default_gen() dans https://github.com/numpy/numpy/pull/13840. ( @rkern pour référence future, il est probablement bon d'appeler explicitement les PR - ils sont faciles à manquer dans GitHub si vous ne fournissez qu'un lien arrière, car il n'y a pas de notifications pour cela.)

Une petite remarque: qu'en est-il de l'appeler np.random.default_generator() place? gen me semble trop court / pas évident. Je serais curieux de ce que pensent les autres.

qu'en est-il d'appeler ce np.random.default_generator () à la place?

J'ai eu la même pensée, mais ensuite, np.random.default_generator() est un cheveu sur le côté long, alors j'ai joué avec default_rng .

👍 J'aime aussi default_rng mieux que default_gen . Je serais heureux avec l'un ou l'autre de ces derniers, même si je pencherais toujours vers default_generator .

: +1: pour default_rng() .

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

MorBilly picture MorBilly  ·  4Commentaires

navytux picture navytux  ·  4Commentaires

inducer picture inducer  ·  3Commentaires

kevinzhai80 picture kevinzhai80  ·  4Commentaires

Kreol64 picture Kreol64  ·  3Commentaires