web-dev-qa-db-fra.com

Est-ce que std :: mt19937 nécessite un échauffement?

J'ai lu que de nombreux générateurs de nombres pseudo-aléatoires nécessitent de nombreux échantillons pour être "réchauffés". Est-ce le cas lors de l'utilisation de std :: random_device pour amorcer std :: mt19937, ou peut-on s'attendre à ce qu'il soit prêt après la construction? Le code en question:

#include <random>
std::random_device rd;
std::mt19937 gen(rd());
46
Brent

Mersenne Twister est un pRNG (générateur de nombres pseudo-aléatoires) basé sur un registre à décalage et est donc sujet à de mauvaises graines avec de longues séries de 0 ou 1 qui conduisent à des résultats relativement prévisibles jusqu'à ce que l'état interne soit suffisamment mélangé.

Cependant, le constructeur qui prend une seule valeur utilise une fonction compliquée sur cette valeur de départ qui est conçue pour minimiser la probabilité de produire de tels "mauvais" états. Il existe une deuxième façon d'initialiser mt19937 où vous définissez directement l'état interne, via un objet conforme au concept SeedSequence. C'est cette deuxième méthode d'initialisation où vous devrez peut-être vous soucier de choisir un "bon" état ou de vous échauffer.


La norme comprend un objet conforme au concept SeedSequence, appelé seed_seq. seed_seq prend un nombre arbitraire de valeurs de départ en entrée, puis effectue certaines opérations sur ces valeurs afin de produire une séquence de différentes valeurs appropriées pour définir directement l'état interne d'un pRNG.

Voici un exemple de chargement d'une séquence de départ avec suffisamment de données aléatoires pour remplir l'ensemble std::mt19937 Etat:

std::array<int, 624> seed_data;
std::random_device r;
std::generate_n(seed_data.data(), seed_data.size(), std::ref(r));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));

std::mt19937 eng(seq);

Cela garantit que l'état entier est randomisé. En outre, chaque moteur spécifie la quantité de données qu'il lit à partir de la séquence seed_sequence, vous pouvez donc vouloir lire les documents pour trouver ces informations pour le moteur que vous utilisez.

Bien qu'ici, je charge entièrement le seed_seq à partir de std::random_device, seed_seq est spécifié de telle sorte que seuls quelques nombres qui ne sont pas particulièrement aléatoires devraient bien fonctionner. Par exemple:

std::seed_seq seq{1, 2, 3, 4, 5};
std::mt19937 eng(seq);

Dans les commentaires ci-dessous, Cubbi indique que seed_seq fonctionne en effectuant une séquence d'échauffement pour vous.

Voici ce qui devrait être votre "valeur par défaut" pour l'ensemencement:

std::random_device r;
std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
std::mt19937 rng(seed);
57
bames53

Si vous amorcez avec une seule valeur 32 bits, tout ce que vous obtiendrez sera l'une des mêmes 2 ^ 32 trajectoires à travers l'espace d'état. Si vous utilisez un PRNG avec des KiB d'état, alors vous devriez probablement tout semer. Comme décrit dans les commentaires de la réponse de @ bames63, utiliser std::seed_seq N'est probablement pas un bonne idée si vous voulez initialiser l'état entier avec des nombres aléatoires. Malheureusement, std::random_device n'est pas conforme au concept SeedSequence, mais vous pouvez écrire un wrapper qui fait:

#include <random>
#include <iostream>
#include <algorithm>
#include <functional>

class random_device_wrapper {
    std::random_device *m_dev;
public:
    using result_type = std::random_device::result_type;
    explicit random_device_wrapper(std::random_device &dev) : m_dev(&dev) {}
    template <typename RandomAccessIterator>
    void generate(RandomAccessIterator first, RandomAccessIterator last) {
        std::generate(first, last, std::ref(*m_dev));
  }
};

int main() {

    auto rd = std::random_device{};
    auto seedseq = random_device_wrapper{rd};
    auto mt = std::mt19937{seedseq};
    for (auto i = 100; i; --i)
        std::cout << mt() << std::endl;

}

Cela fonctionne au moins jusqu'à ce que vous activiez les concepts. Selon que votre compilateur connaît SeedSequence en tant que C++ 20 concept, il peut ne pas fonctionner car nous fournissons uniquement la méthode generate() manquante, rien d'autre . Dans la programmation de modèles de type canard, ce code est cependant suffisant, car le PRNG ne stocke pas l'objet de séquence de départ.

4

Je crois qu'il existe des situations où MT peut être semé "mal", ce qui entraîne des séquences non optimales. Si je me souviens bien, l'ensemencement avec tous les zéros est un tel cas. Je vous recommande d'essayer de utilisez les générateurs WELL si c'est un problème sérieux pour vous. Je pense qu'ils sont plus flexibles - la qualité de la semence n'a pas autant d'importance. (Peut-être pour répondre à votre question plus directement: il est probablement plus efficace de se concentrer sur l'ensemencement aussi bien que plutôt que de mal ensemencer, puis d'essayer de générer un tas d'échantillons pour amener le générateur à un état optimal.)

2
Mayur Patel