想把自己训练的模型移植到Android上(tensorflow框架),有没什么方法可以对已经训练好的模型进行优化,减少参数量、运算量,让模型跑的快些?
当前人工智能领域的先进技术层出不穷,诸如计算机视觉、自然语言处理、影像生成,深度神经网络等先进技术日新月异。然而,从计算能力、内存或能源消耗角度来看,这些新技术的成本可能令人望而却步,其中某些成本对于大多数硬件资源受限的用户来说,则完全负担不起。所以说,人工智能许多领域将对神经网络进行必要的修剪,在确保其性能的同时降低其运行成本。
这就是神经网络压缩的全部要点,在这一领域,有多种方法来实现神经网络的压缩,如量化、分解蒸馏等等,本文的重点在于阐述神经网络的剪枝。
神经网络剪枝旨在去除性能良好但花费大量资源的多余部分网络。尽管大型神经网络的学习能力有目共睹,但事实上,在训练过程结束后,并不是所有的神经网络全部有用,神经网络剪枝的想法便是在不影响网络性能的情况下去除这些无用的部分。
在这个研究领域,每年均有几十篇,甚至数百篇论文发布,众多的论文揭示了这一想法蕴含的复杂性。通过阅读这些文献,可以快速识别出神经网络的无用部分,并在训练前后去除它们,值得强调的是,并不是所有的修剪都可以提速神经网络,某些工作甚至会事倍功半。
本文在阅读原生神经剪枝论文的基础上,提出了解决神经网络修剪的解决方案,依次回答了三个这一领域中的核心问题:“应该对哪部分进行修剪?”、“如何判断哪些部分可以修剪?”以及“如何在不损害网络的情况下进行修剪?”。综上所述,本文将详细介绍神经网络剪枝的结构、剪枝准则和剪枝方法。
当涉及神经网络的成本时,参数的数目和FLOPS(每秒钟的浮点操作次数)是其中最广泛使用的指标之一。看到网络显示出天文数字的参数(对某些人来说会花费高达数十亿美元的成本),这的确令人生畏。通过直接删除参数,从而直观地减少参数数目,这一方法肯定有效。实际上,这一方法在多个文献中均有提及,修剪参数是文献中提到的应用最为广泛的示例,被视为处理剪枝时的默认框架。
直接修剪参数的方法有诸多优点。首先,它很简单,在参数张量内,将其权重值设为零,便可以实现对参数的修剪。在Pytorch深度学习框架中,可以轻松地访问到网络的所有参数,实现起来非常简单。尽管如此,修剪参数的最大优势是:它们是网络中最小、最基本的元素,因此,只要数量足够多,便可以在不影响性能的情况下,对它们进行大量修剪。这种精细修剪的粒度使得可以在非常精密的模式下实现剪枝,例如,可以对卷积内核内的参数进行修剪。由于剪枝权值根本不受任何约束条件的限制,而且是修剪网络的最好方法,因此将这种方式称为非结构化剪枝。
然而,这种方法的致命缺点是:大多数深度学习框架和硬件无法加速稀疏矩阵的计算,这意味着无论你为参数张量填充多少个零,都不会对实际训练成本产生实质的影响,仅仅是一种直接改变网络架构的方式进行剪枝,而不是对任何框架都放之四海而皆准的方法。
非结构化(左)和结构化(右)剪枝之间的区别:结构化剪枝会同时删除卷积过滤器和内核行,而不仅仅是修剪参数。从而使得中间特征映射的数目更少。
结构化剪枝专注于对更为庞大的结构进行修剪,比如修剪整个神经元,或者,在更现代的深度卷积网络中,直接修剪卷积过滤器。大型网络往往包含许多卷积层,每个层中包含数百或数千个过滤器,可以对卷积层过滤器进行细粒度的修剪。移除这种结构不仅使得深度神经网络的层结构更为稀疏,而且这样做还可以去除过滤器输出的特性映射。
由于减少了参数,这种网络不仅存储起来更为轻便,而且计算量也得以降低,生成更为便捷的中间表示,因此在运行时需要更少的内存。实际上,有时降低带宽比减少参数数目更有益。对于涉及大型图像的任务,如语义分割或对象检测,中间表示可能比网络本身更加消耗内存,出于这些原因,可以将过滤器修剪视为默认的结构化剪枝。
在应用结构化剪枝时,应注意以下几方面:首先,应考虑如何构建卷积层,对于Cin输入通道和Cout输出通道,卷积层由Cout过滤器构成,过滤器分别对Cin内核进行计算;每个过滤器均输出一个特征映射,每个输入通道为一个内核专用。基于这种架构,卷积网络为堆叠的多个卷积层,当对整个过滤器进行剪枝时,可以观察到对每一个过滤器剪枝的过程,随后输出特征映射,这一过程也会导致对后续层内核的修剪。这意味着,当修剪过滤器时,在第一次删除参数之后,实际删除的参数数量是最初认为要删除的参数数量的数倍。
下面来考虑一下这种特殊情况,当一不留神把所有卷积层都修剪掉之后(虽然卷积层被修剪掉了,但神经网络并没有被摧毁,这由神经网络的架构来决定),无法链接到前一层的输出,这也可以是神经网络的一种剪枝方式:修剪掉所有卷积层,实际上等于修剪掉了所有上一层的输出,所以只能连接到其他地方(如残余连接或并行通道)。
在对过滤器剪枝时,首先应该计算出实际参数的确切数量,再根据过滤器在神经网络架构中的分布,修剪相同数量的过滤器,如果实际参数的数量与修剪参数数量不同,结果将不具备可比性。
在进入下一个主题之前,需要提及的是:依然有少数工作集中于剪枝卷积内核、核内架构乃至是特定的参数结构。然而,这些架构需要用特殊的方法来实现(如非结构化剪枝)。此外,另一种方法是对每个核中的某个参数进行修剪,将卷积层转换为“移位层”,这可以通过一次移位操作和一次1×1卷积的组合来实现。
结构化剪枝的缺点:输入和输出维度的改变会引发某些偏差。
在决定了采用何种结构进行剪枝之后,下一个问题便会是:“现在,如何找出保留哪些部分,哪些部分需要修剪?”为了回答这个问题,通过对参数、过滤器或其他特性进行排序基础上,生成一个恰当的剪枝准则。
一个非常直观而又有效的准则是:修剪掉那些权重绝对值最小的参数。事实上,在权重衰减的约束条件下,那些对函数没有显著贡献的参数在训练期间,它们的幅度会不断减小。因此,那些权重比较小的参数便显得多余了。原理很简单,这一准则在当前神经网络的训练中被广泛使用,已成为该领域的主角。
尽管这个准则在非结构化剪枝的实现中显得微不足道,但大家更想知道如何将其应用于结构化剪枝。一种简单的方法是根据过滤器的范数(例如L1或L2)对过滤器进行排序。这种方法实现起来简单粗暴,即将多个参数封装在一起:例如,将卷积过滤器的偏差和批归一化参数封装到一起,将在并行层的过滤器输出融合起来,从而减少通道数目。
其中一种方法是:在无需计算所有参数的组合范数的前提下,为需要修剪的每一层的特征映射插入一个可学习的乘法因子,当它减少为零时,有效地删除负责这个通道的所有参数集,该方法可用于修剪权重幅度较小的参数。
权重大小剪枝准则并非唯一流行的准则,实际上,还有另一个重要准则,即梯度大小剪枝准则,也非常适用。根据上世纪80年代的一些基础理论,通过泰勒分解去消除参数对损失的影响,某些指标:如反向传播的梯度,可提供一个不错的判断方法,来确定在不损害网络的情况下可以修剪掉哪些参数。
在实际项目中,这一准则是这样实现的:首先计算出小批量训练数据的累积梯度,再根据这个梯度和每个参数对应权重之间的乘积进行修剪。
最后一个需要考虑的因素是,所选择的剪枝准则是否适用于网络的所有参数或过滤器,还是为每一层独立计算而设计。虽然神经网络全局剪枝可以生成更优的结果,但它会导致层垮塌。避免这个问题的简单方法是,当所使用的全局剪枝方法无法防止层垮塌时,就采用逐层的局部剪枝。
局部剪枝(左)和全局剪枝(右)的区别:局部剪枝对每一层分别进行剪枝,而全局剪枝同时将其应用于整个网络
在明确了剪枝结构和剪枝准则之后,剩下就是应该使用哪种方法来剪枝一个神经网络。这实际上是最令人困惑的话题,因为每一篇论文都会带来自己的独有的剪枝方法,以至于大家可能会对到底选用什么方法来实现神经网络的剪枝感到迷盲。
在这里,将以此为主题,对目前较为流行的神经网络剪枝方法作一个概述,着重强调训练过程中神经网络稀疏性的演变过程。
训练神经网络的基本框架是:训练、剪枝和微调,涉及1)训练网络2)按照剪枝结构和剪枝准则的要求,将需要修剪的参数设置为0(这些参数之后也无法恢复),3)添加附加的epochs训练网络,将学习率设为最低,使得神经网络有一个从剪枝引起的性能损失中恢复的机会。通常,最后两步可以迭代,每次迭代均加大修剪率。
具体剪枝方法如下:按照权重大小剪枝原则,在剪枝和微调之间进行5次迭代。实验表明,通过迭代可以明显提高训练性能,但代价是要花费额外的算力和训练时间。这个简单的框架是许多神经网络剪枝的基础,可以看作是训练神经网络的默认方法。
有一些方法对上述经典框架做了进一步的修改,在整个训练过程中,由于删除的权重越来越多,加速了迭代过程,从而从迭代的优势中获益,与此同时,删除整个微调过程。在各个epoch中,逐渐将可修剪的过滤器数目减少为0,不阻止神经网络继续学习和更新,以便让它们的权重在修剪后能重新增长,同时在训练中增强稀疏性。
最后,Renda等人的方法指出:在网络被修剪后进行重新再训练。与以最低学习率进行的微调不同,再训练采用与原先相同的学习率,因此称这种剪枝方法为“学习率重绕”。这种剪枝后再一次重新训练的方法,比微调网络的性能更优。
为了加快训练速度,避免微调,防止训练期间或训练后神经网络架构的任何改变,许多工作都集中在训练前的修剪上:斯摩棱斯基在网络初始化时便对网络进行修剪;OBD(Optimal Brain Damage)在对网络初始化剪枝时采用了多种近似,包括一个“极值”近似,即“假设在训练收敛后将执行参数删除”,这种方法并不多见;还有一些研究对这种方法生成掩码的能力提出了保留意见,神经网络随机生成的每一层的掩码具有相似的分布特性。
另一组研究剪枝和初始化之间关系的方法围绕着“彩票假说”展开。这一假设指出,“随机初始化的密集神经网络包含一个初始化的子网,当隔离训练时,它可以在训练相同次数迭代后匹配原始网络的测试精度”。项目实践中,在刚刚初始化时,便使用已经收敛的剪枝掩码。然而,对这一剪枝方法的有效性,还存在着诸多质疑,有些专家认为,利用特定掩码来训练模型的性能甚至可以优于用“胜券”假设下获得的性能。
经典的剪枝框架、彩票假说的学习率调整比较
上述方法均共享相同的底层主题:在稀疏性约束下的训练。这一原则以一系列稀疏训练方法为核心,它包括在训练分布变化的情况下,执行恒定的稀疏率并逐步调整。由Mocanu等人提出,它包括:
1)用一个随机掩码初始化网络,并对网络进行一定比例的修剪
2)在一个epoch内训练这个修剪过的网络
3)修剪一定数量较小的权值,4)再生相同数量的随机权值。
这种情况下,剪枝掩码是随机的,被逐步调整以瞄准最小的导入权值,同时在整个训练过程中强制执行稀疏性。各层或全局的稀疏性级别可以相同。
稀疏训练在训练过程中周期性期切割和增长不同的权重,经过调整后的权重与相关参数的掩码相关
还有一些方法侧重于在训练期间学习掩码修剪,而不是利用特定准则来修剪权重。在这一领域,比较流行的有以下两种方法:1)对网络或层分开进行掩码学习,2)通过辅助参数进行掩码学习。第一种方法中可以采用多种策略:尽可能多的修剪的每层的过滤器,在最大限度地提高精度的前提下,插入基于attention的层或使用强化学习。第二种方法将剪枝视为一个优化问题,它倾向于最小化网络的L0范数和监督损失。
由于L0是不可微的,有些方法主要围绕着使用辅助的惩罚参数来实现,在前向通路中,将辅助的惩罚参数乘以其相应的参数来规避这个问题。还有一些方法采用了一种类似于“二进制连接”的方法,即:在参数选择时,应用随机门的伯努利分布,这些参数p利用“直接估计器”或其他学习方法获取。
有许多方法应用各种惩罚来增加权重,使它们逐步收缩为0,而不是通过手动修剪连接或惩罚辅助参数。实际上,这一概念相当古老,因为权重衰减是衡量权重大小的一个基本标准。除了单独使用重量衰减之外,还有许多方法专门为执行稀疏性而设计了惩罚。当前,还有一些方法在权重衰减的基础之上应用不同的正则化,以期进一步增加稀疏性。
在最近的研究中,有多种方法采用了LASSO(最小绝对收缩并选择操作符)来修剪权重或组。某些其他的方法还采用了针对弱连接的惩罚,以加大保留参数和修剪参数之间的距离,使它们对性能的影响降为最小。经验表明,在整个训练过程中进行惩罚,可以逐步修剪参数,从而达到无缝剪枝的目的。
在神经网络的训练过程中,无须从头开始实现(重用论文作者提供的源代码),在某些现成框架上应用上述基本剪枝方法,实现上相对会更加容易一些。
Pytorch为网络剪枝提供了多种高质量的特性,利用Pytorch所提供的工具,可以轻松地将掩码应用到网络上,在训练期间对该掩码进行维护,并允许在需要时轻松地恢复该掩码。Pytorch还提供了一些基本的剪枝方法,如全局或局部剪枝,无论是结构化的剪枝还是非结构化的剪枝,均能实现。结构化剪枝适用于任何维度的权值张量,可以对过滤器、内核行,甚至是内核内的某些行和列进行修剪。这些内置的基本方法还允许随机地或根据各种准则进行剪枝。
Tensorflow的Keras库提供了一些基本的工具来修剪权重较低的参数,修剪的效率根据所有插入的零引入的冗余来度量,从而可以更好地压缩模型(它与量化很好地结合)。
Blalock等人研发了一个自定义库ShrinkBench,以帮助社区剪枝算法进行规范化。这个自定义的库基于Pytorch框架,旨在使修剪方法的实现更加容易,同时对训练和测试条件规范化。它为不同的剪枝方法(如随机剪枝,权重大小剪枝或梯度大小剪枝),提供了不同的基准。
综上所述,可以看出
1)剪枝结构定义了通过剪枝操作可以获得哪种收益
2)剪枝准则基于多种理论和实际的结合
3)剪枝方法倾向于围绕在训练过程中引入稀疏性以协调性能和成本。
此外,尽管神经网络的基础工作可以追溯到上世纪80年代末,但是,目前神经网络剪枝是一个非常动态的领域,期待有新的基础理论和新的基本概念出现。
尽管目前该领域百花齐放,但仍有很大的探索和创新空间。每种剪枝方法都试图回答一个问题(“如何重建修剪后的权重?”“如何通过优化学习修剪掩码?”“如何释放已经剪去的权重……”),上述所有的研究指明了一个方向:贯穿整个训练过程中的稀疏性。同时,这一方向带出了许多新的问题,比如:“剪枝准则在还没有收敛的网络上有效吗?”, “如何判断是否剪枝选定的权重会有益于所有稀疏的训练?”
卷积神经网络的结构优化:
网络剪枝与稀疏化
根据卷积神经网络训练阶段的不同,网络剪枝与稀疏化方法主要包含训练中稀疏约束与训练后剪枝两个大类。对于前者,通过在优化函数添加稀疏性约束, 诱导网络结构趋于稀疏, 这种端到端的处理方法不需要预先训练好模型, 简化了网络的优化过程。对于后者, 通过剔除网络中相对冗余、不重要 的部分, 同样可以使得网络稀疏化、精简化。事实上, 无论是在训练中引入稀疏约束还是训练后剪枝网络, 最终目的都是使网络的权重矩阵变得稀疏, 这也是加速网络训练、防止网络过拟合的重要方式。
张量分解
利用张量分解以加速卷积过程最典型的例子就是将高维离散余弦变换分解为一系列一维DCT变换相乘, 以及将小波系统分解为一系列一维小波的乘积。张量分解对于深度网络的压缩与加速具有直接 作用, 可以作为网络结构优化设计方法的重要补充。然而目前大多数的张量分解方法都是逐层分解网络, 缺乏整体性的考虑, 有可能导致不同隐含层之间的信息损失。此外, 由于涉及到矩阵分解操作, 会造成网络训练过程的计算资源花费高昂。最后, 由于每次张量分解过后都需要重新训练网络至收敛, 这进一步加剧了网络训练的复杂度。
知识迁移
知识迁移是属于迁移学习的一种网络结构优化方法,即将教师网络的相关领域知识迁移到学生网络 以指导学生网络的训练, 完成网络的压缩与加速。一般地, 教师网络往往是单个复杂网络或者是若干网络的集合,拥有良好的性能和泛化能力, 而学生网络则具有更小的网络规模, 还未获得充分的训练。 考虑利用教师网络本身的知识或通过教师网络学习到的知识去指导学生网络训练, 使得学生网络具有与教师网络相当的性能, 但是参数数量大幅降低, 同样可以实现网络压缩与加速的效果。
知识迁移主要由教师网络获取和学生网络训练两部分内容构成, 在教师网络获取中, 由于教师网络规模较大, 需要用大量标签数据对其进行训练以获得较高的预测准确率。在学生网络训练过程中,首先 将未标签数据输入教师网络进行预测, 然后将预测到的结果与输入数据人工合成为标签数据, 最后将 这些人工合成的标签数据作为领域知识以指导学生网络的训练。由于学生网络规模较小, 因此只需少量的标签数据即可完成训练。
精细模块设计
网络剪枝与稀疏化、张量分解、知识迁移等方法都是在已有高性能模型基础上, 保证模型性能的前提下降低时间复杂度和空间复杂度。目前还有一些工作专注于设计高效的精细模块, 同样可以实现优化网络结构的目的。基于这些精细模块构造的网络具有运行速度快、占用内存少、能耗低下的优点, 此外, 由于采用模块化的网络结构优化方法, 网络的设计与构造流程大幅缩短。目前具有代表性的精细 模块有Inception 模块、网中网和残差模块。
以下诸多因素建议都考虑一下:
网络剪枝(Pruning)、知识蒸馏(Knowledge Distillation)、量化(Quantization)、模型压缩(Model Compression)、编解码器(Codec)、减小输入大小、模型架构优化、引擎优化、使用针对移动端优化的推理引擎,如TensorFlow Lite。
综合把以上方法运用好了,模型压缩、速度提升,模型可高效运行在内存和计算资源受限的移动端。
以上