深度学习与浮点数精度问题探究
一 浮点数概述
浮点数是一种用于近似实数的数值表示法,它允许在很大范围内表示数字,包括非常小和非常大的值。浮点数的名字来源于“浮动的小数点”,因为小数点的位置可以在数中浮动以适应不同大小的数值。
在计算机科学中,浮点数通常遵循IEEE 754标准,该标准定义了半精度(16位),单精度(32位)和双精度(64位)三种主要的浮点数格式。这个标准还规定了特殊值如无穷大、负零、以及非数值(NaN, Not a Number)的表现形式。
一个典型的浮点数由三部分组成:
- 符号位(Sign bit):用来表示数的正负,0表示正数,1表示负数。
- 指数部分(Exponent):用来表示小数点移动的位数。它可以是正数或负数,这取决于实际的数值大小。
- 尾数部分(Mantissa 或 Fraction):也称为有效数或系数,它包含了数的实际数字信息。为了节省空间并提高精度,IEEE 754标准使用了一种隐含的前导1的表示方法(对于规格化数),只存储小数部分。
浮点运算可能会导致舍入误差,这是因为不是所有的实数都可以精确地用有限的二进制位来表示。例如,分数1/3在十进制下是一个无限循环小数,在二进制下同样如此。因此,当执行涉及浮点数的算术运算时,结果可能不完全准确,尽管对于大多数应用来说,这种不准确性是可以接受的。
二 浮点数介绍
2.1 双精度浮点数 double
双精度浮点数的结构如下:
- 符号位(Sign bit):1位,用来表示数的正负。0表示正数,1表示负数。
- 指数部分(Exponent):11位,允许表示一个更宽范围的指数值。指数采用偏置(Bias)表示法,对于双精度浮点数,偏置值为1023。
- 尾数部分(Mantissa 或 Fraction):52位,加上隐含的前导1,实际上可以提供53位的有效数字精度。
具体来说,一个双精度浮点数的值计算方式如下:
( − 1 ) sign × 2 (exponent - bias) × ( 1 + mantissa ) (-1)^{\text{sign}} \times 2^{\text{(exponent - bias)}} \times (1 + \text{mantissa}) (−1)sign×2(exponent - bias)×(1+mantissa)
这里:
sign
是符号位,0或1;exponent
是指数部分的实际二进制值;bias
对于双精度浮点数是1023;mantissa
是尾数部分的二进制值,它是一个小于1的小数,表示形式为b_1 b_2 ... b_52
,其中每个b_i
都是0或1。
双精度浮点数能够表示大约从 (2^{-1022}) 到 (2^{1023}) 的正规化数,并且有特殊的值用于表示无穷大、负零以及非数值(NaN)。这种格式适用于需要高精度和大数值范围的应用,如科学计算、工程应用、金融计算等。
2.2 单精度浮点数 float
单精度浮点数的结构如下:
- 符号位(Sign bit):1位,用来表示数的正负。0表示正数,1表示负数。
- 指数部分(Exponent):8位,允许表示一个相对宽范围的指数值。指数采用偏置(Bias)表示法,对于单精度浮点数,偏置值为127。
- 尾数部分(Mantissa 或 Fraction):23位,加上隐含的前导1,实际上可以提供24位的有效数字精度。
具体来说,一个单精度浮点数的值计算方式如下:
( − 1 ) sign × 2 (exponent - bias) × ( 1 + mantissa ) (-1)^{\text{sign}} \times 2^{\text{(exponent - bias)}} \times (1 + \text{mantissa}) (−1)sign×2(exponent - bias)×(1+mantissa)
这里:
sign
是符号位,0或1;exponent
是指数部分的实际二进制值;bias
对于单精度浮点数是127;mantissa
是尾数部分的二进制值,它是一个小于1的小数,表示形式为b_1 b_2 ... b_23
,其中每个b_i
都是0或1。
单精度浮点数能够表示大约从 (2^{-126}) 到 (2^{127}) 的正规化数,并且有特殊的值用于表示无穷大、负零以及非数值(NaN)。这种格式适用于大多数不需要极高精度的应用,如图形渲染、视频游戏开发和其他实时应用,在这些场景中,效率往往比绝对的数值精度更重要。
2.3 半精度浮点数 half
半精度浮点数的结构如下:
- 符号位(Sign bit):1位,用来表示数的正负。0表示正数,1表示负数。
- 指数部分(Exponent):5位,允许表示一个有限范围的指数值。指数采用偏置(Bias)表示法,对于半精度浮点数,偏置值为15。
- 尾数部分(Mantissa 或 Fraction):10位,加上隐含的前导1,实际上可以提供11位的有效数字精度。
具体来说,一个半精度浮点数的值计算方式如下:
( − 1 ) sign × 2 (exponent - bias) × ( 1 + mantissa ) (-1)^{\text{sign}} \times 2^{\text{(exponent - bias)}} \times (1 + \text{mantissa}) (−1)sign×2(exponent - bias)×(1+mantissa)
这里:
sign
是符号位,0或1;exponent
是指数部分的实际二进制值;bias
对于半精度浮点数是15;mantissa
是尾数部分的二进制值,它是一个小于1的小数,表示形式为b_1 b_2 ... b_10
,其中每个b_i
都是0或1。
半精度浮点数能够表示大约从 (2^{-14}) 到 (2^{15}) 的正规化数,并且有特殊的值用于表示无穷大、负零以及非数值(NaN)。由于其有限的精度和范围,半精度浮点数主要用于特定的应用场景,在这些场景中,内存和带宽的节省比数值精度更为重要。
三 模型浮点数
3.1 深度学习的各种浮点数
在深度学习中,浮点数格式的选择对于模型训练的效率、精度和硬件资源的利用有着重要的影响。常用的浮点数格式包括:
-
单精度浮点数(float32):
- 这是深度学习中常用的数据类型。它提供了足够的数值范围和精度来表示大多数神经网络中的权重和激活值。
- 占用32位(4字节),能够很好地平衡计算精度与内存使用。
- 支持广泛存在于各种硬件平台上,如CPU、GPU等。
-
半精度浮点数(float16 或 binary16):
- 逐渐被用于加速训练过程,特别是在那些对精度要求不是非常高的应用中,比如图像识别或自然语言处理。
- 占用16位(2字节),可以显著减少内存占用和带宽需求,从而加快数据传输速度,并可能提高某些硬件上的运算速度。
- 不所有硬件都原生支持float16运算,但现代GPU(如NVIDIA的Volta, Turing, 和Ampere架构)已经开始提供对此格式的良好支持,使得其在训练和推理阶段都能得到高效利用。
-
混精度浮点数(Mixed Precision):
- 混合精度是一种结合了单精度和半精度浮点数的技术,旨在充分利用两者的优势。在训练过程中,模型参数和梯度通常以单精度存储,而前向传播和反向传播计算则使用半精度执行。
- 这种方法可以在不明显牺牲模型最终性能的情况下大幅加快训练速度并降低内存消耗。
- NVIDIA等公司提供的深度学习框架和库(如TensorFlow、PyTorch以及NVIDIA的Apex库)已经实现了混合精度训练的支持。
-
双精度浮点数(float64):
- 虽然提供了更高的精度,但在深度学习中并不常见,因为大多数情况下float32已经足够满足需求,而且使用float64会增加不必要的计算开销和内存占用。
- 在某些需要极高数值稳定性的特殊应用场景中可能会用到,例如涉及高精度科学计算的任务。
3.2 深度学习半精度浮点数
在深度学习中,半精度浮点数格式(如float16和bfloat16)因其能够减少内存占用、加快计算速度并降低功耗而变得越来越重要。下面将具体介绍这两种格式:
Float16 (binary16)
-
结构:Float16是IEEE 754标准定义的一种16位浮点数格式。
- 符号位(Sign bit):1位
- 指数部分(Exponent):5位,偏置值为15
- 尾数部分(Mantissa 或 Fraction):10位
-
优点:
- 减少了一半的存储空间需求,相比32位单精度浮点数(float32),可以显著减少模型大小和数据传输量。
- 在支持float16运算的硬件上,例如现代GPU,可以加速训练和推理过程。
-
局限性:
- 数值范围和精度有限,可能不适合所有应用,特别是在数值稳定性要求较高的场景。
- 并非所有硬件都原生支持float16运算,这可能导致需要软件仿真,从而影响性能。
BFloat16 (Brain Floating-Point Format)
-
结构:BFloat16是一种由Google开发的16位浮点数格式,特别设计用于机器学习任务。
- 符号位(Sign bit):1位
- 指数部分(Exponent):8位,与32位单精度浮点数相同,偏置值为127
- 尾数部分(Mantissa 或 Fraction):7位,加上隐含的前导1,提供8位的有效数字精度
-
优点:
- 保留了与32位单精度浮点数相同的指数范围,这意味着它可以表示几乎同样大的数值范围,这对于避免数值下溢或上溢非常有用。
- 由于尾数部分比float16少3位,它提供的精度略低于float16,但对于许多深度学习应用来说,这种精度损失是可以接受的。
- 更好地兼容现有的32位单精度浮点数生态系统,便于实现混合精度训练,即使用bfloat16进行计算,但以float32保存变量。
-
局限性:
- 相较于float16,提供的有效数字精度较低,可能会对某些对精度敏感的应用产生影响。
- 需要特定的硬件支持来发挥其优势,虽然一些最新的CPU和GPU已经开始支持bfloat16,但并非普遍可用。
应用场景
- Float16常用于图形处理、视频编码等对数值范围要求不高的领域,以及那些对内存带宽和存储有较高要求的应用。
- BFloat16更倾向于被应用于深度学习训练和推理,尤其是在那些对数值范围有更高要求的任务中,如自然语言处理和语音识别。