本文首发于GiantPandaCV公众号。

1. 前言

这篇文章首先详细介绍了DoreFaNet任意比特量化网络的原理,然后提供了DoreFaNet的Pytorch代码实现解析,并给出将DoreFa-Net应用到YOLOV3模型上之后的精度测试结果。论文原文:https://arxiv.org/pdf/1606.06160.pdf

2. DoreFa-Net

和前面我们讲过的BNN和TWN相比,DoreFa-Net并没有针对卷积层输出的每一个卷积核计算比例因子,而是直接对卷积层的整体输出计算一个比例因子,这样就可以简化反向传播时候的运算,因为在DoreFa-Net中反向传播也要量化。

首先我们介绍一下DoreFa-Net中的比特卷积核,然后详细说明如何使用低比特的方法量化权值,激活值以及梯度。

2.1 比特卷积核

我们知道,BNN中的点积可以用下面的公式表示:

上面的式子同样也可以用在计算低位宽定点整数之间的乘加运算。假设xxx是一个MMM位定点整数序列集合,x=∑m=0M−1cm(x)x=\sum_{m=0}^{M-1}c_m(x)x=∑m=0M−1​cm​(x),yyy是一个KKK位定点整数序列集合,y=∑k=0K−1ck(y)y=\sum_{k=0}^{K-1}c_k(y)y=∑k=0K−1​ck​(y),这里的(cm(x))m=0M−1(c_m(x))_{m=0}^{M-1}(cm​(x))m=0M−1​和(ck(y))k=0K−1(c_k(y))_{k=0}^{K-1}(ck​(y))k=0K−1​都是位向量,xxx和yyy的点积可以由位运算来计算:


在上面的等式中,计算复杂度为M∗KM*KM∗K,和位宽成正比。

2.2 直通估计器

然后为了规避0梯度的问题,使用了直通估计(STE)。

使用直通估计器(STRAIGHT-THROUGHESTIMATOR,STE)的原因可以用一个例子加以说明,假如网络有一个ReLU激活函数,并且网络被初始时即存在一套权重。这些ReLU的输入可以是负数,这会导致ReLU的输出为0。对于这些权重,ReLU的导数将会在反向传播过程中为0,这意味着该网络无法从这些导数学习到任何东西,权重也无法得到更新。针对这一点,直通估计器将输入的梯度设置为一个等于其输出梯度的阈值函数,而不管该阈值函数本身的实际导数如何。

一个简单的例子是在伯努利分布采样中定义的STE为:


在这里,ccc是目标函数,由于从伯努利分布中采样是一个不可微分的过程,∂c∂q\frac{\partial c}{\partial q}∂q∂c​没有定义,因此反向传播中的梯度不能由链式法则直接算出∂c∂p\frac{\partial c}{\partial p}∂p∂c​,然而由于ppp和qqq的期望相同,我们可以使用定义好的梯度∂c∂q\frac{\partial c}{\partial q}∂q∂c​对∂c∂p\frac{\partial c}{\partial p}∂p∂c​做近似,并且构建了一个如上所示的STE,因此STE实际上给出了一个对∂c∂p\frac{\partial c}{\partial p}∂p∂c​的定义。在本文的工作中广泛使用的STE是量化器—将一个真实的浮点数输入ri∈[0,1]r_i\in [0,1]ri​∈[0,1]量化未kkk位输出ro∈[0,1]r_o\in [0,1]ro​∈[0,1],定义的STE如下:


我们可以看到,直通估计器STE的输出qqq是一个由kkk位表示的真实数,由于ror_oro​是一个kkk位定点整数,卷积计算可以由等式(3)高效执行,后面跟着正确的缩放即可。

2.3 权重的低比特量化

在之前的工作中,STE被用来做二值化权重,比如在BNN中,权重被下面的STE二值化:


在XNOR-Net中,权重按照下面的STE二值化,不同之处在于权重在二值化之后进行了缩放:


在XOR-Net中,缩放因子EF(∣ri∣)E_{F}(|r_i|)EF​(∣ri​∣)是对应卷积核的权重绝对值均值。理由是引入这个缩放因子将会增加权重表达范围,同时仍然可以在前向传播卷积时做位运算。因此,在本文的实验中,使用一个常量缩放因子来替代通道级缩放。在这篇论文中,对于所有的二值化权重使用同一个缩放因子:


当k>1k>1k>1时,论文使用kkk位表达的权重,然后将STE fwkf_{w}^kfwk​应用在权重上:


在量化到kkk位之前,论文先使用tanh将权重限制在[-1,1]之间。通过tanh(ri)2max(∣tanh(ri)∣)+1/2\frac{tanh(r_i)}{2max(|tanh(r_i)|)}+1/22max(∣tanh(ri​)∣)tanh(ri​)​+1/2将数值约束在[0,1]之间,最大值是相对于整个层的权重而言的。然后通过:

quantizek=12k−1round((2k−1)ri)quantize_k=\frac{1}{2^k-1}round((2^k-1)r_i)quantizek​=2k−11​round((2k−1)ri​)将浮点数转换位kkk位定点数,范围在[0,1][0,1][0,1],最后通过映射变换将ror_oro​约束到[−1,1][-1,1][−1,1]。

需要注意的是,当k=1时,等式9不同于等式7,它提供了一个不同的二值化权重的方法,然而,论文发现在实验中这种区别不重要。

2.4 梯度的低比特量化

本文已经证明了确定性量化可以产生低位宽的权重和激活值。然后,为了将梯度也量化到低位宽,保持梯度的无界是非常重要的,同时梯度应该比激活值的范围更广。回顾等式(9),我们通过可微分的非线性激活函数将激活值约束到了[0,1][0,1][0,1],然而,这种构造不存在渐变,因此我们对梯度设计了如下的kkk位量化方式:

这里的dr=∂c∂rdr=\frac{\partial c}{\partial r}dr=∂r∂c​是一些层的输出rrr对损失函数ccc的导数,最大值是对梯度张量所有维度(除了batchsizebatch sizebatchsize)的统计,然后在梯度上用来放缩将结果映射到[0,1][0,1][0,1]之间,然后在量化之后又放缩回去。

然后,为了进一步补偿梯度量化引入的潜在偏差,我们引入了额外的函数N(k)=σ2k−1N(k)=\frac{\sigma}{2^k-1}N(k)=2k−1σ​,这里

因此,噪声可能具有和量化误差相同的幅值。论文发现,人工噪声对性能的影响很大,最后,论文做kkk位梯度量化的表达式如下:


梯度的量化仅仅在反向传播时完成,因此每一个卷积层的输出上的STE是:

2.5 DoReFa-Net训练算法

论文给出了DoReFa-Net的训练算法,如Algorithm1所示。假设网络具有前馈线性拓扑,像BN层、池化层这样的细节在这里不详细展开。要注意的是,所有昂贵的操作如forwardbackward_inputbackward_weight(无论是卷积层还是全连接层),都是在低Bit上做的。通过构造,在这些低位宽数字和定点整数之间总是存在仿射映射的,因此,所有昂贵的操作都可以通过定点整数之间的点积等式(3)来加速。

2.6 小结

最终,我们获得了DoreFa-Net的算法,这里对第一层和最后一层不做量化,因为输入层对图像任务来说通常是8bit的数据,做低比特量化会对精度造成很大的影响,输出层一般是一些One-Hot向量,因此输出层也要保持原样。DoreFa-Net分别对SVHN和ImageNet做了实验,其准确率明显高于二值化网络和三值化网络。

3. 代码实战

仍然以666DZY666博主分享的Pytorch实现为例子来介绍一下DoreFa-Net的代码实现,代码地址为:

https://github.com/666DZY666/model-compression

首先我们看一下使用DoreFa-Net算法搭建的网络,代码目录为del-compression/blob/master/quantization/WqAq/dorefa/models/nin.py

# 注意这个代码中对卷积层和全连接层实现了DoreFa-Net的量化方法
import torch
import torch.nn as nn
import torch.nn.functional as F
from .util_wqaq import Conv2d_Qclass DorefaConv2d(nn.Module):def __init__(self, input_channels, output_channels,kernel_size=-1, stride=-1, padding=-1, groups=1, last_relu=0, abits=8, wbits=8, first_layer=0):super(DorefaConv2d, self).__init__()self.last_relu = last_reluself.first_layer = first_layerself.q_conv = Conv2d_Q(input_channels, output_channels,kernel_size=kernel_size, stride=stride, padding=padding, groups=groups, a_bits=abits, w_bits=wbits, first_layer=first_layer)# BN和激活都保留了常规方式self.bn = nn.BatchNorm2d(output_channels)self.relu = nn.ReLU(inplace=True)def forward(self, x):# 第一个卷积层后面不接relu激活函数if not self.first_layer:x = self.relu(x)x = self.q_conv(x)x = self.bn(x)# 最后一层卷积层后要使用relu激活函数if self.last_relu:x = self.relu(x)return xclass Net(nn.Module):def __init__(self, cfg = None, abits=8, wbits=8):super(Net, self).__init__()if cfg is None:# 网络层通道数cfg = [192, 160, 96, 192, 192, 192, 192, 192]# model - A/W全量化(除输入、输出外)   self.dorefa = nn.Sequential(DorefaConv2d(3, cfg[0], kernel_size=5, stride=1, padding=2, abits=abits, wbits=wbits, first_layer=1),DorefaConv2d(cfg[0], cfg[1], kernel_size=1, stride=1, padding=0, abits=abits, wbits=wbits),DorefaConv2d(cfg[1], cfg[2], kernel_size=1, stride=1, padding=0, abits=abits, wbits=wbits),nn.MaxPool2d(kernel_size=3, stride=2, padding=1),DorefaConv2d(cfg[2], cfg[3], kernel_size=5, stride=1, padding=2, abits=abits, wbits=wbits),DorefaConv2d(cfg[3], cfg[4], kernel_size=1, stride=1, padding=0, abits=abits, wbits=wbits),DorefaConv2d(cfg[4], cfg[5], kernel_size=1, stride=1, padding=0, abits=abits, wbits=wbits),nn.MaxPool2d(kernel_size=3, stride=2, padding=1),DorefaConv2d(cfg[5], cfg[6], kernel_size=3, stride=1, padding=1, abits=abits, wbits=wbits),DorefaConv2d(cfg[6], cfg[7], kernel_size=1, stride=1, padding=0, abits=abits, wbits=wbits),DorefaConv2d(cfg[7], 10, kernel_size=1, stride=1, padding=0, last_relu=1, abits=abits, wbits=wbits),nn.AvgPool2d(kernel_size=8, stride=1, padding=0),)def forward(self, x):x = self.dorefa(x)x = x.view(x.size(0), -1)return x

可以看到这个代码的核心是调用了DorefaConv2d这个DoreFa量化卷积层,这个实现在https://github.com/666DZY666/model-compression/blob/master/quantization/WqAq/dorefa/models/util_wqaq.py中,即:

# ********************* 量化卷积(同时量化A/W,并做卷积) ***********************
class Conv2d_Q(nn.Conv2d):def __init__(self,in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True,a_bits=8,w_bits=8,first_layer=0):super().__init__(in_channels=in_channels,out_channels=out_channels,kernel_size=kernel_size,stride=stride,padding=padding,dilation=dilation,groups=groups,bias=bias)# 实例化调用A和W量化器self.activation_quantizer = activation_quantize(a_bits=a_bits)self.weight_quantizer = weight_quantize(w_bits=w_bits)    self.first_layer = first_layerdef forward(self, input):# 量化A和Wif not self.first_layer:input = self.activation_quantizer(input)q_input = inputq_weight = self.weight_quantizer(self.weight) # 量化卷积output = F.conv2d(input=q_input,weight=q_weight,bias=self.bias,stride=self.stride,padding=self.padding,dilation=self.dilation,groups=self.groups)return output

对于权重的量化代码实现如下,对应了公式9和公式10:

# ********************* W(模型参数)量化 ***********************
class weight_quantize(nn.Module):def __init__(self, w_bits):super().__init__()self.w_bits = w_bitsdef round(self, input):output = Round.apply(input)return outputdef forward(self, input):if self.w_bits == 32:output = inputelif self.w_bits == 1:print('!Binary quantization is not supported !')assert self.w_bits != 1                      else:# 按照公式9和10计算output = torch.tanh(input)output = output / 2 / torch.max(torch.abs(output)) + 0.5  #归一化-[0,1]scale = float(2 ** self.w_bits - 1)output = output * scaleoutput = self.round(output)output = output / scaleoutput = 2 * output - 1return output

其中round函数的实现如下,可以看到继承了torch.autograd.Function,使得这个round操作可以反向传播:

class Round(Function):@staticmethoddef forward(self, input):output = torch.round(input)return output@staticmethoddef backward(self, grad_output):grad_input = grad_output.clone()return grad_input

然后对于激活值的量化,论文中的介绍如下图所示:

代码实现如下:

# ********************* A(特征)量化 ***********************
class activation_quantize(nn.Module):def __init__(self, a_bits):super().__init__()self.a_bits = a_bitsdef round(self, input):output = Round.apply(input)return outputdef forward(self, input):if self.a_bits == 32:output = inputelif self.a_bits == 1:print('!Binary quantization is not supported !')assert self.a_bits != 1else:output = torch.clamp(input * 0.1, 0, 1)  # 特征A截断前先进行缩放(* 0.1),以减小截断误差scale = float(2 ** self.a_bits - 1)output = output * scaleoutput = self.round(output)output = output / scalereturn output

注意一下这里有个Trick,即 特征A截断前先进行缩放(* 0.1),以减小截断误差。

代码中还实现了全连接层量化:

# ********************* 量化全连接(同时量化A/W,并做全连接) ***********************
class Linear_Q(nn.Linear):def __init__(self, in_features, out_features, bias=True, a_bits=2, w_bits=2):super().__init__(in_features=in_features, out_features=out_features, bias=bias)self.activation_quantizer = activation_quantize(a_bits=a_bits)self.weight_quantizer = weight_quantize(w_bits=w_bits) def forward(self, input):# 量化A和Wq_input = self.activation_quantizer(input)q_weight = self.weight_quantizer(self.weight) # 量化全连接output = F.linear(input=q_input, weight=q_weight, bias=self.bias)return output

4. 将DoreFa-Net应用到YOLOV3上

上次介绍的YOLOV3剪枝方法汇总 文章中还剩下一个量化方法当时没有提到,实际上它的量化方法就是DoreFa-Net的量化方法,所以我们来看一下量化效果:


但是需要注意的是,在框架下量化训练过程都还是在float32精度下的表达,只是尺度scale到量化的尺度上了,能够验证量化的有效性。但如果要实际部署,可以看下我们发布的深度学习量化技术科普 ,并且后续我也会更新实际工程中的做量化加速的一些分享。

5. 总结

这篇文章,从算法原理和代码实现方面剖析了DoreFa-Net,并验证了DoreFaNet的有效性,并且可以看到通过这种方法INT8的掉点情况完全可以接受。


欢迎关注GiantPandaCV, 在这里你将看到独家的深度学习分享,坚持原创,每天分享我们学习到的新鲜知识。( • ̀ω•́ )✧

有对文章相关的问题,或者想要加入交流群,欢迎添加BBuf微信:

为了方便读者获取资料以及我们公众号的作者发布一些Github工程的更新,我们成立了一个QQ群,二维码如下,感兴趣可以加入。

低比特量化之DoreFa-Net理论与实践相关推荐

  1. 神经网络低比特量化——LSQ

    神经网络低比特量化--LSQ 摘要 方法 量化计算公式 STEP SIZE GRADIENT STEP SIZE GRADIENT SCALE 直通估计器 实验结果 Weight Decay Imag ...

  2. 全网最全-网络模型低比特量化

    CSDN的公式是在是太蛋疼了,如果要看公式的可以上知乎上面看原文 神经网络低比特量化中训练和推理是如何实现的? - ZOMI酱的回答- 知乎 https://www.zhihu.com/questio ...

  3. 神经网络低比特量化——DSQ

    神经网络低比特量化--DSQ 摘要 简介 问题描述 方法 量化表示 二值网络量化表示 多位均匀量化表示 量化函数 渐进函数 可微软量化(DSQ)函数 实验结果 消融实验 二值量化消融实验 均匀量化消融 ...

  4. LSQ 的低比特量化之路

    LSQ 是一种有效的低比特量化算法 对于深度神经网络,模型的权重一般都用浮点数表示,如 32-bit 浮点数(float32).推理的时候,如果也采用这种数值类型,会消耗较大的内存空间和计算资源.在不 ...

  5. 基于可训练Step-size的低比特量化——LSQ: Learned Step-size Quantization

    Paper地址:https://arxiv.org/abs/1902.08153 GitHub地址 (PyTorch):GitHub - zhutmost/lsq-net: Unofficial im ...

  6. 神经网络压缩之低比特量化的优劣分析

    优势 减小模型尺寸(减少内存占用):如8位整型量化可减少75%的模型大小,更小的模型大小意味着不需要更多的内存 加快推理速度: 1)8 位的访问次数要比 32 位多,在读取 8 位整数时只需要 32 ...

  7. 【视频课】模型优化拆分!分别学习模型剪枝与模型量化理论与实践

    前言 好的模型结构是深度学习成功的关键因素之一,不仅是非常重要的学术研究方向,在工业界实践中也是模型是否能上线的关键.对各类底层深度学习模型设计和优化技术理解的深度是决定我们能否在深度学习项目中游刃有 ...

  8. 【总结】最好的CV学习小组,超200个课时+10个方向+30个项目,从理论到实践全部系统掌握...

    2022年有三AI-CV夏季划已经正式发布,有三AI-CV夏季划是我们最系统最重要的深度学习和计算机视觉学习计划小组,目标是在新手入门的基础之上,彻底掌握好CV的重要方向,同时提升模型设计与优化的工程 ...

  9. HAWQ-V2:基于Hessian迹的混合比特量化策略

    HAWQ-V2:基于Hessian迹的混合比特量化策略 摘要 方法 自动化的位宽选择 Hutchinson快速求解Hessian迹 敏感度指标分析 激活的混合精度量化 实验结果 ResNet50 on ...

最新文章

  1. JWT的使用及登录账号
  2. 9、ctemplate文档,简记(2)
  3. 第二阶段个人博客总结8
  4. 垃圾邮件分类 python_在python中创建SMS垃圾邮件分类器
  5. C语言怎么输出百分号%
  6. php写不了php,php写入内存不足怎么办
  7. WordPress多用途电子商务博客新闻主题betheme 21.5.6版本
  8. 的pcie带宽_新品推荐:乐扩PCIe四路SDI高清采集卡
  9. 基于maven创建一个javaweb项目
  10. javascript-复制
  11. Java枚举类型的用法
  12. latex 设置pdf的页边距
  13. 笔记+R︱风控模型中变量粗筛(随机森林party包)+细筛(woe包)
  14. mqtt客户端工具_如何在 Rust 中使用 MQTT
  15. SQL中 UNION 和 UNION ALL 操作符小结
  16. Docker教程:docker远程repository和自建本地registry
  17. GIT的安装与gitee基础使用
  18. 51单片机之共阳极静态数码管
  19. is_file php 绕过,文件上传之绕过
  20. Java中for循环的应用

热门文章

  1. Construct2 第一次制作的小游戏
  2. 桌面的html文件怎么打开方式,怎么将浏览器打开的页面保存到桌面
  3. MediaPlayer简单使用
  4. 重装系统后win10激活
  5. javascript按钮的三级联动
  6. MQTT中topic匹配规则基础
  7. android 授予root权限,关于android手机获得ROOT权限问题
  8. oracle直接将日期转为月份,如何使用Oracle将日期转换为周,月,季度,半年,年...
  9. 乘风破浪,一往无前 – Smartbi和你一起走过的2020年
  10. 江门python培训机构