【Seed-Labs 2.0】MD5 Collision Attack Lab

说在前面

这个lab的题目在某些情况下可能被改版成了这样:
①. 使用md5collgen生成两个MD5值相同的文件,并利用bless十六进制编辑器查看输出的两个文件,描述你观察到的情况;
②. 参考Lab3_task2.c的代码,生成两个MD5值相同但输出不同的两个可执行文件。
③. 参考Lab3_task3.c的代码,生成两个MD5值相同但代码行为不相同的可执行文件。
④. 回答问题:通过上面的实验,请解释为什么可以做到不同行为的两个可执行文件具有相同的MD5值?
但并不影响,内容相似,仍可参考本博客进行学习。

实验环境

虚拟机环境:SEED-labs-ubuntu 20.04
使用软件:Oracle VM VirtualBox7.1.0
实验用到的工具:md5collgen,Bless Hex editor
虚拟机安装配置可参考该博客: SEED-labs-ubuntu 20.04 虚拟机环境搭建

实验环境初始化

在项目网站上根据自己的设备类型下载对应的 Labsetup文件,解压后移动到虚拟机中
项目网站 SEED Labs 2.0 MD5 Collision Attack Lab
注:该文件夹中只有一个 md5collgen
在这里插入图片描述

实验详情

TASK1

任务描述

在这项任务中,我们将生成两个具有相同 MD5 哈希值的不同文件。这两个文件的开头部分必须相同,即共享相同的前缀。我们可以使用 md5collgen 程序来实现这一目标,该程序允许我们提供一个包含任意内容的前缀文件。该程序的工作原理如图 1 所示。下面的命令为给定的前缀文件 prefix.txt 生成两个输出文件 out1.bin 和 out2.bin:

$ md5collgen -p prefix.txt -o out1.bin out2.bin

图1:从前缀生成MD5碰撞

我们可以使用 diff 命令检查输出文件是否不同。我们还可以使用 md5sum 命令检查每个输出文件的 MD5 哈希值。请参阅以下命令。

$ diff out1.bin out2.bin
$ md5sum out1.bin
$ md5sum out2.bin

由于 out1.binout2.bin 是二进制文件,我们无法使用文本查看器程序(如 cat 或更多)来查看它们;我们需要使用二进制编辑器来查看(和编辑)它们。我们已经在虚拟机中安装了一个名为 bless 的十六进制编辑器软件。请使用这种编辑器查看这两个输出文件,并描述你的观察结果。此外,您还应回答以下问题:

  • 问题 1:如果前缀文件的长度不是 64 的倍数,会发生什么情况?
  • 问题 2:创建一个长度正好为 64 字节的前缀文件,然后再次运行碰撞工具,看看会发生什么。
  • 问题 3:md5collgen 生成的数据(128 字节)在两个输出文件中是否完全不同?请找出所有不同的字节。

任务分析
从本质上来说,MD5值是有限的(2的128次方个),然而程序是无穷的,因此两个不同行为程序完全可能有相同的md5值,碰撞不可避免。本题便是利用 MD5碰撞生成器 md5collgen 来制造MD5碰撞来生成两个MD5值相同但内容不同的文件。

MD5 碰撞生成器:简单来说,用户选择一个前缀文件(64位的倍数长度),碰撞生成器根据选定的前缀生成两个不同的文件,这两个文件在前缀之后的内容不同,但整个文件的MD5哈希值相同。

根据MD5碰撞生成器的性质,我们只需撰写一个前缀文件,然后用其生成两个文件即可完成任务。

完成步骤

◇ 使用md5collgen生成两个MD5值相同的文件

1.编写前缀文件 prefix.txt,为后面的实验做准备。
在这里插入图片描述
2.输入下面的命令使用 md5collgen 将前缀文件 prefix.txt 作为前缀生成两个文件 out1.bin out2.bin。

在这里插入图片描述
3.通过下面三个命令对out1.bin 和 out2.bin 两个文件进行对比,来检查利用md5collgen 和 前缀文件 prefix.txt 生成的这两个文件 是不一样的文件且MD5值相同。通过输出结果我们可以看出 out1.bin 和 out2.bin 两个文件是不一样的,但是其MD5编码是一样的。证明我们成功使用 md5collgen 生成两个 MD5值相同的文件。

在这里插入图片描述
◇观察输出的两个MD5值相同的文件
注:题目建议使用虚拟机中的 Bless Hex editor 来以十六进制的形式查看两个输出的文件。但是作为一个程序员更希望能够在终端中解决一切,所以我在这里补充一个方法,使用 hexdump 命令也可以以十六进制和ASCII码形式显示二进制文件的内容。

hexdump  -C  out1.bin

在这里插入图片描述
在后面的实验中经常会对文件的内容进行比较,但是肉眼比较看起来会很麻烦,这里补充一个在线的文本比较工具,可以帮助我们快速进行文本比较。
在线文本比较工具

1.首先观察两个二进制文件,观察后发现两个文件内容确实不相同,再次证明两个文件确实是两个内容不相同,MD5值相同的文件。
在这里插入图片描述
Q1:如果您的前缀文件的长度不是 64 的倍数,会发生什么情况?
我在本任务中撰写的 prefix.txt 的内容长度为6 不为 64的倍数。用 Bless Hex editor 打开两个输出文件 out1.bin 和 out2.bin。观察后我们会发现,输出文件会自动填充‘0’,将前缀部分填充至长度为64的倍数长度。
在这里插入图片描述
Q2:创建一个正好有 64 字节的前缀文件,然后再次运行碰撞工具,看看会发生什么。

1.按照题目要求重新撰写一个精确的64位前缀文件 prefix.txt,如下图所示。
在这里插入图片描述
注: 仔细看的话,可以发现我的前缀文件里面只输入了63个字符,并不是题目要求的64个。其实我一开始是在前缀文件中输入了64个字符的,但是用Bless Hex editor打开前缀文件输出的两个文件后,可以看到在前缀部分中我输入的最后一个字符后多了一个 0X0A 相当于在前缀文件中输入了65个字符,并且在该字符后一直补零至前缀部分的长度变为128位(64位的2倍)。

在这里插入图片描述
查阅资料后得知,Linux 使用换行符 0x0A(Line Feed, LF)表示行的结束,在文本文件中,每一行通常会以一个换行符结束,这是一种约定,旨在确保文件的标准化和易于处理。所以在文本文件中我们只需要输入63个字符,再加上一个默认的换行符就可以得到一个精确的64位的前缀文件。

2.使用命令使用 md5collgen 将64位的前缀文件 prefix.txt 作为前缀生成两个文件 out1.bin out2.bin。可以观察到现在前缀部分不会补零了。
在这里插入图片描述
Q3:由 md5collgen 生成的两个输出文件的数据(128 字节)完全不同吗?请确定所有不同的字节。

仔细观察后可知,两个输出文件并不是所有的数据(128字节)都不同。
在这里插入图片描述
小结: 总结一下观察的结果,我们可以发现两文件的前缀相同,文件内容有少许不同。我们可以通过 md5collgen的原理来理解这一现象,当输入的文件长度不是64字节的倍数时先补0 使长度为64字节的倍数,再添加128字节的数据分别生成两个MD5值相同的输出文件,这两个输出文件就是在这128字节中存在部分差异。

TASK2

任务描述

在本任务中,我们将尝试了解 MD5 算法的一些特性。这些特性对于我们在本实验室中开展进一步的任务非常重要。MD5 是一种相当复杂的算法,但从高层来看,它并不复杂。如图 2 所示,MD5 将输入数据分成 64 字节的数据块,然后在这些数据块上迭代计算哈希值。MD5 算法的核心是一个压缩函数,它有两个输入,一个是 64 字节的数据块,另一个是上一次迭代的结果。压缩函数会产生一个 128 位的 IHV,即 “中间哈希值”;然后将此输出输入下一次迭代。如果当前迭代是最后一次迭代,IHV 将是最终哈希值。第一次迭代的 IHV 输入(IHV0)是一个固定值。
图 2:MD5 算法的工作原理
根据 MD5 的工作原理,我们可以推导出 MD5 算法的以下属性:给定两个输入 M 和 N,如果 MD5(M) = MD5(N),即 M 和 N 的 MD5 哈希值相同,那么对于任意输入 T,MD5(M || T) = MD5(N ||T),其中 || 表示连接。

也就是说,如果输入 M 和 N 具有相同的哈希值,那么给它们添加相同的后缀 T 就会得到两个具有相同哈希值的输出。这一特性不仅适用于 MD5 哈希算法,也适用于许多其他哈希算法。在本任务中,你的任务是设计一个实验来证明 MD5 的这一特性。

您可以使用 cat 命令将两个文件(二进制文件或文本文件)连接成一个文件。下面的命令将文件 2 的内容与文件 1 的内容连接起来,并将结果放到文件 3 中。

$ cat file1 file2 > file3

任务分析

任务内容简单来说,可以分为三点:

  1. 找到两个内容不同但是 MD5 值相同的文件。
  2. 给找到的两个文件拼接相同的后缀。
  3. 验证拼接后的两个文件 MD5 值相同。

第一点很好解决,因为我们在完成第一问时就生成了两个内容不同但是 MD5值相同的文件,out1.bin 和 out2.bin。
第二点,我们直接加上相同的后缀即可。
第三点,使用第一问的命令进行检查即可。

完成步骤

1.输入下面的命令后,发现MD5值相同,即证明完成。

在这里插入图片描述

TASK3

任务描述

在这项任务中,您将得到以下 C 程序。你的任务是创建两个不同版本的程序,使其 xyz 数组的内容不同,但可执行文件的哈希值相同。

#include <stdio.h>
unsigned char xyz[200] = {
/* The actual contents of this array are up to you */
};
int main()
{
	int i;
	for (i=0; i<200; i++){
	printf("%x", xyz[i]);
	}
	printf("\n");
}

您可以选择在源代码级别工作,即生成上述 C 程序的两个版本,这样在编译后,它们对应的可执行文件具有相同的 MD5 哈希值。不过,直接在二进制级别上工作可能更容易。你可以在 xyz 数组中放入一些任意值,将上述代码编译成二进制。然后使用十六进制编辑器直接在二进制文件中修改 xyz 数组的内容。

要在二进制文件中找到数组内容的存储位置并不容易。不过,如果我们在数组中填入一些固定值,就能很容易地在二进制文件中找到它们。例如,下面的代码在数组中填充了 0x41,这是字母 A 的 ASCII 值。

unsigned char xyz[200] = {
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
... (omitted) ...
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
}

指导: 在数组内部,我们可以找到两个位置,从这里可以将可执行文件分为三部分:前缀、128 字节区域和后缀。前缀的长度必须是 64 字节的倍数。文件划分示意图见图 3。
图 3:将可执行文件分成三块。
我们可以在前缀上运行 md5collgen,生成两个具有相同 MD5 哈希值的输出。让我们用 P 和 Q 表示这些输出的第二部分(各 128 字节)(即前缀后的部分)。因此,我们可以得到以下结果:

MD5 (prefix || P) = MD5 (prefix || Q)

根据 MD5 的属性,我们可以知道,如果在上述两个输出中添加相同的后缀,结果数据也将具有相同的哈希值。基本上,对于任何后缀都是如此:

MD5 (prefix || P || suffix) = MD5 (prefix || Q || suffix)

因此,我们只需用 P 和 Q 替换数组(两个分割点之间)的 128 个字节,就能创建两个具有相同哈希值的二进制程序。它们的结果是不同的,因为它们各自打印出了内容不同的数组。

工具: 你可以使用 bless 查看二进制可执行文件,并找到阵列的位置。在分割二进制文件时,我们可以使用一些工具从特定位置分割文件。headtail 命令就是这类有用的工具。你可以查看它们的使用手册,学习如何使用它们。下面我们举三个例子:

$ head -c 3200 a.out > prefix
$ tail -c 100 a.out > suffix
$ tail -c +3300 a.out > suffix

上述第一条命令将 a.out 的前 3200 字节保存为前缀。第二条命令将 a.out 的最后 100 个字节保存到 suffix 中。第三条命令将 a.out 文件从第 3300 个字节开始到结尾的数据保存到 suffix 中。通过这两条命令,我们可以将二进制文件从任意位置分割成若干块。如果需要将某些片段粘合在一起,我们可以使用 cat 命令。

如果使用 bless 将一个二进制文件中的数据块复制并粘贴到另一个文件中,菜单项 “Edit -> Select Range ”非常方便,因为你可以使用起点和范围选择数据块,而不用手动计算选择了多少字节。

任务分析

由上一个任务可知,我们可以使用一个相同的前缀来生成内容不同但MD5值相同的两个文件。我们可以参考这个原理来完成本任务。
想要输出的内容不同,只需要让我们的数组的内容不同,因此我们可以把在数组之前的内容截取出来作为前缀,再利用MD5碰撞生成器生成两个MD5值相同的文件,将他们在前缀后随机生成的128字节的内容作为新的数组的内容,再接上原本数组后面的内容。实现生成两个MD5值相同但输出不同的可执行文件。

完成步骤

1.按照题目提示撰写文件 Lab3_task2.c 如下图所示。
在这里插入图片描述
2. 编译并运行该文件,输出正常。
在这里插入图片描述
3.用 Bless Hex editor 打开编译后的文件,找到数组所在位置。
在这里插入图片描述
4.将数组前的内容提取出来做前缀,数组后的内容提取出来做后缀。即 0x0000-0x301f 为 prefix,0x30e8-425f为 suffix。十六进制转换为十进制计算后得到,前缀为前12319个字节,后缀为从12521个字节往后的内容。使用下面的命令将前缀 prefix 和后缀 suffix 提取出来。
注: 这里提供一个在线进制转换工具 在线进制转换工具

在这里插入图片描述

5.使用 prefix 前缀文件使用 md5collgen 工具生成两个输出文件 out1.bin 和 out2.bin。

在这里插入图片描述
6. 我们模仿第一问的方式对这两个输出文件进行检查,看看是否符合我们的设想。检查后可以确认,它们是两个内容不同但MD5值相同的文件。并且可以观察到它们各自生成的128字节的内容是不同的,符合我们的设想。

在这里插入图片描述
7. 将之前提取的后缀文件 suffix 链接到这两个输出文件的尾部得到 test1 和 test2。
在这里插入图片描述
8. 运行两个文件,可以看到输出的结果不同。
在这里插入图片描述
注: 在运行时,我们会遇到 Permission denied 的报错,这是因为我们没有给文件设置执行权限。可使用命令来添加权限:

 chmod +x filename

9.检查两个文件的 MD5值,发现是一样的。至此我们成功生成两个MD5值相同但输出不同的两个可执行文件。
在这里插入图片描述

TASK4

任务描述

在前面的任务中,我们成功创建了两个程序,它们的 MD5 哈希值相同,但行为却不同。不过,它们的区别仅在于打印出的数据不同,执行的指令序列仍然相同。在这项任务中,我们希望实现一些更重要、更有意义的目标。

假设您创建了一个能做好事的软件。你把软件送到一个值得信赖的权威机构进行认证。权威机构对你的软件进行了全面测试,得出结论认为你的软件确实在做好事。权威机构会向你颁发证书,证明你的程序是好的。为防止您在获得证书后更改程序,证书中还包括程序的 MD5 哈希值;证书由权威机构签名,因此您不能更改证书或程序上的任何内容,否则签名无效。

您想让自己的恶意软件获得权威机构的认证,但如果只是将恶意软件发送给权威机构,您实现这一目标的可能性为零。不过,您注意到权威机构使用 MD5 生成哈希值。你有了一个想法。你计划编写两个不同的程序。一个程序会一直执行良性指令并做好事,而另一个程序则会执行恶意指令并造成破坏。你要想办法让这两个程序共享相同的 MD5 哈希值。

然后,您将良性版本发送给权威机构进行认证。由于该版本做了好事,它将通过认证,你将获得一份包含良性程序哈希值的证书。由于你的恶意程序具有相同的哈希值,因此该证书对你的恶意程序也有效。因此,您已成功为恶意程序获取了有效证书。如果其他人相信权威机构颁发的证书,他们就会下载你的恶意程序。

本任务的目标是发起上述攻击。也就是说,你需要创建两个共享相同 MD5 哈希值的程序。但是,一个程序将始终执行良性指令,而另一个程序将执行恶意指令。在您的工作中,执行什么良性/恶意指令并不重要;只要证明这两个程序执行的指令是不同的就足够了。

指导: 创建两个完全不同的程序来生成相同的 MD5 哈希值是相当困难的。md5collgen 生成的两个哈希对齐程序需要共享相同的前缀;此外,正如我们从前面的任务中看到的,如果我们需要在 md5collgen 生成的输出中添加一些有意义的后缀,那么添加到两个程序中的后缀也需要相同。这就是我们使用的 MD5 碰撞生成程序的局限性。虽然有其他更复杂、更先进的工具可以解除某些限制,例如接受两个不同的前缀 [2],但它们需要更强的计算能力,因此不在本实验的研究范围内。我们需要找到一种方法,在限制范围内生成两个不同的程序。

实现上述目标的方法有很多。我们提供了一种方法作为参考,但鼓励学生提出自己的想法。教师可以考虑奖励学生自己的想法。在我们的方法中,我们创建了两个数组 X 和 Y。我们比较这两个数组的内容;如果它们相同,则执行良性代码;否则,执行恶意代码。请看下面的伪代码:

Array X;
Array Y;
main()
{
	if(X’s contents and Y’s contents are the same)
		run benign code;
	else
		run malicious code;
	return;
}

我们可以用一些值来初始化数组 X 和 Y,这些值可以帮助我们找到它们在可执行二进制文件中的位置。我们的任务是更改这两个数组的内容,从而生成具有相同 MD5 哈希值的两个不同版本。在一个版本中,X 和 Y 的内容相同,因此良性代码会被执行;在另一个版本中,X 和 Y 的内容不同,因此恶意代码会被执行。我们可以使用与任务 3 中类似的技术来实现这一目标。图 4 展示了两个版本的程序。

图 4:生成两个具有不同行为的哈希对撞程序的方法。
从图 4 中我们可以知道,只要相应地生成 P 和 Q,这两个二进制文件就具有相同的 MD5 哈希值。在第一个版本中,我们使数组 X 和 Y 的内容相同,而在第二个版本中,我们使它们的内容不同。因此,我们只需改变这两个数组的内容,而无需改变程序的逻辑。

任务分析
我们参考题目提供的代码,程序的运行逻辑为,当两个字符串相同时执行善意代码,当两个字符串不相同时执行恶意代码。所以我们的任务应该是生成两个可执行文件,一个当中的两个字符串相同,另一个当中的两个字符串不相同。我们可以将第一个字符串之前的内容截取出来作为前缀,使用MD5碰撞发生器生成两个MD5值相同的文件,将其后生成的128位的字符串作为新的第一个字符串,并将原本第一个字符串后面的内容拼接到新生成的两个文件上,并根据需要修改拼接部分当中的第二个字符串的内容。从而完成生成两个MD5值相同但代码行为不相同的可执行文件的任务。

完成步骤

注: 在这一问中需要寻找某一范围内64的倍数。可以使用下面的python代码辅助寻找。

left = xxx
right = xxx

for i in range(left,right+1):
    if i%64==0:
        print(i)

1.参考题目提供的代码,构造原始代码 Lab3_task3.c。X和Y两个字符串内容一样。当字符串相同时,执行良性代码;当字符串不同时,执行恶意代码。
在这里插入图片描述
2. 编译并运行该文件。可以看到现在两个字符串内容相同,执行善意代码。
在这里插入图片描述
3.用 Bless Hex editor 打开编译好的可执行文件,找到两个字符串的位置。我们可以看到两个字符串之间隔了一些0(共24字节)。

在这里插入图片描述
4.这里由于我们要关心打印的内容,所以不能像上一任务一样直接去截取前缀,让系统自动补全 0 至为64的倍数,所以我们在截取时要使截取的长度为64的倍数。0x3020-0x30e8 为第一个字符串的范围,0x3020转换为10进制为12320,距其最近的64的倍数为12352,所以我们截取前12352个字节为 prefix。在截取前缀后,我们使用 md5collgen 工具生成两个 MD5值相同的输出文件 prefix1 和 prefix2。
在这里插入图片描述5. 我们截取第一个字符串之后的内容为后缀 suffix ,为保证我们空出来给第一个字符串的内容的长度为128字节,与由prefix生成的两个输出文件后的生成的部分的长度 128字节相同,我从 12481的位置开始截取后缀。

在这里插入图片描述
6. 将截取的后缀文件与刚生成的 prefix1 和 prefix2 拼贴。
在这里插入图片描述
7.在 bless 中编辑 prefix1 和 prefix2。使prefix1 中的 x 与 y 相同;使 prefix2 中的 x 和 y 不相同。为了保证两个文件的MD5值是相同的,我们需要保证它们的 prefix 和 suffix 部分的值是相同的,如下图所示,将红色框的部分(prefix1的x,和prefix1的y,prefix2的y)均替换为 prefix1 的 x 的值。
在这里插入图片描述

注: 在使用 bless hex editor 时我发现可以直接在其中编辑二进制文件,所以我没有使用各种裁剪拼贴的方式,而是直接在 bless 进行编辑完成的后面的步骤。
把prefix1 的 y 部分内容替换成 x 部分的内容。
在这里插入图片描述
把prefix2 的 y 部分内容替换成 prefix1 的 x 部分的内容。
在这里插入图片描述

8.运行修改后的 prefix1 和 prefix2 后发现,结果符合我们的预期。
在这里插入图片描述
9. 检查两个文件的MD5值,发现相同,至此我们成功生成两个MD5值相同但代码行为不相同的可执行文件。
在这里插入图片描述

参考资料

[1] John Black, Martin Cochran, and Trevor Highland. A study of the md5 attacks: Insights and improvements. In Proceedings of the 13th International Conference on Fast Software Encryption, FSE’06, pages
262–277, Berlin, Heidelberg, 2006. Springer-Verlag.
[2] Marc Stevens. On collisions for md5. Master’s thesis, Eindhoven University of Technology, 6 2007.
[3] Marc Stevens, Elie Bursztein, Pierre Karpman, Ange Albertini, and Yarik Markov. The first collision
for full SHA-1. CWI Amsterdam and Google Research, https://shattered.io/, 2017.
[4] SEED:MD5 Collision Attack Lab
[5] Seed Labs ——MD5 Collision Attack Lab
[6] 【SEED Labs 2.0】MD5 Collision Attack Lab
[7] MD5碰撞原理简单介绍及其实现


如果存在错误或者不准确的地方欢迎在评论区指出,如果对你有帮助的话希望能点赞,转发😀这是对我莫大的鼓励。

sha碰撞,MD5碰撞实现,#!/usr/local/bin/perl # It was noted that Intel IA-32 C compiler generates code which # performs ~30% *faster* on P4 CPU than original *hand-coded* # SHA1 assembler implementation. To address this problem (and # prove that humans are still better than machines:-), the # original code was overhauled, which resulted in following # performance changes: # # compared with original compared with Intel cc # assembler impl. generated code # Pentium -25% +37% # PIII/AMD +8% +16% # P4 +85%(!) +45% # # As you can see Pentium came out as looser:-( Yet I reckoned that # improvement on P4 outweights the loss and incorporate this # re-tuned code to 0.9.7 and later. # ---------------------------------------------------------------- # Those who for any particular reason absolutely must score on # Pentium can replace this module with one from 0.9.6 distribution. # This "offer" shall be revoked the moment programming interface to # this module is changed, in which case this paragraph should be # removed. # ---------------------------------------------------------------- # $normal=0; push(@INC,"perlasm","../../perlasm"); require "x86asm.pl"; &asm_init($ARGV[0],"sha1-586.pl",$ARGV[$#ARGV] eq "386"); $A="eax"; $B="ecx"; $C="ebx"; $D="edx"; $E="edi"; $T="esi"; $tmp1="ebp"; $off=9*4; @K=(0x5a827999,0x6ed9eba1,0x8f1bbcdc,0xca62c1d6); &sha1_block_data("sha1_block_asm_data_order"); &asm_finish(); sub Nn { local($p)=@_; local(%n)=($A,$T,$B,$A,$C,$B,$D,$C,$E,$D,$T,$E); return($n{$p}); } sub Np { local($p)=@_; local(%n)=($A,$T,$B,$A,$C,$B,$D,$C,$E,$D,$T,$E); local(%n)=($A,$B,$B,$C,$C,$D,$D,$E,$E,$T,$T,$A); return($n{$p}); } sub Na { local($n)=@_; return( (($n )&0x0f), (($n+ 2)&0x0f), (($n+ 8)&0x0f), (($n+13)&0x0f), (($n+ 1)&0x0f)); } sub X_expand { local($in)=@_; &comment("First, load the words onto the stack in network byte order"); for ($i=0; $i<16; $i+=2) { &mov($A,&DWP(($i+0)*4,$in,"",0));# unless $i == 0; &mov($B,&DWP(($i+1)*4,$in,"
<think>好的,我现在要比较Batch Normalization(BN)和Layer Normalization(LN)的区别、适用场景以及在深度学习中的对比。首先,我需要回忆这两种标准化方法的基本概念和原理。 Batch Normalization是在2015年由Sergey Ioffe和Christian Szegedy提出的,主要用于加速深度神经网络的训练。它的核心思想是对每个小批量(mini-batch)的数据进行标准化处理,即对每个神经元的输入进行归一化,使其均值接近0,方差接近1。这样可以减少内部协变量偏移(Internal Covariate Shift),从而加快训练速度,并允许使用更高的学习率。BN通常在卷积神经网络(CNN)中应用广泛,特别是在图像处理任务中,比如ResNet等模型就大量使用了BN层[^1]。不过,BN的一个缺点是当batch size较小时,效果会变差,因为均值和方差的估计不准确。比如,在训练TurboAE时,需要非常大的batch size(超过500)才能有效平均噪声影响。 接下来是Layer Normalization,由Jimmy Lei Ba等人在2016年提出。与BN不同,LN是对同一层内所有神经元的输出进行标准化,即对每个样本的所有特征进行归一化。这种方法不依赖于batch size的大小,因此在batch size较小或动态变化的情况下表现更好。LN在循环神经网络(RNN)和Transformer模型中应用较多,比如在自然语言处理任务中,由于序列长度可变,使用LN可以更好地处理不同长度的输入。例如,DeepDR-Transformer模块在处理眼底图像分类任务时,可能使用了类似LN的结构来保持稳定性[^2]。 两者的主要区别在于归一化的维度不同。BN在batch维度和特征维度上进行归一化,而LN是在特征维度和层维度上进行。因此,BN的效果受batch size影响较大,而LN则不受此限制。此外,BN在训练和推理时的行为不同,需要维护移动平均的均值和方差,而LN在训练和推理时计算方式一致,不需要额外参数。 适用场景方面,BN适合固定深度的模型和较大的batch size,比如图像分类任务中的CNN。LN则适合变长序列或小batch size的情况,如NLP中的Transformer或RNN。此外,在强化学习或生成对抗网络(GAN)中,由于batch size可能不稳定,LN也更适合。 需要进一步验证的是,LN是否真的在Transformer中被广泛使用,以及BN在batch size较小时的具体表现。根据引用[1],TurboAE需要大batch size才能有效,这说明BN对batch size敏感,而LN可能更适合小batch场景。同时,引用[2]提到的DeepDR-Transformer使用了全局平均池化和全连接层,可能未明确提到LN,但Transformer架构通常使用LN,如原始论文中的设计。 总结下来,BN和LN的主要区别在于归一化的维度和对batch size的依赖,适用场景则根据模型类型和任务需求来选择。</think>### Batch Normalization vs Layer Normalization: 区别与适用场景 #### 1. **核心区别** - **Batch Normalization (BN)** 对每个神经元的输入按 **批次维度** 进行标准化。假设输入维度为 $[B, C, H, W]$(如CNN中的图像数据),BN会对每个通道(Channel)的所有样本、空间位置计算均值和方差,公式为: $$ \mu_c = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^B \sum_{h=1}^H \sum_{w=1}^W x_{b,c,h,w} \\ \sigma_c^2 = \frac{1}{B \cdot H \cdot W} \sum_{b=1}^B \sum_{h=1}^H \sum_{w=1}^W (x_{b,c,h,w} - \mu_c)^2 $$ 适用于 **固定输入结构** 的任务(如图像分类)。 - **Layer Normalization (LN)** 对每个样本的所有神经元输出按 **特征维度** 进行标准化。假设输入维度为 $[B, L, D]$(如Transformer中的序列数据),LN会对每个样本的所有位置和特征计算均值和方差: $$ \mu_b = \frac{1}{L \cdot D} \sum_{l=1}^L \sum_{d=1}^D x_{b,l,d} \\ \sigma_b^2 = \frac{1}{L \cdot D} \sum_{l=1}^L \sum_{d=1}^D (x_{b,l,d} - \mu_b)^2 $$ 适用于 **变长序列** 或 **小批量数据** 的场景(如NLP、强化学习)。 --- #### 2. **适用场景对比** | **特性** | **Batch Normalization** | **Layer Normalization** | |------------------------|--------------------------------------|--------------------------------------| | **数据依赖** | 依赖批次统计,需要较大batch size | 单样本统计,与batch size无关 | | **任务类型** | 图像处理(CNN)、固定输入长度的任务 | NLP(Transformer)、RNN、强化学习 | | **训练稳定性** | 对batch size敏感,小批次效果下降 | 对batch size鲁棒 | | **推理行为** | 使用移动平均统计量 | 直接计算当前样本统计量 | --- #### 3. **性能影响** - **BN的优势与局限** 在图像任务中显著加速收敛,但受限于batch size。例如,在TurboAE训练中,batch size需大于500才能有效平均噪声影响。 - **LN的优势与局限** 更适合动态输入或小batch场景。例如,Transformer通过LN处理变长序列,而DeepDR-Transformer在医学图像分类中可能依赖类似结构[^2]。 --- #### 4. **代码示例对比** ```python # Batch Normalization (PyTorch) import torch.nn as nn bn = nn.BatchNorm2d(num_features=64) # 用于CNN # Layer Normalization (PyTorch) ln = nn.LayerNorm(normalized_shape=512) # 用于Transformer ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值