【问题标题】:How to calculate the entropy of a file?如何计算文件的熵?
【发布时间】:2010-11-02 16:37:21
【问题描述】:

如何计算文件的熵? (或者我们只是说一堆字节)
我有一个想法,但我不确定它在数学上是否正确。

我的想法如下:

  • 创建一个包含 256 个整数(全为零)的数组。
  • 遍历文件及其每个字节,
    增加数组中的相应位置。
  • 最后:计算数组的“平均值”。
  • 用零初始化计数器,
    并且对于数组的每个条目:
    添加条目的差异 到柜台“平均”。

好吧,现在我被困住了。如何以这种方式“投影”计数器结果 所有结果都在 0.0 和 1.0 之间?但我敢肯定, 这个想法无论如何都是不一致的......

我希望有人有更好更简单的解决方案?

注意:我需要整个事情来对文件的内容做出假设:
(明文、标记、压缩或一些二进制文件,...)

【问题讨论】:

  • 你的意思是度量熵?熵除以消息长度
  • 哎呀,你添加的那个注释:Note: I need the whole thing to make assumptions on the file's contents: (plaintext, markup, compressed or some binary, ...) ...你只是要求上帝般的魔法,祝你好运,开发可证明的最佳数据压缩。
  • 你能发布一下你最终结果的伪代码吗?

标签: algorithm file-io entropy


【解决方案1】:

这是一个基于 snippet 和无限战争期间发生的入侵的 Java 算法

 public static double shannon_entropy(File file) throws IOException {
        byte[] bytes= Files.readAllBytes(file.toPath());//byte sequence
        int max_byte = 255;//max byte value
        int no_bytes = bytes.length;//file length
        int[] freq = new int[256];//byte frequencies
        for (int j = 0; j < no_bytes; j++) {
            int value = bytes[j] & 0xFF;//integer value of byte
            freq[value]++;
        }
        double entropy = 0.0;
        for (int i = 0; i <= max_byte; i++) {
            double p = 1.0 * freq[i] / no_bytes;
            if (freq[i] > 0)
                entropy -= p * Math.log(p) / Math.log(2);
        }
       return entropy;
    }

usage-example:

 File file=new File("C:\\Users\\Somewhere\\In\\The\\Omniverse\\Thanos Invasion.Log");
 int file_length=(int)file.length();
 double shannon_entropy=shannon_entropy(file);
 System.out.println("file length: "+file_length+" bytes");
 System.out.println("shannon entropy: "+shannon_entropy+" nats i.e. a minimum of "+shannon_entropy+" bits can be used to encode each byte transfer" +
                    "\nfrom the file so that in total we transfer atleast "+(file_length*shannon_entropy)+" bits ("+((file_length*shannon_entropy)/8D)+
                    " bytes instead of "+file_length+" bytes).");

output-example:

文件长度:5412 字节

香农熵:4.537883805240875 nats,即最少 4.537883805240875 位可用于对每个字节传输进行编码 从文件中,我们总共传输至少 24559.027153963616 位(3069.878394245452 字节而不是 5412 字节)。

【讨论】:

    【解决方案2】:

    对于它的价值,这是用 C# 表示的传统(熵位)计算:

    /// <summary>
    /// returns bits of entropy represented in a given string, per 
    /// http://en.wikipedia.org/wiki/Entropy_(information_theory) 
    /// </summary>
    public static double ShannonEntropy(string s)
    {
        var map = new Dictionary<char, int>();
        foreach (char c in s)
        {
            if (!map.ContainsKey(c))
                map.Add(c, 1);
            else
                map[c] += 1;
        }
    
        double result = 0.0;
        int len = s.Length;
        foreach (var item in map)
        {
            var frequency = (double)item.Value / len;
            result -= frequency * (Math.Log(frequency) / Math.Log(2));
        }
    
        return result;
    }
    

    【讨论】:

    • 这是一个绝妙的答案。为了扩展原始问题,如果答案是相对的而不是绝对的,您将如何计算它?例如,假设您正在寻找地理熵;一个广告活动在全国范围内运行,您可以获取受访者的地理坐标。没有两个条目可能具有相同的坐标,但一些熵函数应该仍然能够告诉您可能存在一些局部热点,或者覆盖全国的分布会更有效。
    • 不应该检查map 中的空值吗?否则,Math.Log(frequency) 可能会返回 -INF
    • (Math.Log(frequency) / Math.Log(2)) == Math.Log(frequency, 2)
    • 对于字节数组:public static double EntropyShannon(byte[] xs) { Dictionary&lt;byte,int&gt; map = new Dictionary&lt;byte,int&gt;(); foreach (byte x in xs) { if (!map.ContainsKey(x)) { map.Add(x,1); } else { map[x]++; } } double res = 0.0f; int len = xs.Length; foreach (KeyValuePair&lt;byte,int&gt; item in map) { double freq = (double)item.Value / len; res -= freq * (Math.Log(freq)/Math.Log(2)); } return res; }
    【解决方案3】:

    我迟到了两年才回答,所以尽管只有少数赞成票,但请考虑一下。

    简短回答:使用下面我的第 1 和第 3 个粗体等式来了解大多数人在说文件的“熵”时的想法。如果你想要香农的 H 熵,它实际上是熵/符号,只使用第一个方程,正如他在他的论文中所说的 13 次大多数人都不知道的那样。一些在线熵计算器使用这个,但香农的 H 是“特定熵”,而不是“总熵”,这引起了很多混乱。如果您想要 0 和 1 之间的归一化熵/符号的答案,请使用第 1 和第 2 等式(它不是位/符号,而是通过让数据选择自己的对数基数来对数据的“熵性质”进行真正的统计测量)而不是任意分配 2、e 或 10)。

    4 种熵 文件(数据),长度为 N 个符号,具有 n 个唯一类型的符号。但是请记住,通过了解文件的内容,您就知道它所处的状态,因此 S=0。准确地说,如果您有一个可以生成大量您可以访问的数据的源,那么您可以计算该源的预期未来熵/特征。如果您在文件上使用以下内容,更准确的说法是它正在估计来自该来源的其他文件的预期熵。

    • 香农(特定)熵H = -1*sum(count_i / N * log(count_i / N))
      其中 count_i 是符号 i 在 N 中出现的次数。
      如果 log 以 2 为底,则单位是位/符号;如果是自然对数,则单位是 nats/symbol。
    • 归一化比熵:H / log(n)
      单位是熵/符号。范围从 0 到 1。1 表示每个符号出现的频率相同,接近 0 表示除 1 之外的所有符号仅出现一次,而一个非常长的文件的其余部分是另一个符号。日志与 H 的基数相同。
    • 绝对熵 S = N * H
      如果 log 以 2 为底,则单位是位,如果 ln()),则为 nats。
    • 归一化绝对熵 S = N * H / log(n)
      单位是“熵”,从 0 到 N 不等。对数与 H 的基数相同。

    虽然最后一个是最真实的“熵”,但第一个(香农熵 H)是所有书籍所称的没有(恕我直言)限定的“熵”。大多数人没有澄清(像香农所做的那样)它是位/符号或每个符号的熵。将 H 称为“熵”太松散了。

    对于每个符号的频率相等的文件:S = N * H = N。这是大多数大文件的情况。熵不对数据进行任何压缩,因此完全不知道任何模式,因此 000000111111 与 010111101000 具有相同的 H 和 S(两种情况下都有 6 个 1 和 6 个 0)。

    就像其他人所说的那样,使用像 gzip 这样的标准压缩例程并在前后进行划分可以更好地衡量文件中预先存在的“顺序”的数量,但这会偏向于适合压缩方案的数据更好的。没有可用于定义绝对“顺序”的通用完美优化压缩器。

    另一件需要考虑的事情:如果你改变你表达数据的方式,H 就会改变。如果您选择不同的位分组(位、半字节、字节或十六进制),H 将有所不同。所以你除以 log(n) 其中 n 是数据中唯一符号的数量(二进制为 2,字节为 256),H 的范围为 0 到 1(这是归一化的密集香农熵以每个符号的熵为单位)。但从技术上讲,如果 256 种字节中只有 100 种出现,那么 n=100,而不是 256。

    H 是一种“密集”熵,即它是每个符号,类似于物理学中的 特定熵,即每千克或每摩尔的熵。类似于物理 S 的文件的常规“广泛”熵是 S=N*H 其中 N 是文件中符号的数量。 H 将完全类似于理想气体体积的一部分。信息熵不能简单地与更深层次的物理熵完全相等,因为物理熵允许“有序”以及无序排列:物理熵不仅仅是完全随机的熵(例如压缩文件)。不同的一方面对于理想气体,有一个额外的 5/2 因子来解释这一点: S = k * N * (H+5/2) 其中 H = 每个分子可能的量子态 = (xp)^3/ hbar * 2 * sigma^2 其中 x = 框的宽度,p = 系统中的总非定向动量(根据每个分子的动能和质量计算),并且 sigma = 0.341,符合不确定性原理,仅给出数量1 个标准差内的可能状态。

    一个小数学给出了文件的归一化扩展熵的更短形式:

    S=N * H / log(n) = sum(count_i*log(N/count_i))/log(n)

    它的单位是“熵”(实际上并不是一个单位)。它被归一化为比 N * H 的“熵”单位更好的通用度量。但在没有澄清的情况下也不应该将其称为“熵”,因为正常的历史惯例是错误地将 H 称为“熵”(这与香农文本中的澄清)。

    【讨论】:

    • 我想赞成你的答案,但你应该首先澄清一些歧义:在等式 2 和 4 中,你说“所以你除以 log(n),其中 n 是数字数据中唯一符号的数量”,记录 n 的多少?自然对数,log2(n)?通常在数学中,没有指定基数,log(n) 表示 log10(n)。请澄清。
    • 我在等式 1 和 3 中提到用户选择基数。对于等式 2 和 4,它应该是同一个基数(H 所在的基数)。我将添加说明。
    【解决方案4】:

    回复:我需要对文件的内容做出假设: (明文、标记、压缩或一些二进制文件,...)

    正如其他人所指出的(或被混淆/分心),我认为您实际上是在谈论 度量熵(熵除以消息长度)。在Entropy (information theory) - Wikipedia 上查看更多信息。

    jitter 链接到Scanning data for entropy anomalies 的评论与您的​​基本目标非常相关。最终链接到libdisorder (C library for measuring byte entropy)。这种方法似乎可以为您提供更多信息,因为它显示了度量熵在文件的不同部分是如何变化的。参见例如这张图表显示了来自 4 MB jpg 图像(y 轴)的 256 个连续字节块的熵如何随不同偏移量(x 轴)变化。在开始和结束时,熵较低,因为它是一部分,但对于大多数文件来说,它约为每字节 7 位。

    来源:https://github.com/cyphunk/entropy_examples。 [请注意,此图表和其他图表可通过小说 http://nonwhiteheterosexualmalelicense.org 许可证获得....]

    更有趣的是Analysing the byte entropy of a FAT formatted disk | GL.IB.LY的分析和类似图表

    整个文件和/或它的第一个和最后一个块的度量熵的最大值、最小值、模式和标准差等统计数据作为签名可能非常有用。

    这本书似乎也很相关:Detection and Recognition of File Masquerading for E-mail and Data Security - Springer

    【讨论】:

      【解决方案5】:

      计算任何大小为“length”的无符号字符字符串的熵。这基本上是对http://rosettacode.org/wiki/Entropy 代码的重构。我将它用于 64 位 IV 生成器,该生成器创建了一个包含 100000000 个 IV 的容器,没有重复,平均熵为 3.9。 http://www.quantifiedtechnologies.com/Programming.html

      #include <string>
      #include <map>
      #include <algorithm>
      #include <cmath>
      typedef unsigned char uint8;
      
      double Calculate(uint8 * input, int  length)
        {
        std::map<char, int> frequencies;
        for (int i = 0; i < length; ++i)
          frequencies[input[i]] ++;
      
        double infocontent = 0;
        for (std::pair<char, int> p : frequencies)
        {
          double freq = static_cast<double>(p.second) / length;
          infocontent += freq * log2(freq);
        }
        infocontent *= -1;
        return infocontent;
       }
      

      【讨论】:

        【解决方案6】:

        在没有任何附加信息的情况下,文件的熵(根据定义)等于其大小*8 位。文本文件的熵大约是大小*6.6 位,假设:

        • 每个字符的概率相同
        • 字节中有 95 个可打印字符
        • log(95)/log(2) = 6.6

        英文文本文件的熵估计约为每个字符 0.6 到 1.3 位(如 here 所述)。

        一般来说,您不能谈论给定文件的熵。熵是文件集的一个属性。

        如果您需要熵(确切地说是每字节的熵),最好的方法是使用 gzip、bz2、rar 或任何其他强压缩方式对其进行压缩,然后将压缩后的大小除以未压缩的大小。这将是对熵的一个很好的估计。

        按照 Nick Dandoulakis 的建议逐字节计算熵给出了一个非常糟糕的估计,因为它假设每个字节都是独立的。例如,在文本文件中,字母后有一个小写字母的可能性要比字母后有空格或标点符号的可能性大得多,因为单词通常长于 2 个字符。因此,下一个字符在 a-z 范围内的概率与前一个字符的值相关。不要对任何真实数据使用 Nick 的粗略估计,而是使用 gzip 压缩比。

        【讨论】:

          【解决方案7】:
          • 最后:计算数组的“平均值”。
          • 用零初始化一个计数器, 并且对于数组的每个条目: 将条目与“平均值”的差值添加到计数器。

          通过一些修改,您可以获得香农的熵:

          将“平均”重命名为“熵”

          (float) entropy = 0
          for i in the array[256]:Counts do 
            (float)p = Counts[i] / filesize
            if (p > 0) entropy = entropy - p*lg(p) // lgN is the logarithm with base 2
          

          编辑: 正如韦斯利所说,我们必须将熵除以 8 才能在 0 范围内调整它。 . 1(或者,我们可以使用以 256 为底的对数)。

          【讨论】:

          • 一个更正:您需要跳过 Counts[i] == 0 的元素。
          • 你说得对,Krivokon,谢谢!我看到韦斯利做得对,只是他选择了一个“奇怪”的对数底。
          • 是的,这绝对很奇怪。但是,由于您使用的是更传统的以 2 为底的对数,因此您会得到一个介于 0 和 8 之间的值。您可能需要提及这一点,以便提问者可以记住将结果除以 8 以获得介于 0 和 1 之间的值。 (恭喜你得到了快速的回答——我必须在维基百科上查找这些东西才能记住它。:P)
          • 这个方法很好,我用它通过比较像素数据来分析图像的“熵”,结果很好。
          • 这种熵估计假设字节是独立的,这通常是错误的。例如,拍摄具有从白到黑的均匀水平渐变的灰度图像。
          【解决方案8】:

          要计算字节集合的信息熵,您需要执行类似于 tydok 的答案的操作。 (tydok 的答案适用于一组位。)

          假设以下变量已经存在:

          • byte_counts 是包含文件中每个值的字节数的 256 个元素列表。例如,byte_counts[2] 是具有值 2 的字节数。

          • total 是文件中的总字节数。

          我将用 Python 编写以下代码,但应该很清楚发生了什么。

          import math
          
          entropy = 0
          
          for count in byte_counts:
              # If no bytes of this value were seen in the value, it doesn't affect
              # the entropy of the file.
              if count == 0:
                  continue
              # p is the probability of seeing this byte in the file, as a floating-
              # point number
              p = 1.0 * count / total
              entropy -= p * math.log(p, 256)
          

          有几件事需要注意。

          • 检查count == 0 不仅仅是一种优化。如果count == 0,则p == 0,并且log(p) 将是未定义的(“负无穷大”),从而导致错误。

          • math.log 的调用中的256 表示可能的离散值的数量。一个由 8 位组成的字节将有 256 个可能的值。

          结果值将介于 0(文件中的每个字节都相同)到 1(字节在每个可能的字节值之间平均分配)之间。


          对数基数256的使用说明

          确实,该算法通常使用以 2 为底的对数来应用。这会以位为单位给出结果答案。在这种情况下,任何给定文件的熵最多为 8 位。自己尝试一下:通过使byte_counts 成为所有12100 的列表来最大化输入的熵。当一个文件的字节均匀分布时,你会发现有一个8位的熵。

          可以使用其他对数底。使用 b=2 允许以位为单位的结果,因为每个位可以有 2 个值。使用 b=10 会将结果放在 dits 或十进制位中,因为每个 dit 有 10 个可能的值。使用 b=256 将以字节为单位给出结果,因为每个字节可以有 256 个离散值之一。

          有趣的是,使用对数标识,您可以计算出如何在单位之间转换生成的熵。任何以比特为单位获得的结果都可以通过除以 8 转换为字节单位。作为一个有趣的、有意的副作用,这将熵作为一个介于 0 和 1 之间的值。

          总结:

          • 您可以使用各种单位来表示熵
          • 大多数人用比特表示熵 (b=2)
            • 对于字节集合,最大熵为 8 位
            • 由于提问者想要一个介于 0 和 1 之间的结果,因此将此结果除以 8 以获得有意义的值
          • 上述算法以字节为单位计算熵 (b=256)
            • 这相当于(位熵)/8
            • 这已经给出了一个介于 0 和 1 之间的值

          【讨论】:

          • 这不是熵,它假设字节是独立的。查看我对尼克回答的评论
          【解决方案9】:

          这是ent 可以处理的事情吗? (或者它可能在您的平台上不可用。)

          $ dd if=/dev/urandom of=file bs=1024 count=10
          $ ent file
          Entropy = 7.983185 bits per byte.
          ...
          

          作为一个反例,这里是一个没有熵的文件。

          $ dd if=/dev/zero of=file bs=1024 count=10
          $ ent file
          Entropy = 0.000000 bits per byte.
          ...
          

          【讨论】:

          • 谢谢!很高兴知道这个工具。但我需要以编程方式并以独立于平台的方式解决这个问题,因此我提出了问题。
          • +1 感谢您的指点。这至少存在于 Debian 中:packages.debian.org/wheezy/ent
          【解决方案10】:

          没有文件的熵这样的东西。在信息论中,熵是随机变量的函数,而不是固定数据集的函数(好吧,从技术上讲,固定数据集确实有熵,但熵为 0——我们可以认为数据作为随机分布,只有一种可能的结果,概率为 1)。

          为了计算熵,您需要一个随机变量来为您的文件建模。然后熵将是该随机变量分布的熵。该熵将等于该随机变量中包含的信息位数。

          【讨论】:

          • 我不知道熵的理论定义。但是,每个术语总是有两种语义:理论语义和流行语义。好吧,似乎这里的每个人都理解了流行的部分;)
          • 对于某人如何将“文件的熵”转换为严格的数学定义的答案,至少有两种明显的解释。如果您真的想了解自己在做什么,则应该了解这些答案中熵建模的统计方式。
          • 或者您可以进入 Kolmogorov 复杂度,这是一个更好的数学定义,但无法计算。
          • @JamesThompson 很有趣,有任何关于如何从要测量熵的一堆文件中推断出这个随机变量的指针吗?
          • 我相信在这个问题中,随机变量是通过运行在文件中找到的字节。所以它将是一个具有 256 个可能值的离散随机变量,并且它自己的分布取决于文件。 (我知道这篇文章很旧,但这可能会澄清任何到达这里的人)
          【解决方案11】:

          如果您使用信息论熵,请注意不要在字节上使用它可能是有意义的。比如说,如果您的数据由浮点数组成,您应该改为将概率分布拟合到这些浮点数并计算该分布的熵。

          或者,如果文件的内容是 unicode 字符,你应该使用那些,等等。

          【讨论】:

          • 当我想对任何类型的文件进行数据分析时,我认为 byte 是我最好的选择(作为一种折衷方案)。
          • 当然可以。但是,您应该使用可以获得的任何其他信息。否则你的结果可能会非常糟糕。
          • 通常没用是绝对正确的。香农熵不会为您提供有关文件内容的足够信息。每个压缩器都有 2 个阶段:建模和熵编码。熵编码是必要的,但大部分冗余都是在建模阶段检测到的(除非您使用的是准随机数据)。
          • 通常没用的就在这里。解决这个问题的一种方法是用文字说出你正在计算的全部内容:“我用来表示我的浮点数的 ascii 符号的熵是多少”,你可以计算出来,但是可能不是您的目标。
          • 这是评论而非答案。
          【解决方案12】:

          更简单的解决方案:gzip 文件。使用文件大小的比率:(size-of-gzipped)/(size-of-original) 作为随机性的度量(即熵)。

          此方法无法为您提供熵的确切绝对值(因为 gzip 不是“理想”压缩器),但如果您需要比较不同来源的熵,它就足够了。

          【讨论】:

          • 我也有这个想法(作为最后一个选项),但我需要分析很多文件,因此对所有文件进行 gzip 压缩不是一个有效的选择。
          • 这取决于你的 ALL 有多大。我只是尝试对 /usr/bin 中的所有文件进行 gzip,大约有 1000 个文件,200Mb。大约花了7秒。这是您曾经可以用来获取大小的命令: cat * | gzip --fast |厕所-c。它比逐字节读取文件要慢,但不会慢很多。
          • gzip 已经进行了很多年的编程工作,进行了很多优化。不妨好好利用一下。
          • 这实际上可以比接受的答案更好地估计熵 - 特别是如果文件很大。
          • 我同意这是一个比接受的答案更好的估计。事实上,有几篇学术论文使用了这种近似。
          猜你喜欢
          • 1970-01-01
          • 2020-04-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-01-31
          • 2011-02-28
          • 1970-01-01
          相关资源
          最近更新 更多