给神经网络做减法(文字版)

点击这里收听本期节目音频版

编辑: 肖怡婷

本期嘉宾: 林政豪 (G)

主播: 舒晏 (S), 阿拉法特 (A)

A
这几年人工智能的发展,深度学习几乎是抢了所有的风头。很多大公司的核心技术都开始转向深度学习,比如Google的在线翻译和街景识别,还有很多主要电商平台上面的以图搜图的功能。
S
是的,这些任务的精度确实得到了很大的提高,但是精度提高的代价之一就是深度学习里面的神经网络是越来越复杂了,并不只是指深度越来越深,其实参数量也是非常的庞大,比如大家前几年常用的VGG深度卷积网络,参数量就达到了几百个million。所以即使是用预先练好的模型做inference,计算量都非常非常大。这也就限制了深度学习在很多小型设备上的应用,毕竟Nvidia现在还没有做出一款可以放进手机,手表里面的高性能显卡。所以在这一两年,确实有更多的工作来研究如何减少深度神经网络里面的参数数量。但是我只了解软件方面,比如说去年有一篇影响非常大的文章,是做残差网络(ResNet)。它就是通过改变层与层之间连接的方式来用更少的参数加深网络的深度,并且达到了更好的效果。以后有机会我们可以把ResNet和大家详细地讨论。
A
虽然现有的显卡并不能够适应这些非常小型的应用场景,像嵌入式的这些应用场景,但是我们可以从另外一个方向想,怎么样能够让神经网络去适应一些小型的,比较简单的硬件。我们今天请到的嘉宾要跟我们讨论的话题,就和这个主题有关。

 01:53 
A
大家好,欢迎收听德塔赛。我是阿拉法特。
S
我是舒晏。
A
今天我们的来宾是林政豪。
G
谢谢两位主播。大家好,我叫林政豪。很高兴可以参加这个节目,我们组的人都是在做一些嵌入式系统的优化或者应用。我的研究方向是简化深度学习的软、硬件需求,并且以嵌入式硬件来实现神经网络。目前我主要专注在嵌入式硬件的图像识别。听众可能不太熟悉硬件设计,所以我今天会尽可能把重点放在软件的观念还有算法上面。

 02:37 
S
我知道的大部分深度学习网络的改进都是基于一些连接方法上的改进。那除了刚才我们提到的ResNet以外,还有一些文章是通过剪枝层与层之间的连接来达到简化的目的。那么豪哥,在嵌入式系统中的深度学习算法的这些优化,和刚才我提到的这些有什么区别呢?
G
剪枝是一个比较直观的做法,它就是把相对无足轻重的神经连接剪掉,减少记忆体用量还有运算量。但是实际上我们如果这么做的时候,因为这些神经连接都是有一个权重在上面,又因为神经网络其实是可以等效成矩阵的相乘,所以我们一旦移除掉了某些权重,权重矩阵可能大部分就会变成零,就会在记忆体的使用上相当的没有效率。所以你其实是破坏了它的规律性,把事情弄得更糟,因为我得到了一个稀疏矩阵,让计算变得更困难。如果我们用硬件去实现它的时候,其实是根本没有帮助的,就是你还是需要用那么大的记忆体。所以斯坦福大学的Song Han这两年就提出了蛮多系统的优化方式。他透过记忆体调用的方式把一些空白的权重给填满,然后具体地用硬件来实现出来,得到一些两次数据,结果确实有效。这是一个可行的方向。
A
我不太了解硬件,但是常规我们在软件上处理这些非常稀疏的矩阵的时候,我们可能会选择压缩这个矩阵,把它做成一个行压缩或者列压缩的形式。硬件的研究者会这么做吗?
G
是,其实Song Han他做的事情就有点像是我们用稀疏矩阵的行压缩列压缩,只是我们移除的权重更没有规律性。训练完或者训练当中你就把它做剪枝,在哪一行哪一列权重被移掉是非常没有规律的。
A
所以硬件上就很难维护这个稀疏矩阵。
G
对。

 05:17 
S
好,那么这是刚才我提到的第一种方法,做层与层之间的剪枝。那ResNet我记得它的文章中说他们的参数量也是在几十倍的减少,这是适合在硬件上面做优化的一种方式吗?
G
是的,ResNet的做法就是改变层与层的连接方式。然后它与前面提到的剪枝的做法有某个程度上的类似。原本是你training的过程中移掉矩阵的权,现在变成你在training的过程中就强制这个权重矩阵它必须得长的是什么形状,还有它的连接必须要是一种特殊的连接方式,它不单是接到下层神经网络,它还接到下下层。那原本需要学习的卷积和,现在就成了余项。如果再配合上适当的神经网络的深度与结构,目前已经证实可以得到比较好的结果,还有它的效能也被提高了,就是用比较少的记忆体得到同样的精准度,或者是用相同的记忆体得到更高的精准度。它其实是有坏处的,因为这么做其实是把整个training变得复杂化了,因为你必须要在training的过程中就定义好这些权重矩阵的形状,而且他们使用了很多种形状。传统神经网络的矩阵具有单一性,要么就全部都是5×5,要么就3×3,但现在有1×3或者是1×1或者是1×5,1×7都混杂,所以这样会增加他们在training上面,就是programming的复杂度。

 07:27 
A
豪哥,你的研究是更多的关注二元化神经网络。你可以给我们介绍一下二元化神经网络和我们刚刚提到的两种简化神经网络的方式相比有什么特点吗?
G
可以的,除了前面提到了两个方向之外,在2016年Bengio的团队有提出了BNN二元化神经网络。他的做法就是站在硬件的角度来简化神经网络。他的目标就是不要去破坏矩阵运算的规律性,也可以维持传统神经网络向前传播的结构,用比较少的记忆体与比较简单的计算来达到同样的精准度,原因就是它里面所传递的那些变数现在都成了一个bit的0或1。
A
他是把整个网络全部变成了一个binary二元化,还是它只变了一部分?
G
站在硬件的角度来讲,因为我们会有很多的memory access,我们也需要去储存training完之后的weight,这会占用大量的记忆体储存空间,所以他的目标是把这些权重,还有这些中间产生的feature map(特征图)给变成binary。也就是说,权重原本比方说是2.3,那它现在就存为1,如果是-6,它就存为0。特征图原本是灰阶的,现在就变成了黑白,所以它是把这些重要的矩阵变成二元化。但事实上中间还是有一些暂存的变数,它是要维持高精准度的。
A
那这里BNN这个binary指的是把网络中的哪些部分变为了binary呢?
G
BNN的目标就是把权重以及层与层之间的特征图都变成二元化,都变成只用一个bit来储存一个位置或者储存一个pixel。如果我们抓出中间的特征图来看,你就会看到原本灰阶的feature map上面灰阶的图现在变成了黑白。显而易见,这么做就可以把记忆体缩小32倍,因为原本我们如果用floating point的话,32个bit。现在只需要一个bit,就是每一个变数或者一个pixel现在都变成了1/32,就是一个位元而已。如果我们实做成硬件加速器的时候,我们会有很多次的记忆体调用,不管是内部还是外部,那如果使用现场可编程逻辑阵列FPGA来做它的话,在传统的内神经网络上面必须要读取一个权重,意味着读取32个位元。可是用BNN来做的时候,如果你读取32位元,意思就是读取32个权重。就是有效的可以使用这个记忆体的频宽,如果用,即专用集成电路ASIC来实做硬件的时候就更直观了,原本读取一个权重你需要一个bus,这个bus的宽度为32根线,但是如果我们实做的是BNN的话,你只要拉一根线传递0或1,这会大幅的简化硬件实现上的拥塞问题。所以BNN其实是一个为了硬体而生的一个演算法。
A
不只是为了缩小网络的体积,而且也减少了像这种硬件电路中的排线的数量。像原本需要32根排线传递的部分,现在只要一根排线就可以传过来。
G
对的。除了储存还有memory access这个好处之外,另外一个重点就是因为现在的权重,还有每个pixel都变成了一个bit,所以在一个bit与一个bit中间的乘法再也不需要使用乘法器,只需要使用一颗Exclusive-NOR逻辑闸就可以等效以位元的乘法。32位元的乘法器用硬件实现需要超过200颗逻辑闸,可是BNN用一颗Exclusive-NOR就可以搞定。如果神经网络里面所有乘法全部都能被简化200倍的话,就可以省下好多的晶元面积。讲得更明白一点,就是省下很多的钱,因为我们组毕竟还是做硬件的,所以什么都要考虑钱了。

 13:12 
A
如果听众比较了解硬件方面的一些知识,比如说你本科上过一些VHDL方面的课,你应该学到过在运算单元中如果想实现加法器或者乘法器,实际上是要用很多逻辑或、逻辑与和逻辑异或的单元去把它拼接成一个等效的实现加法或者乘法效果的单元。但是用BNN这样的binary的表示显然我们就只需要一个,比如说抑或单元去实现加法。这样听起来好处是非常的多,但是让我非常困惑的地方是我们损失了非常多的信息。网络是怎么样用一位去存储本来需要很多位去存储这些信息的呢?
G
答案就藏在Paul Brady的《青蛙》这幅图里面。你可以上网搜寻Paul Brady Frog,搜寻的结果通常都会给你一张远看像是灰阶的青蛙的图。如果你用高清的荧幕把它打开一看,就会了解到原来这张图是由空白、还有许多的黑点组成的。如果你仔细的看局部的一群黑点,你没有办法理解Paul想要表达的是什么,可是如果你站远一点,你来看整幅大图的时候,你的眼睛就会为你做信息的积分,告诉你的大脑这个图上有一些区块,有一些线条,那这些东西组成了一只青蛙。
A
这个青蛙的图它其实是黑白,它是Binary的表示,但是如果远看它会感觉是灰阶的形式。我们会把这个图放在我们的推送文章里面,如果大家感兴趣可以直接看一下我们推送的文章。
G
所以BNN成功的关键,事实上也是这样。Bengio的团队加大了神经网络的结构,让它可以容下更多、更大的中间表示的Feature Map的特征图,也使用了2015年一个谷歌团队提出的批量规范化(Batch Normalization)的方法来加强了神经元当中的信息积分。

 15:33 
S
那么在做了binarization之后也可以用反向传播来训练网络吗?比如说在传统的结构中,我们是不能用step function来做activation function的,因为它不可导。这样error就传不回来,那在BNN中也有这个问题吗?
G
对的。BNN的activation function(激活函数)就是使用step function,这个step function只会输出0或1,当今天输入结果小于一个门槛时,它就输出零;大于这个门槛,它就输出1。这么做会使得这个激活函数变成不可微分,不可导。可是Bengio的团队把这个问题给解决了,他们依然可以使用反向传播来训练BNN。尽管在向前传播的时候,他们使用了这个step function二元化参数作为非线性激活函数,但是他们在backpropagate的部分采用了Hinton在2012年提出的一个叫做直接估算值(straight through estimator)的方式来解决这个问题。在处理二元化函数求导的时候,只有当输入图的像素强度的绝对值小于1的时候,才会反向传递误差。这个做法背后的原理是将step function,将不可微的这个函数再度近似为硬双曲正切函数Hard hyperbolic tangent function,变成由三段线段组成。听众可能会觉得这非常暴力,几乎是胡说八道,乱作一通,但是事实上证明有效,他们可以train出一个performance accuracy(精准度)很高的BNN。
A
Hyperbolic tangent function什么可以想象,但这个Hard是什么意思?Hard这个前缀我觉得并不常见。
G
这是我们在硬体实现上面实现各种超越函数的时候常用的一个形容词Hard。它其实很简单,就是把这个原本圆滑的曲线变硬,也就是说,现在它变成由不同段数,不同数量的的线段组成。原本是圆滑的,现在Bengio的团队把它变成有两个转折点这样的双曲正切函数。
A
大概就是两段直线,然后用一个斜线把它们连起来。
G
对的。
A
那对于这些不能微分的部分,他们怎么处理呢?
G
始终是有这些转折点不能微分。如果输入落在转折点上的时候,它就定义这个转折点的微分子是多少,比方说它可以定义这个微分子是0,或这个微分子是1。
S
其实我一直都没有搞清楚,像在ReLU(Rectified Linear Unit)这样的activation function中,它在0的部分也是一个转折点,对吗?
G
对的,只有这样子程序上面才能跑。

 19:04 
A
在这个二元化过程中,算法显然是损失了非常多的精度,后面是要怎么处理,怎么补偿这个损失的精度呢?
G
我们前面其实有稍微提到过两个关键技术。第一,我们可以加深或加广这个神经网络;第二,就是强化信息的积分。第一个观念,我想其实非常多人都已经有这个经验了,如果我们的神经网络的学习效果不好,我们通常在推升一两层或者多加一点卷积和,效果就会上来了。我们需要注意的是今天这边提到的第二个观念,使用批量规范化Batch Normalization来强化信息积分这个技巧。那Batch Normalization其实对小批量的图像做一个特殊的规范化。
S
那提到这个Batch Normalization,我对它的理解是这个idea更像是给数据做预处理,比如说我们在做预处理的时候会把这个数据的mean减掉,然后除以它的variance,这样可以得到一个unit variance。为了保证神经网络在每一层的输入的时候都是zero mean unit variance,Batch Normalization就设计了这样的一个过程,只是它是参数化的,也就是说在每一层都对这个Batch做一次这种预处理,我的理解对吗?
G
对的。我们在处理数据的时候,如果做Normalization,数据通常都可以变得更有效,它可以跟其他的数据做比较使用,这里其实也不例外。批量规范化不同于单一一张图的规范化,单一一张图的规范化是在二维上面将它做Normalization;批量规范化是做一个不同方向上,就是我们先把一个小批量的图叠在一起,在同一个像素位置上面的所有的像素强度来做规范化,就是说你今天叠在一起的时候,图与图之间非常可能是没有关联的:前一张图可能是一条狗,后一张图可能是一辆车。那批量规范化就把比方说坐标(2,3)的这两张图的这两颗像素做一个规范化,那不管是狗还是车,其实它的图都会起变化,虽然不至于把狗变成猫,或者是汽车变成飞机,但是在原本的图不同的位置,它们的像素强度都会被线性缩放,还有位移成新的值。那如果我们把它调出来看的话,我们会看到好像是加入了杂讯,有些地方变得模糊不清了。但是以神经元的立场来讲,每个神经元处理同一个坐标上面的像素强度现在被规范了,这个神经元不用涵盖太正或太负的数值,也不用担心现在输入进来的这个位置上面的数值都是偏大,就是严重位移,难以使得我们接下来的这一个非线性激活函数的转折区去做区分,所以批量规范化就会使得神经元更容易区别图与图之间的差异。
S
其实我之前是有看到过很多在传统网络中的Batch Normalization的应用,大部分情况下是有作用的,可以加速训练,对精度有一些提高,但是包括他自己的文章中也从来没有给出过一个很大的margin的提高。所以如果豪哥告诉我们说这个Batch Normalization在binary的情况下是有很大的作用的话,我会非常惊讶。
G
BNN里面不能没有Batch Normalization。以MNIST数据集来举例的话,今天我们如果移除了批量规范,那么training的结果最高只能达到接近15%的精准度,非常的差。那因为你想想看MNIST有10个class,10种label,你今天如果random地猜应该也是10%吧。但是有了批量规范化之后,只要4层的BNN,它就能够超越99.2%的精准度。就是说,BNN是不能没有Batch Normalization,这是非常有趣的一件事情。如果我们仔细去看批量规范化到底是怎么帮助BNN的,那我们就会发现,规范化之后的信息全部都变成了这些特征图,这些feature map的每个pixel的强度都变成了介于正负一之间的小数。批量规范化又另外引入了两个参数,叫做缩放与位移,一组参数叫做gamma,还有beta。这两个参数在BNN里面依然保留它们的小数点,它们是可以被训练的,训练的过程中它们会变。每一个神经元都会有一对这样的可训练的参数。可能有听众会觉得被忽悠了,为什么我们二元神经网络里面竟然还有小数?没错,但是妙就妙在Bengio的团队,他改变了一般人使用批量规范化的时机点。如果我们仔细去看2015年批量规范化的文章,就可以发现谷歌团队是把这个批量规范化设计给激活去使用的,但是Bengio的团队改变了顺序,他把批量规范化移动到了激活函数之前,先规范化再做激活。这么做会让神经元最后的产物是一位的二元数字,中间我们其实做了累加,做计数的过程当中,本来你就必须要记住现在这个神经元它接收了多少个0,多少个1。比方说有3个0、2个1,那这个3就必须要使用多于一个Bit来储存。
A
也就是说神经原激活的次数,为了记录这个量,我们需要有一个比较高精度的变量在这里。
G
对的。也就是说这个数字会是一个整数,而且可能是3、5或11这样一个整数。储存这个整数,你就必须得使用多于一个Bit的记忆体。所以当Bengio今天把批量规范化移到了这个累加的结果之后,移到了这个激活函数之前,就介乎累加的结果还有激活函数中间的时候,它其实不会对硬体造成负担。因为假设原本你需要用三个bit来储存这个累加的结果,你一开始把这三个bit当成整数在用,但是你做了Batch Normalization的除法与减法之后,你需要去储存有小数点的变量,可是这些有小数点的变量事实上可以再存回这三个bit。我们今天这边可以举一个简单的例子来讲,如果有一个二元数,它是11,那么其实我们可以把它视为整数的3,那也可以把它视为十进位的小数1.5。
A
这是在一个硬件层面上,同一个硬件部分,那几个Bit weight怎么解释完全取决于设计者,但是硬件是没有变的,还是那几个位置。
G
对的,所以不会对硬件造成负担,只要你的查表这个动作看得懂这个东西,能够做配合就可以了。
A
那这是一个很聪明的设计了,它就是在进入激活函数之前把这几个Bit位为当做浮点数来用,然后再经过了这个activation function之后,它就开始把这几个Bit位当整数变量来用。
G
简单来说,批量规范化为BNN带回了失去的信息量,然后也大幅的提高了网络的精准度。神经元内部需要多一道这样的mapping,多一道这样查表。那神经元与神经元之间现在仍然只需要一个位元,所以听众不需要觉得说被骗,因为这个东西才是我们的目标。我们想要去简化记忆体存取这个方向,我们想要得到这个好处。那Bengio的团队,他们确实是达到了这个效果。

 29:03 
S
那豪哥,BNN的效果怎么样呢?它有没有在一些Benchmark数据集上面的结果呢?
G
有的。BNN是一种极端量子化的结果,它的野心其实很大。即使使用了批量规范化帮助它提升精准度,但是这样一来一往,仍然失去了不少的信息。目前BNN在比较小的数据集,比方说MNIST前面提到的,还有CIFAR-10,CIFAR-100,还有SVHN都达到了跟传统神经网络极为接近的精准度,它们的差异都在0.5%以下,算是非常好的。那Bengio的团队发展的BNN的理论之后其实并没有实做,并没有实现在硬件上面。我们的小组,还有我的工作事实上就是把它做到FPGA上面。我们有在FPGA期刊上面发表了文章,我们是第一个团队做出这样子,而且不丢失精准度。
A
在硬件上实现了 BNN。
G
对的,那它中间是有难度的,并不是说你就把C++、C的code,Python的code改写成VHDL或者Verilog就好了,不是这么的直觉的。因为这些VHDL library,它其实不是设计给BitWit的操作,这些次自主的操作使用,我们必须要有效地利用记忆体才可以得到别人的理论的这些记忆体的缩小32倍的好处。但是偏偏就是这些library,它其实并不能够去支援,就是比方说你可以叫它一次处理八个bit,但你不能叫它一次只搬一个bit。所以我们的组就是除了trival的部分,把C转成Verilog之外,我们还要设计几个特殊的component去搬这些bit做BitWit的操作。我们将它称之为次自主的记忆体优化问题。就是我们克服了一些难度,并且得到实际量测数据,在CIFAR-10上面,我们不损失精准度,就是跟Bengio他们在gpu上面跑出来一样是98.7%的精准度,我们整体的记忆体只使用小于2 megabyte,是1.775 megabyte。比较令人惊讶的是,我们的效能每瓦特每秒钟我们可以处理33.5张图,这个效率是gpu的五倍以上,就是我们用便宜的硬体可以在效率上远远地打败gpu。
A
这就是精度是完全一样的,但是在效能上是远远高于的。
S
不过我今天是第一次听说效率是用每瓦特每秒钟来衡量,在我们计算机领域。
G
对,因为你们一般是属于使用high-level,你其实根本不去管gpu到底用了多少,多少能量,多少电,你根本不在乎,可是我们做这个嵌入式系统的,它的背后是support你这个电路的东西,可能是一颗电池,它会耗尽的。所以我们必须斤斤计较,你每瓦特每秒钟到底能做多事。
A
其实包括这种更多做理论方面研究的人,也可以引用这种衡量效率的方法,虽然我们不用瓦特为单位,但是毕竟大家现在对AWS,你可以说你每美元做了多少预算,还是非常值得参考的。毕竟在这种大家都开始纷纷砸钱来做运算的背景下,这种考虑还是非常值得的。

 33:23 
S
是的,那像在大一些的数据集上的表现怎么样呢?
G
如果我们用当今最火最大(可能不是最大,因为有据说已经有人提出更大的了)的数据集,就是ImageNet,它已经算是大的了,在这上面的效果BNN确实是不好。如果我们看Top 5的精准度,BNN只能达到69.1%;Top 1就更低了,只能达到47.1%。它是根本跟现在最好的结果Top 5: 93.7%,还有Top 1: 77%,叫做DenseNet-201这一种神经网络来比的话是远远不如的。但是我们再回过头来想,为什么Bengio他们要创造BNN呢?他们是为了硬体,他们是aim small,他们的目标是把神经网络拿来解比较小的问题,然后带进生活中。那ImageNet,它其实野心非常大,里面的东西包山包海。那我们今天如果只是单纯的想要识别手写数字是1234567890哪一个的时候,我们没有必要用DenseNet来识别,这是拿大炮打小鸟,我们就用比较小的BNN就可以了,而且小的BNN我们只用一颗小小的FPGA晶片就可以搞定了。
A
那今天林政豪带我们了解了大家比较少关注的一个领域,怎么样在低功耗的环境里面去实现神经网络,非常感谢豪哥今天帮我们录成了这期节目。
G
我也很高兴,今天可以参与到这里,那希望我的这些介绍可以引起听众的兴趣,然后也增加听众在这方面深度学习以及硬件加速器的了解。谢谢大家!
A
那如果大家想进一步了解林政豪的工作可以在我们的网站主页还有微信公众号推送的文章里找到豪哥的主页,可以看到更多他的文章。
S
如果还没有关注我们微信公众号的朋友,请在微信平台上面搜索德塔赛三个字的中文全拼,并且关注我们的节目。
A
那么我们今天的节目就到这里了。我们下期节目见。