C ++随机生成器提供(至少估计)熵

时间:2017-02-04 08:33:27

标签: c++ random entropy

使用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;
}

首先,在MSVC 2015中似乎std::random_device::entropy()硬编码为return 32;(根据香农的定义,这可能是8.0)。正如你可以尝试的那样,离真相不远,这个例子总是接近7.9998 ......,即绝对的混乱。

工作示例在IDEONE上(顺便说一下,他们的编译器硬编码熵为0)

还有一个主要问题 - 是否可以创建这样一个生成器,使用定义的熵生成线性分布的序列,让我们说6.0到7.0?它是否可以实现,如果有,是否有一些实现?

3 个答案:

答案 0 :(得分:4)

首先,你完全错误地看待香农的理论。他的论点(当你正在使用它时)很简单,“鉴于可能xPr(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绝对是混乱的。“统一分布 不混乱 。事实上,它是......好吧,制服。它的属性众所周知,简单,易于预测。您正在寻找“{em>有限样本集S是否符合独立分布均匀分布的标准(通常称为”Independently and Identically Distributed Data“或”iid“ )Pr(x) = 1/256?“这与香农的理论无关,并且更接近于涉及翻转硬币的基本概率理论(在这种情况下binomial给出假定的均匀性)。

假设某个C ++ 11 <random>生成器符合“统计上与i.i.d无法区分”的标准。 (顺便说一句,那些生成器没有),你可以使用它们模拟 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(我的首选是Bob Jenkins'ISAAC),其接口满足<random>类生成器的要求(可能只是覆盖std::random_device的基本界面足够好了。

为了测试一个集合是否遵循特定模型的统计上“no”或“我们不能拒绝”(因此Pr(x)是准确的,因此Shannon的熵函数是准确的预测),那是完全是另一回事。就像我说的那样,<random>中的生成器都没有符合这些标准(除了可能 std::random_device)。我的建议是研究Central limit theoremGoodness-of-fitBirthday-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),这是unsigned long位宽的2倍。但是,它不是独立分布的。因为我们知道它的属性,所以只需存储seed就可以“存储”它的整个序列。令人惊讶的是,所有PRNG都以这种方式工作。因此,存储 PRNG的整个序列所需的位是-log2(1/2^bitsForSeed)。随着样本的增长,存储所需的位数与生成该样本的位数(也就是压缩比)接近0的限制。

答案 1 :(得分:1)

我还不能评论,但我想开始讨论: 从通信/信息理论来看,您似乎需要概率性的整形方法来实现您想要的。您应该能够通过整形编码器提供任何分布函数的输出,然后整形编码器应将输入重新分配到特定目标香农熵。 概率星座整形已成功应用于光纤通信:Wikipedia with some other links

答案 2 :(得分:1)

您不清楚自己想要达到的目标,并且有几种方法可以降低序列的香农熵:

  • 比特之间的相关性,例如把random_sequence放到一个 简单过滤。
  • 个别位不是完全随机的。

如下例所示,您可以使字节更少随机:

AddLotPage

我认为这不是你想要的答案 - 所以你可能需要更多地澄清这个问题。