Требуется ли прогрев std::mt19937?

Я читал, что многим генераторам псевдослучайных чисел требуется много образцов для «разогрева». Так ли это при использовании std::random_device для заполнения std::mt19937, или мы можем ожидать, что он будет готов после построения? Рассматриваемый код:

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

person Brent    schedule 19.03.2013    source источник
comment
Где ты это прочитал? Я никогда не слышал об этом, все, что я знаю, это то, что они должны быть засеяны...   -  person PlasmaHH    schedule 20.03.2013
comment
Например, это обсуждается в этой статье: www0 .cs.ucl.ac.uk/staff/d.jones/GoodPracticeRNG.pdf   -  person Brent    schedule 22.03.2014
comment
Для большинства PRNG это вообще не имеет смысла. Заполнение устанавливает внутреннее состояние, а любое прогревание изменяет внутреннее состояние, поскольку оно имеет точно такой же эффект, если бы это новое состояние было выбрано в качестве затравки.   -  person PlasmaHH    schedule 22.03.2014
comment
FWIW многие не советуют std::random_device, так как он может бросить в любой момент по разным бессмысленным причинам. Вы можете обернуть его в блок try..catch, но я бы рекомендовал использовать специфичный для платформы способ получения случайного числа: в Windows используйте Crypto API, в противном случае используйте /dev/urandom/.   -  person BoltzmannBrain    schedule 06.12.2018


Ответы (3)


Mersenne Twister — это pRNG (генератор псевдослучайных чисел), основанный на регистре сдвига, и, следовательно, он подвержен неправильным начальным числам с длинными сериями нулей или единиц, которые приводят к относительно предсказуемым результатам до тех пор, пока внутреннее состояние не будет достаточно перепутано.

Однако конструктор, который принимает одно значение, использует сложную функцию для этого начального значения, которая предназначена для минимизации вероятности создания таких «плохих» состояний. Существует второй способ инициализации mt19937, когда вы напрямую устанавливаете внутреннее состояние через объект, соответствующий концепции SeedSequence. Это второй метод инициализации, когда вам, возможно, придется побеспокоиться о выборе «хорошего» состояния или о прогреве.


Стандарт включает объект, соответствующий концепции SeedSequence, который называется seed_seq. seed_seq принимает произвольное количество входных начальных значений, а затем выполняет определенные операции над этими значениями, чтобы создать последовательность различных значений, подходящих для прямой установки внутреннего состояния pRNG.

Вот пример загрузки исходной последовательности с достаточным количеством случайных данных, чтобы заполнить все состояние std::mt19937:

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);

Это гарантирует рандомизацию всего состояния. Кроме того, каждый движок указывает, сколько данных он считывает из seed_sequence, поэтому вы можете прочитать документацию, чтобы найти эту информацию для любого движка, который вы используете.

Хотя здесь я загружаю seed_seq полностью из std::random_device, seed_seq указано так, что всего несколько чисел, которые не являются особенно случайными, должны работать хорошо. Например:

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

В комментариях ниже Cubbi указывает, что seed_seq работает, выполняя для вас последовательность разогрева.

Вот что должно быть вашим «по умолчанию» для раздачи:

std::random_device r;
std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
std::mt19937 rng(seed);
person bames53    schedule 19.03.2013
comment
По совпадению, C++11 seed_seq по умолчанию является последовательностью прогрева Mersenne Twister (хотя существующие реализации, например mt19937 в libc++, используют более простой прогрев, когда предоставляется начальное число с одним значением) - person Cubbi; 20.03.2013
comment
std::generate_n генерирует предупреждение SDL C4996 для более новых компиляторов MSVC, поэтому вместо него можно использовать std::generate(seed_data.begin(), seed_data.end(), std::ref(r));. - person NightElfik; 18.02.2014
comment
Безопасно ли предположить, что state_size генератора всегда выражается кратно sizeof(int)? Например, если бы генератор имел state_size, измеряемый в байтах, массив seed_data был бы слишком большим. - person Wyzard; 20.03.2014
comment
@Wyzard Каждый класс движка точно указывает, сколько значений он считывает из seed_seq. Так получилось, что state_size для класса std::mt19937, но, например, 2*std::mt19937_64::state_size для std::mt19937_64. Вы должны прочитать документы для конкретного двигателя. Похоже, что онлайн-ссылки не вдаются в эти детали, но они описаны в стандарте. - person bames53; 20.03.2014
comment
Хм. Я удивлен, что он не определен как константа в классе для использования универсальным кодом, но, по крайней мере, он хорошо определен. - person Wyzard; 22.03.2014
comment
В то время как стандартное соответствие вашего ответа может привести к плохим результатам, потому что вы не создаете внутреннее состояние напрямую, а вместо этого используете std::seed_seq, который изменяет заданные значения. Дополнительную информацию см. здесь. Хотя, судя по вашему нику, вы уже нашли его на Reddit, где опубликовали хорошую альтернативу. - person mfuchs; 21.04.2015
comment
@mat69 Да, я знаю о работе /u/ProfONEill, в принципе согласен с ее критикой и с нетерпением жду ее предложений по улучшению положения. Вы можете увидеть ее комментарий к этому ответу stackoverflow здесь. Также обратите внимание, что основная критика seed_seq в этой статье становится все менее и менее значимой по мере увеличения случайности ввода. ProfONeil говорит, что 256 бит, вероятно, достаточно. - person bames53; 21.04.2015
comment
Как насчет конструктора по умолчанию для движка, использует ли он разумное начальное значение? - person BeeOnRope; 05.05.2017
comment
@BeeOnRope Это разумно в том смысле, что это не одно из семян, которое дает плохие результаты, а фиксировано на определенное значение, поэтому вы всегда знаете, какие значения будет генерировать созданный по умолчанию движок mt19937. - person bames53; 05.05.2017
comment
Многие отговаривают от std::random_device, так как он может бросить в любой момент по разным глупым причинам. Вы можете обернуть его в блок try..catch, но я бы рекомендовал использовать специфичный для платформы способ получения случайного числа: в Windows используйте Crypto API, в противном случае используйте /dev/urandom/. - person BoltzmannBrain; 06.12.2018

Если вы зададите только одно 32-битное значение, все, что вы когда-либо получите, — это одна из тех же 2^32 траекторий в пространстве состояний. Если вы используете PRNG с KiB состояния, то вам, вероятно, следует заполнить все это. Как описано в комментариях к ответу @bames63, использование std::seed_seq, вероятно, не очень хорошая идея, если вы хотите инициализировать все состояние случайными числами. К сожалению, std::random_device не соответствует концепции SeedSequence, но вы можете написать оболочку, которая соответствует:

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

}

Это работает, по крайней мере, до тех пор, пока вы не включите концепции. В зависимости от того, знает ли ваш компилятор о SeedSequence как о concept C++20, он может не работать, потому что мы предоставляем только отсутствующий метод generate() и ничего больше. Однако в шаблонном программировании с утиным типом этого кода достаточно, потому что PRNG не хранит объект начальной последовательности.

person Marc Mutz - mmutz    schedule 06.05.2018

Я полагаю, что бывают ситуации, когда MT может быть засеян «плохо», что приводит к неоптимальным последовательностям. Если я правильно помню, заполнение всеми нулями — один из таких случаев. Я бы порекомендовал вам попробовать использовать генераторы WELL, если это серьезная проблема для вас. Я считаю, что они более гибкие - качество семян не так важно. (Возможно, чтобы ответить на ваш вопрос более прямо: вероятно, более эффективно сосредоточиться на правильном заполнении, а не на плохом заполнении, а затем пытаться сгенерировать кучу образцов, чтобы привести генератор в оптимальное состояние.)

person Mayur Patel    schedule 19.03.2013