10
votes

Le générateur de nombres aléatoires pseudo donne la même première sortie, mais se comporte ensuite comme prévu

Utilisation de la classe aléatoire et une graine de temps (NULL) La distribution uniforme donne toujours la même première sortie, même avec des compilations différentes, mais une fois la première sortie se comporte comme on peut s'attendre à ce que le générateur de numéros de pseudorandom se comporte.

Est-ce par construction, ou je l'utilise de manière incorrecte? P>

MWe: P>

57
73
181
160
46


5 commentaires

Je suis capable de reproduire le problème sur Ubuntu 14.04 avec G ++ 4.8.2 et Intel Compiler 15.0.0. Intéressant...


Oui, je n'ai essayé que sur mon compilateur Ubuntu 14.10, G ++ 4.8.2, content que je ne suis pas fou cependant.


Ajoutez une constante comme 1000000 à TIME (NULL) et vous verrez une première valeur différente. Ce que vous voyez est attendu pour quelque chose comme un LCG.


Je suis aussi capable de reproduire le problème. Et lorsque je publie la première valeur de gen () à partir de chaque exécution, je reçois un numéro de départ différent dans chaque exécution, il semble donc que le problème est dans uniforme_int_distribution <> Au lieu de par défaut_random_engine . Les 12 bits supérieurs de la première valeur de gen () sont toujours les mêmes (probablement parce qu'ils sont identiques dans temps (null) ). Question probablement liée (et sans réponse) Stackoverflow - Stackoverflow.com/Questtions/21843172/...


Et un test rapide montre que si j'enverse les octets dans la graine , le problème disparaît. Donc, par défaut_random_engine et / ou uniforme_int_distribution <> semble être sensible (je suis sûr que ce n'est pas le mot juste) à l'état des forces de la graine.


3 Réponses :


9
votes

Je ne suis pas sûr de ce qui ne va pas mal (encore!), mais vous pouvez toujours initialiser par le temps comme suit sans frapper le problème (emprunté à partir de ici ). xxx

Vous pouvez également utiliser le périphérique aléatoire, non déterminant (il vole des informations de chronométrage de votre Coups de clés, mouvements de la souris et autres sources pour générer des nombres imprévisibles). C'est la graine la plus forte que vous puissiez choisir, mais l'horloge de l'ordinateur est la meilleure façon d'y aller si vous n'avez pas besoin de garanties solides, car l'ordinateur peut être à court de "aléatoire" si vous l'utilisez souvent (il faut de nombreux coups et souris clés mouvements pour générer un seul nombre véritablement aléatoire). xxx

exécuté xxx

sur ma machine génère xxx

Donc, la bibliothèque chrono fournit un nombre significativement plus grand et un nombre plus rapide de changement rapide (c'est-à-dire des nanosecondes) que la bibliothèque CTTIN / code>. Le aléatoire_device produit des nombres non déterministes qui sont surtout sur la carte. Il semble donc que les graines cTature produisent peuvent être trop proches ensemble et ainsi planer partiellement vers le même état interne?

J'ai fait un autre programme qui ressemble à ceci: < / p> xxx

Comme vous pouvez le constater, ceci imprime simplement la première valeur de sortie pour toutes les semences aléatoires possibles jusqu'à l'heure actuelle ainsi que la graine et le nombre de graines précédentes qui avaient la même chose valeur. Un extrait de la sortie ressemble à ceci: xxx

Donc, pour chaque nouveau numéro de premier numéro, il y a 669 graines qui donnent ce premier numéro. Parce que les deuxième et troisième numéros sont différents, nous générons toujours des états internes uniques. Je pense que nous devrions comprendre beaucoup plus sur le par défaut_random_engine afin de comprendre ce qui est spécial d'environ 669 (qui peut être pris en compte dans 3 et 223).

donné ceci, c'est Effacer pourquoi le chrono et aléatoire_device Les bibliothèques fonctionnent mieux: les graines qu'ils génèrent sont toujours plus de 669 à distance. N'oubliez pas que même si le premier nombre est le même que ce qui compte dans de nombreux programmes, c'est que la séquence de nombres générés par distinct.


15 commentaires

ok c'est intéressant, il semble donc que ce semble être lié à la mise en œuvre de la part, bien que je ne comprenne pas beaucoup d'utilisation de Chrono


Je favoriserais le périphérique aléatoire ici, car nous ne l'utiliserions qu'une seule fois.


@ E_net4 (vous étiquetez au cas où vous pourriez aider) WOW, ce commentaire sur le périphérique aléatoire est génial, je ne savais pas que cela existait, merci pour cela. Lorsque vous dites que cela peut manquer de hasard si elle est trop souvent utilisée, ce qui définit trop souvent? Comme je peux seulement l'utiliser x fois et puis je ne peux jamais avoir de nombre aléatoire à nouveau, ou comme x fois par jour? ou comme x fois avant de recompiler? Je n'ai pas nécessairement besoin de détails, mais une idée générale de ce que vous vouliez dire.


Votre édition sur chrono être plus précis que ctime est intéressant, je fais maintenant le code quelques fois de plus et commence par 10 pour toutes les courses maintenant. Il semble donc mettre à jour le premier terme, juste lentement.


En utilisant deux variables globales différentes, vous pourriez rencontrer un problème d'ordre d'initialisation.


Je suis certainement d'accord, le premier numéro est dans un sens moins important, tant que nous obtenons d'autres bonnes sorties, et je suis d'accord que cela a définitivement un sens pourquoi chrono et aléatoire_device sont meilleur. Mais si vous l'avez, j'aimerais toujours un peu mieux une explication de la fréquence, à peu près, je pourrais utiliser un régime aléatoire. Ou plus spécifiquement ce que je ferais, afin de pouvoir l'utiliser à nouveau plus tard; Dois-je juste attendre une journée? Ou simplement écraser mes clés plus un moment?


@Markransom, si vous regardez mon deuxième programme de recherche de semences, vous remarquerez qu'il n'utilise aucune variables globales mais démontre toujours le problème de l'OP. Par conséquent, l'ordre d'initialisation n'est pas en cause ici. Si vous envisagez le programme de OP avec mon premier programme, vous remarquerez également qu'il y a un seul ordre d'initialisation possible pour les globaux en question.


@ user2386276, votre ordinateur collecte continuellement entropie. Donc, si vous exécutez votre programme pas trop souvent que aléatoire_device aura également des chiffres aléatoires de votre part. Si vous exécutez souvent votre programme (comme dans, probablement, plusieurs fois par seconde), vous épuiserez votre fournisseur d'entropie. Vous pouvez jouer avec cela sur Linux en tapant cat / dev / aléatoire . La première fois que vous courez cela, cela générera probablement de nombreux personnages de trucs aléatoires. Sur les exécutions ultérieures, il générera moins de caractères. Si vous attendez un moment et de l'exécuter à nouveau, vous aurez reconstruit votre magasin d'entropie.


Alors @ user2386276, je ne m'en avais pas trop trop. En outre, le Documentation pour aléatoire_device dit qu'il va jeter une exception si l'entropie est indisponible. Dans ce cas, vous devriez probablement revenir à l'utilisation de l'horloge système (sinon, c'est un excellent moyen de contrôler votre programme). Jouer avec Cat , Lisez un peu sur la génération d'entropie (recherche autour de vous) et, éventuellement, vous aurez une idée de cela.


C'est exactement ce que je cherchais, merci pour votre aide avec ce problème, me dérange toute la journée. De plus, je sais comment tester le problème.


Votre premier code de code a définitivement le potentiel des problèmes d'initialisation des commandes mondiaux, c'est ce que je commentant à faire - cela n'a rien à voir avec le problème initial d'OP ni aucun autre code de votre réponse.


@MarkRansom: depuis l'OOI de dist et Gen dans cet extrait ne peut être responsable du problème de l'OP et Gen ne peut pas être initialisé sans SEED1 ayant été initialisé, je ne vois pas comment aucun problème pourrait survenir.


Qu'est-ce qui vous fait penser que graine1 doit être initialisé avant gen ? Ce n'est pas parce que l'on dépend de l'autre.


@MarkRansom, étant donné que cette discussion a, comme vous le dites, "rien à voir avec le problème initial de l'OP" Je ne pense pas que cela vaut la peine de continuer. Si vous êtes en désaccord, je vous suggère de fournir des liens vers une documentation spécifique, des exemples ou des références élucidant le problème que vous prétendez. Il n'y a pas suffisamment de place dans cette marge.


Je sais que c'est tard. Je ne suis pas sûr qu'il n'y ait rien de spécial environ 669 et défaut_random_engine , c'était juste la différence de semences produite à cause de la plage choisie (10.200) . En effet, pour la petite plage (0,50) Vous obtenez une différence de graines de 2505 car une différence de graines plus importante est nécessaire pour obtenir une valeur mappée différente de cette gamme plus petite. Une plus grande gamme créerait une différence de graines plus petite et ainsi de suite. Si vous examinez la sortie d'un objet par défaut_random_engine , vous voyez la première valeur à peine change lorsque la graine fait la source de la question.



0
votes

La graine que vous utilisez pourrait introduire un biais, si l'utilisation d'une graine différente donne des mêmes résultats, le générateur lui-même n'est pas correctement écrit.

Je suggérerais de tester avec différentes graines pour tirer une conclusion.


1 commentaires

C'est une bonne idée. Je vais admettre de ne pas savoir comment semer le générateur une manière différente. Je peux simplement nourrir les numéros informatiques (comme 10 ou 127) mais cela donnera évidemment exactement la même sortie à chaque fois. Le commentaire de Richard ci-dessus semble suggérer que c'est la graine qui introduit un biais.



1
votes

Utiliser std :: Default_Random_Engine est comme dire "Surprenez-moi!" dans un mauvais restaurant. La seule chose que vous connaissez, c'est que le résultat sera médiocre - car les générateurs fournis par sont tous déficients - mais vous ne saurez même pas quels défauts particuliers vous devez faire face.

Le Twister Mersenne peut être un choix décent si - et seulement si - il est correctement ensemencé, et il se trouve le frottement. Idéalement, chaque morceau de la graine devrait affecter chaque bit de l'état générateur résultant avec une probabilité égale; Comme vous l'avez découvert, ce n'est tout simplement pas le cas dans les implémentations communes de STD :: MERSENNE_TWISTER_Engine.

Les twisters de Mersenne sont normalement initialisés avec la sortie d'un PRN plus simple qui a à son tour été ensemencé par toute entropie disponible. Cela étire efficacement l'entropie de graines du PRN plus simple sur l'énorme état de la Twister. Les fabricants de la norme ont pensément fourni l'interface SEED_SEQ à cette fin; Cependant, il semble que la bibliothèque ne contienne aucun adaptateur d'utilisation d'un générateur comme séquence de semences.

Il y a aussi la divergence entre deux conceptions différentes de l'ensemencement. Du côté du générateur, la fonction d'ensemencement doit prendre l'entropie qui a été transmise et la cartographique fidèlement sur l'état du générateur, garantissant qu'aucune entrée n'est perdue dans le processus. Sur le côté de l'utilisateur, il s'agit "Prenez ces chiffres et donnez-moi des séquences extrêmement différentes", où "ces chiffres" est {1, 2, 3, ...} ou une sortie d'horloge ().

En d'autres termes, l'entropie de graines est proposée sous une forme qui ne convient pas directement à l'initialisation de l'état du générateur; Les petites différences de graines donnent de petites différences d'état. Ceci est particulièrement problématique avec d'énormes générateurs à la traîne comme la Mersenne Twister ou les Fibonacci à la traîne qui aliment les générateurs STD :: Ranluxxx.

une fonction de mélange de bits - une fonction bijective où chaque bit de la sortie dépend de chaque Bit de l'entrée avec une probabilité égale - peut aider à faire des graines telles que 1, 2, 3 ou une sortie d'horloge () plus utile pour l'ensemencement. Le Murmur Hash Mixer s'approche de cet idéal, en réalisant une diffusion presque parfaite (32 -Bit version illustrée): xxx

La fonction est bijective, il ne perd donc aucune entropie du tout. Cela signifie que vous pouvez l'utiliser pour améliorer toutes les graines sans danger pour aggraver les choses.

Une autre solution rapide - sans effort de fabrication d'une graine_seq - consiste à appeler rejeter () sur le générateur avec un paramètre qui dépend de la (Murmur-Mixed). Cependant, l'effet sur d'énormes générateurs comme la mersenne Twister est quelque peu limité, car leur État évolue extrêmement lentement et il a besoin de centaines de milliers d'itérations pour une récupération complète des États déficients.


4 commentaires

Il est logique que la bibliothèque standard n'aurait pas quelque chose qui était le meilleur possible, certainement non sécurisé cryptographiquement, mais cela fait du travail si vous avez besoin de rouler un dés ou quelque chose. Vous avez parlé de graver la mersenne twister correctement, le aléatoire_device ne pas être un excellent choix pour cela? Il est tiré comme un le générateur de nombres aléatoire après tout.


Tous les générateurs de la bibliothèque standard échouent des tests PRNG standard tels que TESTU01 systématiquement , à l'exception des générateurs de Ranlux avec un facteur de luxe «assez grand». Vous devez être circonspect avec ces générateurs et choisissez-les soigneusement. Ce que je trouve étrange, c'est que la norme ne contient aucun bon choix à part le simple LCG qui aura probablement toujours ses utilisations (tant que vous comprenez les réserves). Qu'est-il arrivé à "Comportement de moteur au moins acceptable pour une utilisation relativement déconte, inexpérimente et / ou légère"?


aléatoire_device n'est pas garanti d'être disponible; Sinon, ce serait un bon choix pour obtenir une graine aléatoire. Cependant, il faut probablement résister à la tentation de faire une graine_seq en emballant un random_device, comme cela ferait que le programme fonctionne sans répétibilité. Mieux vaut extraire une quantité fixe raisonnable de bits - 32, 64 ou 256, selon le cas, peut être imprimée à la sortie ou à un journal et vers une version ultérieure au programme si une exécution répétée est souhaitée. La quantité fixe d'entropie peut ensuite être faite dans une graine de la taille souhaitée à l'aide d'un générateur simple et robuste comme Xorshift *.


Malheureusement, la norme ne contient pas de générateurs simples et robustes. Votre meilleur choix serait probablement ranlux24; Cependant, ce n'est ni simple ni robuste et en fait, il est lui-même l'un des générateurs dont on utiliserait normalement un générateur de semences. Les générateurs LCG sont suffisamment simples, mais la périodicité de leurs bits de sortie (bit le plus bas a la période 2, les deux les plus basses ont une période 4 et ainsi de suite) signifie qu'il existe un potentiel d'interaction imprévue avec le générateur d'être ensemencés. Si j'étais coincé avec la bibliothèque, je prendrais un LCG en tant que graine RNG et transmettez la sortie via Murmur_mix ()