如何计算文件的熵?

83
如何计算文件的熵?(或者说一堆字节) 我有一个想法,但不确定它在数学上是否正确。我的想法如下:
  • 创建一个256个整数的数组(全部为零)。
  • 遍历文件,并对每个字节递增数组中相应位置的值。
  • 最后:计算数组的“平均”值。
  • 用零初始化计数器,并对数组的每个条目执行以下操作:
    将条目的差异添加到“平均值”的计数器中。
现在我卡住了。 如何以这样的方式“投影”计数器结果,以使所有结果都在0.0和1.0之间? 但我确定,这个想法无论如何都是不一致的...
希望有人有更好和更简单的解决方案?
注意:我需要整个过程来对文件内容进行假设:(明文、标记、压缩或某些二进制文件,...)

2
请查阅 扫描熵异常数据 - jitter
1
你是指度量熵吗?熵除以消息长度。 - user2622016
哎呀,你添加的那个注释:注意:我需要整个文件来对其内容进行假设:(纯文本、标记、压缩或某些二进制...)... 你刚刚要求了神一般的魔力,祝你开发出可证明最优数据压缩的好运。 - MickLH
请问您能否发布一下您最终结果的伪代码? - Guy Kahlon
有哪些Linux软件可以用来探索文件的熵?binwalk和radare2。 - milahu
12个回答

52
  • 最后:计算数组的“平均”值。
  • 将计数器初始化为零,并对数组的每个条目执行以下操作:将条目的差异添加到“平均值”中以计数器。

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

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

(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

编辑: 正如Wesley所提到的那样,我们必须将熵除以8才能调整到0 . . 1范围内(或者我们可以使用对数基数256)。


2
一个修改:你需要跳过Counts[i] == 0的元素。 - Igor Krivokon
4
是的,这确实很奇怪。然而,由于您使用更常规的以2为底的对数,您得到的值在0和8之间。您可能需要提醒问者将结果除以8,以获得在0到1之间的值。(祝贺您的快速回答 - 我不得不在维基百科上查找资料才能记起来。:P) - Wesley
@Nick:这个能应用在这里吗:http://stackoverflow.com/questions/4358258/picture-entropy-calculation - Daniel Mošmondor
4
这个熵的估计假设字节是独立的,但一般情况下这是错误的。例如,取一张从白到黑渐变的均匀水平梯度的灰度图像。 - leonbloy
这只是一种非常粗略的估计,例如对于包含abcd...z重复100万次的文件将失败。使用gzip进行压缩会得到更好的结果。将压缩后的大小除以未压缩的大小,就可以得到归一化熵。 - user2622016
显示剩余6条评论

37

一种更简单的解决方案:对文件进行gzip压缩。 使用文件大小比例:(压缩后大小)/(原始大小)作为随机性(即熵)的度量。

这种方法不能给出熵的准确绝对值(因为gzip不是“理想”的压缩器),但如果你需要比较不同来源的熵,那么它已经足够好了。


1
我也有这个想法(作为最后的选择),但是我需要分析大量的文件,所以将它们全部压缩不是一种有效的选择。 - ivan_ivanovich_ivanoff
3
这取决于你的ALL有多大。我刚试了一下,对/usr/bin中的所有文件进行gzip压缩,大约1000个文件,200Mb大小。用了大约7秒钟。这是一个命令,可以用来获取大小:cat * | gzip --fast | wc -c。它比逐字节读取文件要慢,但差别不大。 - Igor Krivokon
gzip已经投入了许多人年的编程工作,进行了大量的优化。我们不妨好好利用它。 - Nosredna
3
如果文件很大,这实际上可以比已接受的答案更好地估计熵值。 - leonbloy
2
我同意这个估计比被接受的答案更好。事实上,有几篇学术论文使用了这种类型的近似方法。 - Hugo Sereno Ferreira
@HugoSFerreira,你能指出一篇使用这种方法的具体论文吗?我想引用它。 - wi1

33
要计算一组字节的信息熵,您需要执行类似于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` 表示可能的离散值个数。一个由八个位组成的字节将具有 256 种可能的值。 - 得到的值将在 0(文件中的每个字节都相同)和 1(字节均匀分布在每个可能的字节值中)之间。
关于使用对数 256 的解释:
通常使用对数 2 来应用此算法。这会给出以比特为单位的结果。在这种情况下,任何给定文件的熵最多有 8 个比特。您可以自行尝试:通过使 `byte_counts` 成为所有 `1`、`2` 或 `100` 的列表来最大化输入的熵。当文件的字节均匀分布时,您会发现熵为 8 个比特。
可以使用其他对数基数。使用 b=2 允许以比特为单位得出结果,因为每个比特可以有 2 个值。使用 b=10 将结果放在数字位(dit)中,因为每个数字位有 10 种可能的值。使用 b=256 将结果转换为字节,因为每个字节可以具有 256 个离散值之一。
有趣的是,使用对数恒等式,您可以计算如何在单位之间转换得到熵。通过将结果除以 8,可以将任何以比特为单位的结果转换为字节单位的结果。作为一个有趣的、有意的副作用,这将熵给出在 0 和 1 之间的值。
总之:
- 可以使用不同的单位来表示熵。 - 大多数人以比特(b=2)来表示熵
- 对于一组字节,这将得到最大熵为 8 比特。 - 由于提问者想要一个在 0 和 1 之间的结果,因此将此结果除以 8 得到有意义的值。
- 上面的算法计算字节单位下的熵(b=256)
- 这相当于 (比特单位下的熵) / 8 - 这已经给出了 0 到 1 之间的值。

这不是熵,它假设字节是独立的。请参阅我对尼克答案的评论。 - leonbloy

25

我回答晚了两年,所以请忽略只有少数点赞的情况。

简短回答:使用下面我的第1个和第3个粗体方程式来得到大多数人在谈到文件“熵”时所想到的比特数。如果您想要Shannon的H熵,它实际上是熵/符号,正如他在他的论文中强调了13次,但大多数人并不知道。一些在线熵计算器使用这一个,但Shannon的H是“特定熵”,而不是“总熵”,这造成了很多混淆。如果您需要0到1之间的答案,则使用第1个和第2个方程式,这是归一化的熵/符号(它不是比特/符号,而是让数据选择自己的对数基数而不是任意地分配2、e或10,来衡量数据的“熵性质”的真实统计度量)。

文件(数据)的S熵共有4种类型,长度为N个符号,有n种唯一符号类型。但需要记住,通过了解文件的内容,您就知道它所处的状态,因此S=0。要精确计算期望未来的熵/字符,如果您拥有一个可以生成大量数据的源,您可以访问该源。如果您对文件使用以下内容,则更准确地说,它是从该源估计其他文件的期望熵。

  • Shannon(特定)熵 H = -1*sum(count_i / N * log(count_i / N))
    其中count_i是N中符号i出现的次数。
    单位是比特/符号,如果对数是以2为底时;是nats/符号,如果是自然对数。
  • 归一化的特定熵:H /log(n)
    单位是熵/符号。范围从0到1。1表示每个符号出现的频率相同,接近0表示除1外的所有符号仅出现一次,而剩下一个非常长的文件是另一个符号。对数与H使用相同的基数。
  • 绝对熵 S = N * H
    如果以2为底数,单位就是比特;如果以自然对数e为底数,则单位为nats。
    标准化绝对熵S=N*H / log(n),单位是“熵”,取值从0到N。log的底数与H相同。
    虽然最后一个公式(Shannon熵H)才是真正的“熵”,但所有书籍都将其称为“熵”而没有必要进行限定,这点我认为是需要的。大部分书籍并未像Shannon那样澄清它是每个符号的比特数或每个符号的熵。将H称为“熵”言过其实了。
    对于等频率的符号:S = N * H = N。这适用于大多数比特文件。熵不会压缩数据,因此完全无法识别任何模式,所以000000111111和010111101000具有相同的H和S(两种情况下均有6个1和6个0)。
    正如其他人所说,使用标准压缩程序(如gzip),在压缩前后进行除法运算可以更好地衡量文件中预先存在的“有序性”,但这对于更适合压缩方案的数据存在偏差。我们不能使用通用目的完美优化的压缩器来定义绝对的“有序性”。
    另一个需要考虑的因素是:如果更改数据的表达方式,H会发生变化。如果选择不同的比特(bit)、四位数(nibbles)、字节(bytes)或十六进制,H将不同。因此,我们需要除以log(n),其中n是数据中唯一符号的数量(二进制为2,字节为256),H的取值范围为0到1(这是规范化的单位熵),但技术上,如果256种字节类型中只有100种出现,那么n=100而不是256。

    H是“强度”熵,即每个符号的熵,类比于物理学中的比熵,即每千克或每摩尔的熵。文件的常规“广延”熵类似于物理学中的S,其中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为“熵”(这与Shannon的文本所做的阐明相反)。


我想为你的回答点赞,但是首先有一些歧义需要澄清:在第2和第4个公式中,以及当你说“所以你要除以log(n),其中n是数据中唯一符号的数量”时,log(n)的底数是什么?是自然对数还是log2(n)?一般来说,在数学中,没有指定底数的情况下,log(n)表示的是log10(n)。请澄清一下。 - Adam White
我在方程1和3中提到了用户选择基数。对于方程2和4,它应该是相同的基数(即H所在的基数)。我会添加澄清说明。 - zawy

23

这是个计算熵值(bits of entropy)的传统方法,以下是对应的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;
}

这是一个很棒的答案。为了扩展原始问题,如果答案是相对而不是绝对的,你会如何计算它?例如,假设您正在寻找地理熵;一项广告活动在全国范围内运行,并捕获受访者的地理坐标。没有两个条目可能具有相同的坐标,但某些熵函数仍应能够告诉您可能存在一些局部热点,或者覆盖整个国家的分布将更有效。 - Paul Smith
1
map中难道不应该检查空值吗?否则,Math.Log(frequency)可能会返回-INF - executifs
1
(Math.Log(frequency) / Math.Log(2)) == Math.Log(frequency, 2) - citykid
对于一个字节数组:{ Dictionary map = new Dictionary(); 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 item in map) { double freq = (double)item.Value / len; res -= freq * (Math.Log(freq)/Math.Log(2)); } return res; }``` 熵-香农熵的计算方法: - evandrix
我理解的方式是它计算每字节的信息密度,可以最多为8 = -log_2(1/2^8),最少为0 = -log_2(1/1)。也许这一点需要澄清。 - undefined

19

这个东西可以用 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
谢谢!很高兴知道这个工具。但我需要以编程方式和独立于平台的方式解决这个问题,因此提出了我的问题。 - ivan_ivanovich_ivanoff
1
+1 感谢指针。这至少存在于Debian中:http://packages.debian.org/wheezy/ent - tripleee

10

文件的熵并不存在。在信息论中,熵是一个随机变量的函数,而不是固定数据集的函数(实际上,固定的数据集也具有熵,但其熵值为0——我们可以将数据视为只有一种可能的随机分布,其概率为1)。

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


4
我不了解熵的理论定义。但是,每个术语都有两种含义:理论含义和通俗含义。嗯,似乎大家都理解了熵这个通俗的含义 ;) - ivan_ivanovich_ivanoff
1
在如何将“文件的熵”转化为严格的数学定义方面,至少有两种明显的解释。如果你真的想理解自己在做什么,你应该了解这些答案中熵是如何以统计方式建模的。 - James Thompson
1
或者你可以涉足科尔莫戈洛夫复杂度,这是一个更好的数学定义,但是它是不可计算的。 - Jeffrey Hantin
4
我相信在这个问题中,随机变量是通过读取文件而发现的字节。因此,它将是一个离散的随机变量,有256个可能的值,并且具有自己的分布,该分布取决于文件本身。(我知道这篇文章很旧了,但这可能会为任何看到这里的人提供更清晰的解释) - Anoyz
嗯,看起来大家都理解了流行部分;) 错误 实际上,这里发生的事情是大多数人要么无知,要么信息不足,所以他们猜测和/或假设事情。这才是真正的答案。如果你已经很好地知道自己在做什么,其他答案就有用了,那么你已经知道了其他答案。它们是回答不同的问题 - MickLH
显示剩余2条评论

5

如果您使用信息论熵,要注意不要将其用于字节。比如,如果您的数据由浮点数组成,应该拟合一个概率分布并计算该分布的熵。

或者,如果文件内容是Unicode字符,则应使用它们等。


1
当我想要对任何类型的文件进行数据分析时,字节会是我最好的选择(作为一种妥协),我认为。 - ivan_ivanovich_ivanoff
1
当然可以这样做。然而,你应该利用任何额外的信息。否则,你的结果可能会非常糟糕。 - bayer
通常而言,usuallyuseless 是正确的。香农熵并不能为您提供有关文件内容的足够信息。每个压缩器都有两个阶段:建模和熵编码。熵编码是必要的,但大多数冗余在建模阶段被检测出来(除非您正在处理准随机数据)。 - Igor Krivokon
通常情况下,这里是无用的。找出答案的一种方法是用语言说明您正在计算的完整内容:“我用来表示浮点数的 ASCII 符号的熵是多少”,这是可以计算的,但可能不是您的目标。 - tom10
Java的MIME测试能力有限。它信任文件扩展名,只有当扩展名未知或不存在时,才通过查看文件内部来猜测MIME类型。我指的是URLConnection:getContentType()。 - ivan_ivanovich_ivanoff
1
这是注释而不是答案。 - JasonMArcher

2

回复:我需要整个文件来推断其内容:(明文、标记、压缩或二进制等)

正如其他人所指出的(或者被困惑/分散注意力),我认为你实际上在谈论的是度量熵(熵除以消息长度)。请参见熵(信息理论)- 维基百科了解更多信息。

jitter的评论链接到扫描熵异常数据与您的根本目标非常相关。该链接最终将链接到libdisorder(用于测量字节熵的C库)。这种方法似乎会给您提供更多的信息,因为它显示了不同部分的度量熵如何变化。例如,查看此图表,其中显示了来自4 MB jpg图像的256个连续字节块的熵(y轴)如何随偏移量(x轴)而变化。在开头和结尾,熵较低,但在大部分文件中每个字节的熵约为7位。

enter image description here 来源:https://github.com/cyphunk/entropy_examples。[请注意,此图表和其他图表可通过新颖的http://nonwhiteheterosexualmalelicense.org许可证获得。]

更有趣的是在分析FAT格式磁盘字节熵 | GL.IB.LY中类似的分析和图表。

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

这本书也似乎很相关:检测和识别用于电子邮件和数据安全的文件伪装 - Springer


2

计算给定长度的无符号字符串的熵。该代码基本上是对 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;
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接