Разминка крутильной машины Мерсенна против воспроизводимости

В моем текущем проекте на C ++ 11 мне нужно выполнить M-моделирование. Для каждой симуляции m = 1, ..., M я случайным образом генерирую набор данных, используя объект std::mt19937, построенный следующим образом:

std::mt19937 generator(m);
DatasetFactory dsf(generator);

Согласно https://stackoverflow.com/a/15509942/1849221 и https://stackoverflow.com/a/14924350/1849221, ГПСЧ Mersenne Twister извлекает выгоду из фазы разогрева, которая в настоящее время отсутствует в моем коде. Сообщаю для удобства предлагаемый фрагмент кода:

#include <random>

std::mt19937 get_prng() {
    std::uint_least32_t seed_data[std::mt19937::state_size];
    std::random_device r;
    std::generate_n(seed_data, std::mt19937::state_size, std::ref(r));
    std::seed_seq q(std::begin(seed_data), std::end(seed_data));
    return std::mt19937{q};
}

Проблема в моем случае заключается в том, что мне нужна воспроизводимость результатов, то есть при разных исполнениях для каждой симуляции набор данных должен быть одинаковым. Вот почему в моем текущем решении я использую текущее моделирование для заполнения ГПСЧ Mersenne Twister. Мне кажется, что использование std::random_device предотвращает совпадение данных (AFAIK, это точная цель std::random_device).

РЕДАКТИРОВАТЬ: под разными исполнениями я имею в виду повторный запуск исполняемого файла.

Как я могу ввести в код вышеупомянутую фазу разогрева, не влияя на воспроизводимость? Спасибо.

Возможное решение # 1

Вот примерная реализация, основанная на втором предложении @SteveJessop

#include <random>

std::mt19937 get_generator(unsigned int seed) {
        std::minstd_rand0 lc_generator(seed);
        std::uint_least32_t seed_data[std::mt19937::state_size];

        std::generate_n(seed_data, std::mt19937::state_size, std::ref(lc_generator));
        std::seed_seq q(std::begin(seed_data), std::end(seed_data));
        return std::mt19937{q};
    }

Возможное решение # 2

Вот предварительная реализация, основанная на совместном вкладе @SteveJassop и @ AndréNeve. Функция sha256 адаптирована из https://stackoverflow.com/a/10632725/1849221

#include <openssl/sha.h>
#include <sstream>
#include <iomanip>
#include <random>

 std::string sha256(const std::string str) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str.c_str(), str.size());
    SHA256_Final(hash, &sha256);

    std::stringstream ss;
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) 
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];

    return ss.str();
}

std::mt19937 get_generator(unsigned int seed) {
    std::string seed_str = sha256(std::to_string(seed));
    std::seed_seq q(seed_str.begin(), seed_str.end());
    return std::mt19937{q};
}

Скомпилировать с: -I/opt/ssl/include/ -L/opt/ssl/lib/ -lcrypto


person Ilio Catallo    schedule 18.04.2013    source источник
comment
Разве вы не можете просто прочитать фиксированный объем данных из ГПСЧ?   -  person CodesInChaos    schedule 18.04.2013
comment
Вы имеете в виду, что качество псевдослучайной последовательности будет улучшаться по мере того, как вы запрашиваете новые данные? Моя цель - явно учитывать std::mt19937::state_size на этапе инициализации, сохраняя при этом воспроизводимость.   -  person Ilio Catallo    schedule 18.04.2013
comment
Все генераторы случайных чисел имеют функцию-член discard(n) для улучшения внутреннего состояния as-if, вызывая operator() n-раз.   -  person Xeo    schedule 18.04.2013
comment
Достигает ли операция discard(n) того же результата при использовании std::seed_seq такого же размера, как std::mt19937::state_size, для заполнения ГПСЧ? Какой подходящий n будет использоваться?   -  person Ilio Catallo    schedule 18.04.2013
comment
В возможном 2 std::hash<unsigned int> недостаточно хорошо. Проблема с MT, которую вы пытаетесь преодолеть, заключается в том, что ему требуется много ненулевых битов начальных данных, в противном случае его внутреннее состояние в основном равно 0, и он выводит неверные данные. std::hash - не тот хеш-код для решения этой проблемы. В лучшем случае он по-прежнему предоставляет только 64 бита начальных данных, и это еще хуже, поскольку это, скорее всего, операция идентификации. Если вы использовали, например, SHA256-хэш m, возможно, вы занимаетесь бизнесом.   -  person Steve Jessop    schedule 19.04.2013
comment
Вы правы, в случае целочисленных значений std::hash, вероятно, реализован как функция идентификации. Я постараюсь пересмотреть решение №2, чтобы использовать openssl/sha.h   -  person Ilio Catallo    schedule 19.04.2013
comment
Я знаю, что опаздываю, но, наконец, у меня появилась возможность исправить второе решение.   -  person Ilio Catallo    schedule 08.05.2013


Ответы (3)


Два варианта:

  1. Следуйте предложению, которое у вас есть, но вместо использования std::random_device r; для генерации вашей начальной последовательности для MT, используйте другой PRNG с начальным значением m. Выберите тот, который не страдает, как MT, от необходимости разогрева при использовании небольших данных: я подозреваю, что LCG, вероятно, подойдет. Для массового уничтожения вы даже можете использовать ГПСЧ на основе безопасного хэша. Если вы слышали об этом, это очень похоже на «растяжение ключа» в криптографии. Фактически вы можете использовать стандартный алгоритм растяжения ключа, но вы используете его для генерации длинной исходной последовательности, а не большого ключевого материала.

  2. Продолжайте использовать m для заполнения вашего MT, но discard большой постоянный объем данных перед запуском моделирования. Иными словами, игнорируйте совет использовать сильное начальное число и вместо этого запускайте MT достаточно долго, чтобы он достиг приличного внутреннего состояния. Я не знаю, сколько данных вам нужно отбросить, но я полагаю, что это знает Интернет.

person Steve Jessop    schedule 18.04.2013
comment
Проблема в том, что он использует std::random_device для засева Mersenne Twister при каждом запуске, что (по сути) никогда не будет иметь воспроизводимых эффектов. Он действительно может использовать другой PNRG, для которого не требуется фаза разминки, но он по-прежнему не может генерировать новое семя при каждом запуске. Он должен создать семена один раз и хранить их где-нибудь между прогонами, чтобы добиться воспроизводимости. Очевидно, это не обязательно должно быть сильное семя. - person André Neves; 18.04.2013
comment
@ AndréNeves: Я не думаю, что спрашивающий хочет новое семя при каждом запуске. Он хочет M различных семян, которые (а) будут одинаковыми при каждом запуске; но (б) в отличие от значений 1 ... M, которые он в настоящее время использует, не являются слабыми начальными числами для МП. В вопросе указано, что он не в настоящее время использует код random_device. В настоящее время он заполняет МП небольшим целым числом, а код random_device - это предложение, которое он нашел по другому вопросу SO, который ему не подходит. - person Steve Jessop; 18.04.2013
comment
Хорошо, я думал, что он рассматривает возможность использования приведенного выше фрагмента. В любом случае, я понимаю, что он хочет иметь M набор семян, и если он хочет использовать достойные семена, он может использовать random_device без каких-либо проблем, ему просто нужно сохранить его для каждого набора данных. Я помню один дешевый способ, позволяющий не использовать сильное начальное число и не хранить его, но при этом быть относительно случайным, - это использовать хэш набора данных (или какой-либо его части) в качестве начального значения для MT (или другой PNRG). Таким образом, ему не нужно было бы хранить его, поскольку он был бы детерминированным для каждого набора данных. - person André Neves; 18.04.2013
comment
@ AndréNeves: Согласен, он может хранить настоящие случайные данные. В этом отношении ему не понадобится файл / db / что-то еще, он может сгенерировать их один раз и скомпилировать в (файл ресурсов, преобразовать в шестнадцатеричный и вставить в исходный, что угодно). Взятие одного хэша m - это вырожденный случай растяжения ключа - недостаточно для криптографии, но здесь достаточно хорошо, при условии, что размер хэша не слишком мал по сравнению с std::mt19937::state_size. Если он слишком мал, то другой импровизированный алгоритм растяжения ключа будет заключаться в объединении хэшей m, m+M, m+2M, ... до тех пор, пока у вас не будет достаточно данных. - person Steve Jessop; 18.04.2013
comment
Да, он мог сохранить семена в виде файла ресурсов или непосредственно в коде, но это было бы, возможно, слишком статично и даже жестко запрограммировано. В любом случае, я думаю, что хеш в качестве начального числа - хорошее решение, и единственная возможная проблема - это его длина, как вы упомянули, но которую легко обойти. - person André Neves; 18.04.2013
comment
Во-первых, спасибо за большое обсуждение моей проблемы. Что касается вашего предложения, набор данных будет сгенерирован, начиная с ГПСЧ. Если я правильно понимаю вашу идею, вы предлагаете заполнить ГПСЧ путем вычисления какого-то хэша на наборе данных. Дело в том, что я не могу сгенерировать набор данных, пока не получу ГПСЧ. - person Ilio Catallo; 19.04.2013
comment
@ burton0: в вашем случае набор данных Андре упоминает, что начальное число вычисляется из числа m. Затем, конечно, вы используете это начальное число для генерации того, что в вопросе называется набором данных, на котором вы собираетесь запустить симуляцию. - person Steve Jessop; 19.04.2013
comment
Я отредактировал исходный вопрос, чтобы учесть ваше предложение. По-прежнему есть предупреждение о неявном преобразовании с size_t в unsigned int. - person Ilio Catallo; 19.04.2013

Я думаю, что вам нужно сохранить только начальное семя (в вашем случае массив std::uint_least32_t seed_data[std::mt19937::state_size]) и количество n шагов разминки, которые вы сделали (например, с использованием discard(n), как упоминалось) для каждого запуска / моделирования, которое вы хотите воспроизвести.

Обладая этой информацией, вы всегда можете создать новый экземпляр MT, заполнить его предыдущими seed_data и запустить на тех же n этапах разминки. В дальнейшем будет сгенерирована такая же последовательность значений, поскольку экземпляр MT будет иметь такое же внутреннее состояние по окончании разминки.

Когда вы упоминаете std::random_device, влияющие на воспроизводимость, я считаю, что в вашем коде он просто используется для генерации исходных данных. Если бы вы использовали его как источник самих случайных чисел, вы не смогли бы получить воспроизводимые результаты. Поскольку вы используете его только для генерации семени, проблем быть не должно. Вы просто не можете генерировать новое семя каждый раз, если хотите воспроизводить значения!

Из определения std::random_device:

«std :: random_device - это равномерно распределенный генератор целых случайных чисел, который производит недетерминированные случайные числа».

Поэтому, если он не детерминирован, вы не можете воспроизвести последовательность производимых им значений. При этом используйте его просто для генерации хороших случайных начальных чисел только для их последующего сохранения для повторных запусков.

Надеюсь это поможет

РЕДАКТИРОВАТЬ:

После обсуждения с @SteveJessop мы пришли к выводу, что простого хеша набора данных (или его части) будет достаточно, чтобы использовать его в качестве достойного начального числа для нужной вам цели. Это позволяет детерминированным способом генерировать одни и те же начальные числа каждый раз, когда вы запускаете моделирование. Как упомянул @Steve, вам нужно будет гарантировать, что размер хеша не слишком мал по сравнению с std::mt19937::state_size. Если он слишком мал, вы можете объединить хэши m, m + M, m + 2M, ... до тех пор, пока у вас не будет достаточно данных, как он предложил.

Я публикую здесь обновленный ответ, так как идея использования хеша была моей, но я буду поддерживать ответ @ SteveJessop, потому что он внес в него свой вклад.

person André Neves    schedule 18.04.2013
comment
Мне нужно создать M разные наборы данных, которые будут использоваться в M различных симуляциях. Мне нужно, чтобы наборы данных были независимыми, т.е. каждый набор данных генерируется с использованием ГПСЧ, начальное число которого уникально для конкретной симуляции. В моем текущем решении уникальность достигается за счет использования последовательности начальных чисел от 1 до M. Похоже, вы предлагаете создать в начале кода master начальную последовательность q, чтобы результирующая std::mt19937 изменялась в каждой симуляции с помощью discard(n), где n различается для каждой симуляции. Правильно? - person Ilio Catallo; 18.04.2013
comment
Вам необходимо создать master начальную последовательность (seed_data массив) и master n для каждой симуляции, которую вы хотите воспроизвести. Итак, если я правильно понял, у вас будет M разных master seed_data и M n, по одному для каждой симуляции. Затем для каждой симуляции вы вызываете seed(seed_data), а затем discard(n). Вы можете создать массивы размера M для ваших M наборов параметров, а затем обновить свой std::mt19937 экземпляр, используя seed(seed_data_array[i]), а затем discard(n_array[i]), где i - идентификатор набора данных (0..M-1). - person André Neves; 18.04.2013
comment
Таким образом, проблема в том, где взять M разные основные начальные последовательности и ns. Я отредактировал исходный вопрос, чтобы подчеркнуть, что под разными исполнениями я имею в виду повторный запуск исполняемого файла. - person Ilio Catallo; 18.04.2013
comment
Вам нужно будет постоянно хранить M исходные последовательности и ns (например, файл, базу данных), чтобы вы могли загрузить их позже при повторном запуске исполняемого файла. Таким образом, у вас может быть простой аргумент, что когда true запускает моделирование и генерирует основные исходные данные, а когда false просто читает основные исходные данные и эффективно воспроизводит моделирование. - person André Neves; 18.04.2013

Комментарий к одному из ответов, на который вы ссылаетесь, указывает:

По совпадению, Seq_seq C ++ 11 по умолчанию - это последовательность разогрева Mersenne Twister (хотя существующие реализации, например mt19937 в libc ++, используют более простой разогрев, когда предоставляется начальное значение с одним значением)

Так что вы можете использовать ваши текущие фиксированные семена с std::seed_seq, чтобы сделать за вас разминку.

std::mt19937 get_prng(int seed) {
    std::seed_seq q{seed, maybe, some, extra, fixed, values};
    return std::mt19937{q};
}
person bames53    schedule 18.04.2013