1. 动态范围的常用计算方法
动态范围(Dynamic
Range)指的是输入数据中数值的范围,计算动态范围是为了确定量化时使用的比特位数(还是抽象😂)。个人理解:考虑到输入数据可能存在数据分布不均,即有些数据偏离过大。而过大的偏离值,会影响求得的scale,进而影响量化的精度。因此,合理选取数据中的范围,去除偏离值,获得更好的Scale,毕竟Scale会影响到整个量化的精度。
常用的动态范围计算方法方法:
- Max方法:在对称量化中直接取输入数据中的绝对值的最大值作为量化的最大值。这种方法简单易用,但容易受到噪声等异常数据的影响,导致动态范围不准确。
- Histogram方法:统计输入数据的直方图,根据先验知识获取某个范围内的数据,从而获得对称量化的最大值。这种方法可以减少噪声对动态范围的影响,但需要对直方图进行统计,计算复杂度较高。
- Entropy方法:将输入数据的概率密度函数近似为一个高斯分布,以最小化熵作为选择动态范围的准则。这种方法也可以在一定程度上减少噪声对动态范围的影响,但需要对概率密度函数进行拟合和计算熵,计算复杂度较高。
对称量化和非对称量化的选择与动态范围的计算方法有一定的关系:
- 对称量化要求量化的最大值和最小值的绝对值相等,可以采用Max方法或Histogram方法进行计算。
- 非对称量化则可以采用Entropy方法进行计算,以最小化量化后的误差。
下面将对Histogram方法和Entropy方法进行详细介绍
2. Histogram
2.1 直方图的定义
直方图(histogram) 是统计学中常用的一种图形,它将数据按照数值分组并统计每组数据的出现频率,然后将频率用柱状图的方式表示出来。直方图通常用于描述一组数据的分布情况,可以帮助人们了解数据的特征,例如数据的中心位置、离散程度、对称性、峰态等。直方图如下图所示:
生成直方图的代码:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(1000)
plt.hist(data, bins=50)
plt.title("histgram")
plt.xlabel("value")
plt.ylabel("freq")
# plt.savefig("histgram.png", bbox_inches="tight")
plt.show()
2.2 基于Histogram的动态范围实现
为什么要使用Histogram来计算动态范围?
主要在于直方图统计了数据出现的频率,它可以将数据按照一定的区间进行离散化处理,并计算每个区间中数据点的数量。这种方法相对于Max方法来说,能够更好地反映数据的分布情况,从而更准确地评估数据的动态范围。
算法流程
我们假设数据服从正态分布,即离散点在两边,我们可以通过从两边向中间靠拢的方法,去除离散点,类似于双指针的方法,算法具体流程如下:
- 首先,统计输入数据的直方图和范围
- 然后定义左指针和右指针分别指向直方图的左边界和右边界
- 计算当前双指针之间的直方图覆盖率,如果小于等于设定的覆盖率阈值,则返回此刻的左指针指向的直方图值,如果不满足,则需要调整双指针的值,向中间靠拢
- 如果当前左指针所指向的直方图值大于右指针所指向的直方图值,则右指针左移,否则左指针右移
- 循环,直到双指针覆盖的区域满足要求
基于上述算法流程的示例代码:
def scale_cal(x):
max_val = np.max(np.abs(x))
return max_val / 127
# histogram_range函数是基于数据直方图计算缩放因子的。
# 该函数先将数据进行直方图统计。然后,使用双指针算法去除数据直方图中的离散点。最后,通过剩余数据的动态范围计算出缩放因子。
def histogram_range(x):
hist, range = np.histogram(x, 100)
total = len(x)
left = 0
right = len(hist) - 1
limit = 0.99
while True:
cover_percent = hist[left:right].sum() / total
if cover_percent <= limit:
break
if hist[left] > hist[right]:
right -= 1
else:
left += 1
left_val = range[left]
right_val = range[right]
dynamic_range = max(abs(left_val), abs(right_val))
return dynamic_range / 127
if __name__ == "__main__":
np.random.seed(1)
data_float32 = np.random.randn(1000).astype('float32')
# print(f"input = {data_float32}")
scale = scale_cal(data_float32)
scale2 = histogram_range(data_float32)
print(f"scale = {scale} scale2 = {scale2}")
Histogram方法虽然能够解决Max方法中的离散点噪声问题,但是使用数据直方图进行动态范围的计算,要求数据能够比较均匀地覆盖到整个动态范围内。
- 如果数据服从类似正态分布,则直方图的结果具有参考价值,因为此时的数据覆盖动态范围的概率较高。
- 但如果数据分布极不均匀或出现大量离散群,则直方图计算的结果可能并不准确。此时可考虑其它的动态范围计算方法。
3. Entropy
3.1 Entropy的定义
Entropy方法是一种基于概率分布的动态范围计算方法,通过计算概率分布之间的KL散度来选择合适的动态范围。
在模型量化中,通常所用的熵(Entropy)是指量化后的输出值的熵,即量化后的概率分布。因此,使用熵方法计算动态范围就是在计算量化后的概率分布。那么我们该如何度量量化前后的误差呢?可以使用KL散度。
在概率论或信息论中,KL散度(Kullback-Leibler divergence)又称为相对熵(relative entropy),是描述两个概率分布P和Q差异的一种方法。公式如下:
KL散度值越小,代表两种分布越相似,量化误差越小;反之,KL散度值越大,代表二种分布差异越大,量化误差越大。
3.2 基于Entropy的动态范围实现
算法流程
Entropy方法中使用了直方图和概率分布的方法,进而计算动态范围。其流程如下:
- 统计直方图分布。首先,对于待量化的数据,统计其数值分布情况,得到数据的直方图
- 生成p分布和q分布
- 对p和q进行归一化。将p和q的概率分布进行归一化,使其满足概率分布的性质
- 计算p和q的KL散度。使用KL散度方法,计算p和q两个概率分布之间的距离,作为衡量量化误差的指标。KL散度越小表示两个分布越相似,因此在动态范围的选择中,KL散度越小的分布更加合适
在量化操作中,动态范围通常是由P和Q两个概率分布的取值范围决定的。P分布表示原始数据在浮点数表示下的取值范围,Q分布则表示对应的量化数据的取值范围。因此,Entropy方法计算P和Q的KL散度,是为了得到最优的Q分布,而最优的Q分布代表量化数据的最优取值范围,量化数据的最优取值范围和原始数据的取值范围都知道了,那么最优的Scale就确定下来了。
示例代码一
下面是通过生成的两组随机数据使用Entropy方法估计数据的动态范围的示例代码:
import numpy as np
import matplotlib.pyplot as plt
# 首先定义了一个计算KL散度的函数cal_kl,用于计算两个概率分布P和Q之间的KL散度
def cal_kl(p, q):
KL = 0
for i in range(len(p)):
KL += p[i] * np.log(p[i]/q[i])
return KL
# 定义了一个kl_test函数,用于使用Entropy方法来估计数据的动态范围。
# 在kl_test中,先随机生成概率分布y,将其归一化后计算与输入的概率分布x之间的KL散度,如果小于阈值,则认为当前的概率分布y最优,结束迭代。否则继续生成一个新的随机概率分布y,重复KL散度计算,直到找到满足条件的最优概率分布
def kl_test(x, kl_threshod = 0.01):
y_out = []
while True:
y = [np.random.uniform(1, size+1) for i in range(size)]
y /= np.sum(y)
kl_result = cal_kl(x, y)
if kl_result < kl_threshod:
print(kl_result)
y_out = y
plt.plot(x)
plt.plot(y)
break
return y_out
if __name__ == "__main__":
np.random.seed(1)
size = 10
x = [np.random.uniform(1, size+1) for i in range(size)]
x /= np.sum(x)
y_out = kl_test(x, kl_threshod = 0.01)
plt.show()
print(x, y_out)
下面是KL散度阈值为0.01时原始数据x(蓝色)和最优概率分布y(橙色)的可视化图,可以看到此时的x和y的分布比较接近: