J'ai remarqué que vous pouvez mettre différents numéros dans numpy.random.seed()
, par exemple numpy.random.seed(1)
, numpy.random.seed(101)
. Que signifient les différents nombres? Comment choisissez-vous les nombres?
Considérons un générateur de nombres aléatoires très basique:
Z[i] = (a*Z[i-1] + c) % m
Ici, Z[i]
est le nombre aléatoire ith
, a
est le multiplicateur et c
est l’augmentation - pour différentes combinaisons a
, c
et m
, vous avez différents générateurs. Ceci est connu sous le nom de générateur congruentiel linéaire introduit par Lehmer. Le reste de cette division, ou module (%
), générera un nombre compris entre zéro et m-1
et en réglant U[i] = Z[i] / m
, vous obtiendrez des nombres aléatoires compris entre zéro et un.
Comme vous l'avez peut-être remarqué, pour démarrer ce processus génératif - afin d'avoir un Z[1]
, vous devez avoir un Z[0]
- une valeur initiale. Cette valeur initiale qui lance le processus s'appelle la graine. Jetez un oeil à cet exemple:
La valeur initiale, la graine est déterminée en tant que 7 pour démarrer le processus. Cependant, cette valeur n'est pas utilisée pour générer un nombre aléatoire. Au lieu de cela, il est utilisé pour générer la première Z
.
La caractéristique la plus importante d'un générateur de nombres pseudo-aléatoires serait son imprévisibilité. En règle générale, tant que vous ne partagez pas vos semences, vous vous en tirez bien, car les générateurs actuels sont beaucoup plus complexes que cela. Cependant, vous pouvez également générer la graine de manière aléatoire. Vous pouvez également ignorer les premiers nombres n
.
Source principale: Law, A. M. (2007). Modélisation et analyse de la simulation. Tata McGraw-Hill.
Ce qu'on appelle normalement une séquence de nombres aléatoires en réalité est une séquence de nombres "pseudo-aléatoires" car les valeurs sont calculées à l'aide d'un algorithme déterministe et la probabilité ne joue aucun rôle réel.
La "graine" est un point de départ pour la séquence et la garantie est que si vous partez de la même graine, vous obtiendrez la même séquence de chiffres. Ceci est très utile par exemple pour le débogage (lorsque vous recherchez une erreur dans un programme, vous devez être capable de reproduire le problème et de l'étudier, un programme non déterministe serait beaucoup plus difficile à déboguer car chaque exécution serait différente). .
La réponse courte:
Il existe trois manières de seed()
un générateur de nombres aléatoires dans numpy.random
:
n'utilise aucun argument ou utilise None
- le générateur de ressources aléatoires s'initialise à partir du générateur de nombres aléatoires du système d'exploitation (qui est généralement aléatoire de manière cryptographique)
utiliser un entier de 32 bits N - le GNA utilisera cette information pour initialiser son état sur la base d'une fonction déterministe (même graine → même état)
utiliser une séquence d'entiers de 32 bits n, n1, n2, etc. - là encore, le GNA utilisera cette information pour initialiser son état sur la base d’une fonction déterministe (mêmes valeurs pour la valeur initiale → même état). Ceci est destiné à être fait avec une sorte de fonction de hachage, bien qu'il y ait des nombres magiques dans le code source et que la raison pour laquelle ils font ce qu'ils ne savent pas est claire.
Si vous voulez faire quelque chose de simple et répétable, utilisez un seul entier.
Si vous voulez faire quelque chose de reproductible mais peu probable pour un tiers, utilisez un tuple, une liste ou un tableau numpy contenant une séquence d'entiers 32 bits. Vous pouvez, par exemple, utiliser numpy.random
avec une graine de None
pour générer un ensemble d’entiers sur 32 bits (disons 32, ce qui générerait un total de 1024 bits) à partir du RNG du système d’exploitation, dans une graine S
que vous sauvegardez dans un endroit secret, puis utilisez cette graine pour générer la séquence R de nombres pseudo-aléatoires de votre choix. Ensuite, vous pourrez recréer cette séquence ultérieurement en ré-amorçant à nouveau S
, et tant que vous gardez la valeur S
secrète (ainsi que les nombres générés R), personne ne sera en mesure de reproduire cette séquence R. utilisez un seul entier, il n'y a que 4 milliards de possibilités et quelqu'un pourrait potentiellement toutes les essayer. C'est peut-être un peu paranoïaque, mais vous pouvez le faire.
Réponse plus longue
Le module numpy.random
utilise l'algorithme Mersenne Twister , que vous pouvez confirmer vous-même de l'une des deux manières suivantes:
Soit en consultant la documentation de numpy.random.RandomState
, dont numpy.random
utilise une instance pour les fonctions numpy.random.*
(mais vous pouvez également utiliser une instance isolée de)
En regardant le code source in mtrand.pyx qui utilise quelque chose appelé Pyrex pour envelopper une implémentation rapide de C, et randomkit.c et initarray.c .
Dans tous les cas, voici ce que dit la documentation numpy.random.RandomState
à propos de seed()
:
Garantie de compatibilité Un début et une série fixes d'appels à des méthodes
RandomState
utilisant les mêmes paramètres produiront toujours les mêmes résultats jusqu'à l'erreur d'arrondi, sauf lorsque les valeurs sont incorrectes. Les valeurs incorrectes seront corrigées et la version de NumPy dans laquelle le correctif a été apporté sera notée dans la docstring appropriée. L'extension des plages de paramètres existantes et l'ajout de nouveaux paramètres sont autorisés tant que le comportement précédent reste inchangé.Paramètres:
seed _ : {None, int, array_like}, facultatifGraine aléatoire utilisée pour initialiser le générateur de nombres pseudo-aléatoires. Peut être n'importe quel entier compris entre 0 et 2 ** 32 - 1 inclus, un tableau (ou une autre séquence) de tels entiers, ou
None
(valeur par défaut). Si la valeur de départ estNone
, RandomState essaiera de lire les données à partir de/dev/urandom
(ou de l'analogue Windows) si elles sont disponibles ou sinon à partir de l'horloge.
Il ne dit pas comment la graine est utilisée, mais si vous creusez dans le code source, cela fait référence à la fonction init_by_array
: (docstring elided)
def seed(self, seed=None):
cdef rk_error errcode
cdef ndarray obj "arrayObject_obj"
try:
if seed is None:
with self.lock:
errcode = rk_randomseed(self.internal_state)
else:
idx = operator.index(seed)
if idx > int(2**32 - 1) or idx < 0:
raise ValueError("Seed must be between 0 and 2**32 - 1")
with self.lock:
rk_seed(idx, self.internal_state)
except TypeError:
obj = np.asarray(seed).astype(np.int64, casting='safe')
if ((obj > int(2**32 - 1)) | (obj < 0)).any():
raise ValueError("Seed must be between 0 and 2**32 - 1")
obj = obj.astype('L', casting='unsafe')
with self.lock:
init_by_array(self.internal_state, <unsigned long *>PyArray_DATA(obj),
PyArray_DIM(obj, 0))
Et voici à quoi ressemble la fonction init_by_array
:
extern void
init_by_array(rk_state *self, unsigned long init_key[], npy_intp key_length)
{
/* was signed in the original code. RDH 12/16/2002 */
npy_intp i = 1;
npy_intp j = 0;
unsigned long *mt = self->key;
npy_intp k;
init_genrand(self, 19650218UL);
k = (RK_STATE_LEN > key_length ? RK_STATE_LEN : key_length);
for (; k; k--) {
/* non linear */
mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1664525UL))
+ init_key[j] + j;
/* for > 32 bit machines */
mt[i] &= 0xffffffffUL;
i++;
j++;
if (i >= RK_STATE_LEN) {
mt[0] = mt[RK_STATE_LEN - 1];
i = 1;
}
if (j >= key_length) {
j = 0;
}
}
for (k = RK_STATE_LEN - 1; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL))
- i; /* non linear */
mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
i++;
if (i >= RK_STATE_LEN) {
mt[0] = mt[RK_STATE_LEN - 1];
i = 1;
}
}
mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
self->gauss = 0;
self->has_gauss = 0;
self->has_binomial = 0;
}
Il s'agit essentiellement de "masquer" l'état de nombre aléatoire dans une méthode non linéaire ressemblant à un hachage, en utilisant chaque valeur dans la séquence fournie de valeurs de départ.
Pour comprendre la signification des germes aléatoires, vous devez d'abord comprendre la séquence de nombres "pseudo-aléatoires" car les valeurs sont calculées à l'aide d'un algorithme déterministe.
Vous pouvez donc considérer ce nombre comme une valeur de départ pour calculer le prochain nombre que vous obtiendrez du générateur aléatoire. Si vous mettez la même valeur ici, votre programme obtiendra la même valeur "aléatoire" à chaque fois, de sorte que votre programme devient déterministe.
Comme dit dans ce post
ils (
numpy.random
etrandom.random
) utilisent tous deux la séquence twister de Mersenne pour générer leurs nombres aléatoires, et ils sont tous les deux complètement déterministes - en d'autres termes, si vous connaissez quelques informations clés, il est possible de prédire avec une certitude absolue le nombre à venir. suivant.
Si vous vous souciez vraiment du hasard, demandez à l'utilisateur de générer du bruit (quelques mots arbitraires) ou simplement de mettre l'heure du système comme valeur de départ.
Si vos codes fonctionnent sur les processeurs Intel (ou AMD avec les puces les plus récentes), je vous suggère également de vérifier le paquet RdRand qui utilise l'instruction cpu rdrand
pour collecter le caractère "vrai" (matériel) aléatoire.
Refs:
Fondamentalement, le nombre garantit le même «caractère aléatoire» à chaque fois.
Plus exactement, le nombre est une graine, qui peut être un entier, un tableau (ou une autre séquence) d'entiers de n'importe quelle longueur, ou la valeur par défaut (aucune). Dans le cas contraire, random essaiera de lire les données de/dev/urandom si elles sont disponibles ou créera une graine de l’horloge sinon.
Edit: En toute honnêteté, tant que votre programme ne doit pas être super sécurisé, le choix ne devrait pas être important. Si tel est le cas, n'utilisez pas ces méthodes. Utilisez os.urandom()
ou SystemRandom
si vous avez besoin d'un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé.
Le concept le plus important à comprendre ici est celui du pseudo-aléatoire. Une fois que vous avez compris cette idée, vous pouvez déterminer si votre programme a vraiment besoin d'une graine, etc. Je vous recommande de lire ici .