2017-02-04 1 views
2

Используя стандартный генератор случайных чисел C++, я могу более или менее эффективно создавать последовательности с заранее определенными дистрибутивами с использованием языковых инструментов. Как насчет энтропии Шеннона? Возможно ли каким-то образом определить выходную энтропию Шеннона для предоставленной последовательности?C++ случайный генератор с предоставленной (по крайней мере, оцененной) энтропией

Я пробовал небольшой эксперимент, сгенерировал достаточно длинную последовательность с линейным распределением и реализовал калькулятор энтропии Шеннона. Результирующее значение от 0,0 (абсолютного порядка) до 8,0 (абсолютного хаоса)

template <typename T> 
double shannon_entropy(T first, T last) 
{ 
    size_t frequencies_count{}; 
    double entropy = 0.0; 

    std::for_each(first, last, [&entropy, &frequencies_count] (auto item) mutable { 

     if (0. == item) return; 
     double fp_item = static_cast<double>(item); 
     entropy += fp_item * log2(fp_item); 
     ++frequencies_count; 
    }); 

    if (frequencies_count > 256) { 
     return -1.0; 
    } 

    return -entropy; 
} 

std::vector<uint8_t> generate_random_sequence(size_t sequence_size) 
{ 
    std::vector<uint8_t> random_sequence; 
    std::random_device rnd_device; 

    std::cout << "Random device entropy: " << rnd_device.entropy() << '\n'; 

    std::mt19937 mersenne_engine(rnd_device()); 
    std::uniform_int_distribution<unsigned> dist(0, 255); 

    auto gen = std::bind(dist, mersenne_engine); 
    random_sequence.resize(sequence_size); 
    std::generate(random_sequence.begin(), random_sequence.end(), gen); 
    return std::move(random_sequence); 
} 

std::vector<double> read_random_probabilities(size_t sequence_size) 
{ 
    std::vector<size_t> bytes_distribution(256); 
    std::vector<double> bytes_frequencies(256); 

    std::vector<uint8_t> random_sequence = generate_random_sequence(sequence_size); 

    size_t rnd_seq_size = random_sequence.size(); 
    std::for_each(random_sequence.begin(), random_sequence.end(), [&](uint8_t b) mutable { 
     ++bytes_distribution[b]; 
    }); 

    std::transform(bytes_distribution.begin(), bytes_distribution.end(), bytes_frequencies.begin(), 
     [&rnd_seq_size](size_t item) { 
     return static_cast<double>(item)/rnd_seq_size; 
    }); 
    return std::move(bytes_frequencies); 
} 

int main(int argc, char* argv[]) { 

    size_t sequence_size = 1024 * 1024; 
    std::vector<double> bytes_frequencies = read_random_probabilities(sequence_size); 
    double entropy = shannon_entropy(bytes_frequencies.begin(), bytes_frequencies.end()); 

    std::cout << "Sequence entropy: " << std::setprecision(16) << entropy << std::endl; 

    std::cout << "Min possible file size assuming max theoretical compression efficiency:\n"; 
    std::cout << (entropy * sequence_size) << " in bits\n"; 
    std::cout << ((entropy * sequence_size)/8) << " in bytes\n"; 

    return EXIT_SUCCESS; 
} 

Во-первых, оказывается, что std::random_device::entropy() зашиты к return 32; в MSVC 2015 (которая, вероятно, 8.0 согласно определению Шеннона). Как вы можете попробовать, это недалеко от правды, этот пример всегда близок к 7.9998 ..., т. Е. Абсолютный хаос.

Рабочий пример на IDEONE (кстати, их компилятор жестко энтропию на 0)

еще один, самый главный вопрос - можно ли создать такой генератор, который генерируют линейно-распределенную последовательность с определена энтропия, скажем, от 6,0 до 7,0? Может ли это быть реализовано вообще, и если да, если есть некоторые реализации?

+0

То, что оценочная энтропия немного меньше 8, является хорошим знаком, а не признаком близости к истине. Если бы это было ровно 8, это, вероятно, было бы сломано, подобно тому, как если бы кто-то переворачивал монету миллион раз и получал ровно 500 000 хвостов. –

+0

Что делать, если я хочу получить более низкую энтропию? Конечно, это не могло быть точным, но его множественные последовательности поколений могли нормально распределяться с пиком вокруг, скажем, 6.0 –

ответ

4

Во-первых, вы полностью изучаете теорию Шеннона. Его аргумент (как вы его используете) просто: «учитывая, вероятно, x (Pr(x)), бит, необходимый для хранения x, равен -log2 Pr(x). Он не имеет ничего общего с вероятностью x. просмотр Pr(x) неправильно. -log2 Pr(x) дали Pr(x), которые должны быть равномерно 1/256 результаты в требуемом битовой из 8 битов для хранения. Однако, это не так, как статистика работы. вернуться к мысли о Pr(x), поскольку биты, необходимые ничего не значит.

Ваш вопрос о статистике.Учитывая бесконечный образец, if-and-only-if распределение соответствует идеальной гистограмме, так как размер выборки приближается к бесконечности, вероятность того, что каждый образец приблизится к ожидаемой частоте. Я хочу дать понять, что вы не ищете «-log2 Pr(x) - это абсолютный хаос, когда он 8, данный Pr(x) = 1/256». Равномерное распределение является не хаос. На самом деле, это ... хорошо, форма. Его свойства хорошо известны, просты и просты в прогнозировании. Вы ищете: «конечный наборS, отвечающий критериям независимо распределенного равномерного распределения (широко известный как« Independently and Identically Distributed Data »или« i.i.d ») Pr(x) = 1/256?» Это не имеет ничего общего с теорией Шеннона и идет гораздо дальше назад к основным теориям вероятностей, связанных с переворотами монеты (в данном случае binomial при условии предполагаемой однородности).

Предполагая на мгновение, что генератор C++ 11 <random> отвечает критериям «статистически неотличимы от i.i.d.» (которые, кстати, эти генераторы не делают), вы можете использовать их в emulate i.i.d. Результаты. Если вы хотите, чтобы диапазон данных сохранялся в пределах 6..7 бит (было неясно, вы имели в виду 6 или 7 бит, потому что гипотетически все между ними выполнимо), просто масштабируйте диапазон , Например ...

#include <iostream> 
#include <random> 

int main() { 
    unsigned long low = 1 << 6; // 2^6 == 64 
    unsigned long limit = 1 << 7; // 2^7 == 128 
    // Therefore, the range is 6-bits to 7-bits (or 64 + [128 - 64]) 
    unsigned long range = limit - low; 
    std::random_device rd; 
    std::mt19937 rng(rd()); //<< Doesn't actually meet criteria for i.d.d. 
    std::uniform_int_distribution<unsigned long> dist(low, limit - 1); //<< Given an engine that actually produces i.i.d. data, this would produce exactly what you're looking for 
    for (int i = 0; i != 10; ++i) { 
     unsigned long y = dist(rng); 
     //y is known to be in set {2^6..2^7-1} and assumed to be uniform (coin flip) over {low..low + (range-1)}. 
     std::cout << y << std::endl; 
    } 
    return 0; 
} 

Проблема с этим состоит в том, что, в то время как <random> классы распределения являются точными, то генераторы случайных чисел (предположительно в сторону от std::random_device, но это для системы) являются не предназначены, чтобы встать к статистическим испытаниям пригодности как iid генераторы.

Если вы хотите один, что делает, реализовать CSPRNG (мой идти к Боб Дженкинс ISAAC), который имеет интерфейс, отвечающий требованиям к <random> класса генераторов (вероятно, только покрывающие основной интерфейс std::random_device хорошо достаточно).

Для проверки статистически достоверных «нет» или «мы не можем сказать« нет »для того, соответствует ли набор определенной модели (и поэтому Pr(x) является точной, и поэтому энтропийная функция Шеннона является точным предсказанием), это целая вещь иначе полностью. Как я уже сказал, никакой генератор в <random> не отвечает этим критериям (кроме возможноstd::random_device). Мой совет - заниматься исследованиями в таких вещах, как Central limit theorem, Goodness-of-fit, Birthday-spacing и т. Д.

Чтобы управлять моей точки немного больше, в условиях вашего вопроса ...

struct uniform_rng { 
    unsigned long x; 
    constexpr uniform_rng(unsigned long seed = 0) noexcept: 
     x{ seed } 
    { }; 

    unsigned long operator()() noexcept { 
     unsigned long y = this->x++; 
     return y; 
    } 
}; 

... будет абсолютно соответствовать вашим критериям будучи униформе (или, как вы говорите, «абсолютный хаос»). Pr(x) - это, безусловно, 1/N, а биты, необходимые для хранения любого номера набора, равны -log2 Pr(1/N), который имеет значение 2 для мощности битовой ширины unsigned long. Однако он не распространяется независимо. Поскольку мы знаем, что это свойства, вы можете «сохранить» всю свою последовательность, просто сохраняя seed. Сюрприз, все PRNG работают таким образом. Поэтому бит, необходимый для хранения всей последовательности PRNG, равен -log2(1/2^bitsForSeed). По мере роста вашего образца биты, необходимые для хранения по сравнению с битами, которые вы можете сгенерировать, этот образец (ака, коэффициент сжатия) приближается к пределу 0.

+0

Несмотря на то, что я понял, что моего фона в теории вероятности и информации недостаточно для такого рода исследований, вы дали хорошую отправную точку для двигаясь в этом направлении. +50 –

1

Я еще не могу прокомментировать, но я хотел бы начать обсуждение: Из теории коммуникации/информации, похоже, вам потребуются вероятностные методы формирования для достижения того, чего вы хотите. Вы должны иметь возможность подавать выходные данные любой функции распределения через формирующий кодер, который затем должен перераспределять входные данные для конкретной целевой энтропии шеннонов. Вероятностное созвездие формирования было успешно применяется в волоконно-оптической связи: Wikipedia with some other links

1

Вы не ясно, что вы хотите достичь, и есть несколько способов снижения энтропии Шеннона для последовательности:

  • корреляции между битами, например помещая random_sequence через простой фильтр .
  • Отдельные биты не являются полностью случайными.

В качестве примера ниже вы могли бы сделать байты менее случайным:

std::vector<uint8_t> generate_random_sequence(size_t sequence_size, 
    int unit8_t cutoff=10) 
{ 
    std::vector<uint8_t> random_sequence; 
    std::vector<uint8_t> other_sequence; 
    std::random_device rnd_device; 

    std::cout << "Random device entropy: " << rnd_device.entropy() << '\n'; 

    std::mt19937 mersenne_engine(rnd_device()); 
    std::uniform_int_distribution<unsigned> dist(0, 255); 

    auto gen = std::bind(dist, mersenne_engine); 
    random_sequence.resize(sequence_size); 
    std::generate(random_sequence.begin(), random_sequence.end(), gen); 
    other_sequence.resize(sequence_size); 
    std::generate(other_sequence.begin(), other_sequence.end(), gen); 
    for(size_t j=0;j<size;++j) { 
     if (other_sequence[j]<=cutoff) random_sequence[j]=0; // Or j or ... 
    } 
    return std::move(random_sequence); 
} 

Я не думаю, что это был ответ, который вы искали, - так вы, вероятно, необходимо прояснить вопрос больше.

+0

Я искал формулу или алгоритм, я рассмотрел множество способов снижения энтропии, но все они экспериментальные , Мне нужна энтропийная функция любого типа (тип распределения, среднее значение, отклонение и т. Д.), Но в любом случае спасибо –

Смежные вопросы