(本文首发于公众号)
在之前的文章中提到过可以把 ReLU 合并到 Conv 中加速量化推理,当时只是用一个例子简单介绍一下过程,逻辑上存在一些漏洞。本文打算从数学上深入剖析一下其中的原理,并进一步扩展到其他激活函数,看看在网络量化中激活函数一般是怎么处理的。
温故知新
为了简单起见,假设我们是量化到 uint8 的数值范围「即0~255」。回忆一下量化的基本公式 (我在之前的文章中多次强调这几个公式,它们非常重要):
r = S ( q − Z ) (1) r=S(q-Z) \tag{1} r=S(q−Z)(1)
q = c l i p ( r o u n d ( r S + Z ) , 0 , 255 ) (2) q = clip(round(\frac{r}{S}+Z),0,255) \tag{2} q=clip(round(Sr+Z),0,255)(2)
再简单重复一下符号的含义, r r r 表示实数, q q q 表示量化后的定点数, S S S 和 Z Z Z 分别是是 scale 和 zero point。
注意,这次我对 q q q 单独加了一个 clip 操作,在之前的文章中,这一步在公式中被我省略了,不过在实际量化的时候,这一步是必须的,否则会有数值溢出的危险。
现在,假设激活函数为 f ( x ) f(x) f(x),应用到实数域上是这个样子:
r 2 = f ( r 1 ) (3) r_2=f(r_1) \tag{3} r2=f(r1)(3)
那么把 (1) 式代入后可以得到量化的公式:
S 2 ( q 2 − Z 2 ) = f ( S 1 ( q 1 − Z 1 ) ) (4) S_2(q_2-Z_2)=f(S_1(q_1-Z_1)) \tag{4} S2(q2−Z2)=f(S1(q1−Z1))(4)
这就是量化时处理所有激活函数的总口诀,别看它平平无奇,但话越少,信息量越多。下面,我们就看看针对具体的激活函数,怎么运用这个公式。
ReLU
ReLU 是一个非常简单的函数,它除了把小于 0 的数值截断外,甚至不做任何操作:
R e L U ( x ) = { x x > = 0 0 x < 0 (5) \begin{aligned} ReLU(x)=\begin{cases} x & x >= 0 \\ 0 & x < 0 \end{cases} \tag{5} \end{aligned} ReLU(x)={
x0x>=0x<0(5)
如果把上面的函数 f f f 替换成 ReLU 的公式,就可以得到:
r 2 = { r 1 r 1 > = 0 0 r 1 < 0 (6) \begin{aligned} r_2=\begin{cases} r_1 & r_1 >= 0 \\ 0 & r_1<0 \end{cases} \tag{6} \end{aligned} r2={
r10r1>=0r1<0(6)
把 (1) 式代入就变成:
S 2 ( q 2 − Z 2 ) = { S 1 ( q 1 − Z 1 ) q 1 > = Z 1 0 q 1 < Z 1 (7) S_2(q_2-Z_2)=\begin{cases} S_1(q_1-Z_1) & q_1 >= Z_1 \\ 0 & q_1 < Z_1 \end{cases} \tag{7} S2(q2−Z2)={
S1(q1−Z1)0q1>=Z1q1<Z1(7)
换算一下可以得到:
q 2 = { S 1 S 2 ( q 1 − Z 1 ) + Z 2 q 1 > = Z 1 Z 2 q 1 < Z 1 (8) q_2=\begin{cases} \frac{S_1}{S_2}(q_1-Z_1)+Z_2 & q_1 >= Z_1 \\ Z_2 & q_1 < Z_1 \end{cases} \tag{8} q2={
S2S1(q1−Z