深度学习模型压缩方法

2021-04-05 11:21:50

这篇文章涵盖了模型推理优化或压缩,截至3月2021年3月的宽度和最深度。这包括模型量化和二值化的工程主题,更具研究型主题,如知识蒸馏等,以及知名的,以及众所周知的话题。

如果有人注意到任何不正确的东西,请告诉我。请随时与我联系我的电子邮件(我的全名@ Outlook.com)。

每年,较大且较大的模型能够找到从机器学习中噪声提取信号的方法。特别是,语言模型每天都会变大。这些模型在计算上昂贵(在运行时和内存中),在向客户提供或过于慢或较大时可以昂贵,以便在Phone等边缘环境中运行。

研究人员和从业者提出了许多方法,以优化神经网络以更快或更少的内存使用率运行。在这篇文章中,我将涵盖一些最先进的方法。如果您知道应包括其他方法,我很乐意添加它。这有一个轻微的Pytorch偏见(哈哈),因为我最熟悉它。

量化通常是指采用具有在高精度(32或64位)的参数的模型,并减少每个重量所采用的比特数(例如,低至16,8,甚至更少)。在实践中,这通常会导致2-4倍的加速(在我的经验中具有互联网的最高网络)。

为什么这项工作?事实证明,对于深度网络工作,我们不需要高精度的网络权重。通过适当的硬件支持,使用更少位处理深度学习内核(用于数学运算的花哨术语)可以更快,更快的内存效率更快,因为计算的比特较少(torch.qint8是8位,而torch.float32是32位,所以4x较小)。缺点:根据尝试的量化水平,您可能会发现您想要的操作(例如,特定的卷积OP或甚至是转置的特定卷积OP)。当然,与所有方法一样,您可能会发现准确性降低太多才能有用。

PyTorch具有支持特殊量化的张量,其在其案例中对应于在8或16位存储数据。了解有关这项工作的具体细节非常重要。如果您的网络具有特殊的结构,这意味着在某个位置,所有输出都在0到1之间(例如,从Sigmoid)之间,那么您可能能够选择更好,更具体的量化。这意味着量化需要收集有关网络如何运行代表性输入的一些数据。特别地,大多数量化通过像圆形(x *标量)这样的方法发生,其中标量是学习参数(类似于Batchnorm)。

对其中一些操作的支持是"外部" to pytorch(但根据需要加载)。想象类似于诸如量化操作的Blas或MKL。 FBGEMM是服务器的实现,QNNPACK是移动设备的实现(现在在Pytorch适当的内部)。

量化偶尔会在高精度数据类型中累积累积通常比使用较低的精度值更稳定。为每个操作挑选正确的精度可能是不可吸取的,因此Pytorch有一个火炬.cuda.amp包,可以帮助您自动将网络的不同部分投入到半精度(Torch.float16)中。如果您想手动执行此操作,则该页面上有一些有用的提示。

您可以尝试的第一件事之一是携带现有的模型,' s所有`torch.float32`,使用`torch.cuda.amp`运行它,并查看它是否仍然以准确性运行。消费者GPU中的半精确支持仍然相对稀疏,但它适用于非常常见的V100 / P100。

如果您想要更多控制或想要部署到非CUDA环境,则有3个级别的手动量化(根据标签“渴望模式量化”)您可以尝试的,具体取决于您试图量化和多少你愿意出汗:

动态量化:这是最简单的方法。基本上,我们将网络的权重存储在指定的量化中,然后在运行时,激活动态地转换为量化格式,与(量化)权重组合,然后以完全精度写入存储器。然后下一层量化那些,与下一个量化权重相结合,等等。为什么会发生这种情况?我的理解是可以从数据动态确定标量,这意味着这是一种无数据的方法。

我们如何在Pytorch中这样做?这足够短,我们可以在这里写下来:

#量化我们网络的LSTM和线性部分#,并使用Torch.Qint8类型来量化Quantized_Model = Torch.Quantization.Quantize_Dynamic(Model,{Nn.Lstm,NN.LineAl},DType = Torch.Qint8)

有更多的旋钮可以为您的型号做出更好的方式。查看此博客文章的更多详细信息。

静态量化:运行时转换为全精密类型和背部是昂贵的。如果我们知道激活的分布是(通过记录通过网络的真实数据,我们可以删除,如上所述)。当您可以访问流过网络的数据时,Pytorch还可以检查您的模型并实现额外的优化,例如量化操作员融合。以下是设置观察者的示例,使用一些数据运行它,然后导出到新的静态量化模型:

#这是基于移动的推断(ARM)Model.qconfig = torch.get_default_qconfig(qnnpack')#本链(conv + batchnorm + relu)是几个序列之一的默认量化#由模型定位器Model_Fused = torch.quantization.fuse_modules(型号,[' conc' bn'' relu']])#插入观察员_with_observers = torch.quantization.prepare(model_fused)model_with_observers(example_batch)量化_model = torch.quantization.convert(model_with_observers)

量化感知培训(QAT):如果您熟悉神经网络培训,您就知道这将在哪里。如果您告诉培训方法有关如何使用网络的事实,网络将适应此信息。这是如何运作的?在前向和后退通过期间,模型的激活是舍入的挑选量化。这意味着模型基于舍入值获得梯度,这意味着它“调整”其有限的容量。然而,非常重要的是,实际的反射(即重量的梯度下降)完全精确地发生。

我抛出了代码示例,因为这是一个更涉及的方法,但您可以在此处找到一个完整的示例。还有很多旋钮。

笔记!在使用PyTorch量化之前,请参阅模型准备下的有用提示进行量化。

基于Pytorch的量化可能不一定在其他生产环境中工作。特别是,当转换为Apple的CoreM1格式时,您需要使用它们的量化(可能限于16位量化)。使用边缘设备时,请小心检查是否可以检查量化(在Apple的情况下,硬件已经在GPU上计算了FP16中的所有内容,因此您只能保存网络权重的内存)。

Tensorflow具有类似于上面的类似步骤,尽管这些例子集中在TFLITE上。基本上,在训练后量化页面中解释了静态和动态量化,并且有一个QAT页面。我认为权衡非常相似,但在Pytorch和TF之间总是有些功能不匹配。

即将到达1位!多年来已经有几个尝试来创建二元神经网络,如果您想要最精确的vs速度权衡。在大多数情况下,这些仍然是研究项目而不是可用的想法,尽管Xnor-Net ++似乎已在Pytorch中实现。

在训练之后或期间,修剪从神经网络中除去一些重量(即连接)或整个神经元。在实践中,我们通常可以在大型深度神经网络中删除90%的参数,而不会显着影响模型性能。

为什么这项工作?:让我们想象你的模型是一个完全连接的神经网络,只有一个隐藏层,使得输入是尺寸1024,隐藏尺寸为100,输出为20尺寸。然后参数(无偏差)的数量是104400.如果隐藏层中有一个神经元,则从从未触发的隐藏层(或在下游忽略)中,则从网络中删除它可以节省1044参数。为什么不立即培训较小的网络?最引人注目的解释是呼气票假设的东西:

列车成功包含一个初始化的子网络,初始化的子网 - 当触摸训练时 - 它可以与最多相同数量的培训迭代匹配原始网络的准确性。

缺点:去除神经元或选择子网是我(和他人)考虑结构化修剪的内容。但是,此时和Pytorch的Torch.nn.utils.prune的许多方法(包括Tensorflow的TensorFlow_Model_Optimization Toolkit)专注于稀疏模型权重,以便它们更加压缩(有些呼叫非结构化修剪)。这意味着矩阵尺寸相同,但是一些值设置为0.如果这意味着这意味着更大的模型可以使用较少的GPU内存(我基本上不一致),它目前不清楚,但它可以节省磁盘空间。当稀疏模型支持在各种框架中完全落地时(即,您可以将稀疏向量和稀疏矩阵乘以,而不是密集的速度)您也可以加速推断。

因此,我不会花很多时间在非结构化修剪上花费很多时间,因为它似乎没有有用,但基本上你可以在训练期间或之后修剪,你可以选择一定的目标稀疏性(例如80%的重量您的网络将被归零)。然而,这个领域有很多困惑,这使得它很难推荐任何东西。 TensoRflow在培训期间和之后修剪了一些导游,并且Pytorch在训练后使用某些Heuristcs进行修剪教程。

在结构修剪的空间中,仍然有活跃的研究,没有明确的API。我们可以选择一个指标来计算每个神经元的相关性分数,然后删除具有最少信息内容的值。这里可能有用的指标是福芙值,泰勒近似对神经元激活的敏感性,甚至是随机神经元的敏感性。 Torchpruner库自动为NN.LineAre和卷积(NN.conv1d,nn.conv2d等)模块实现了一些。另一个图书馆火炬修剪有几个运营。该区域中最着名的旧作品之一,使用过滤器权重的L1标准从GROMNET提出过滤器。然而,这仍然是一个活跃的研究领域。

在两种情况下,在应用修剪后重新培训网络的标准。我知道的最佳方法基本上是重置学习率(学习速率倒带)并开始再培训网络。如果您想要的话,您可以使用体重倒带,这是将网络的未偿还部分的重量重置为其培训前面的值(例如1/3训练权重)。我的直觉就是现在我们已经确定了彩票票据。

总的来说,我认为一个真正对尝试这一感兴趣的从业者应该从武器或火炬修剪开始,然后尝试使用学习速率倒带进行精细调整所得到的网络。然而,我为大多数架构(包括跳过连接而包括reasnets),它' ll是非常不合作的如何修剪围绕它的其余网络。

一旦我拥有更多的经验这是通过利用智能并行性和更好的缓存来作用。它以扩展到Pytorch来了。

知识蒸馏是一种从大型型号产生更小且更高效的模型的方法。在NLP中,这也被称为教师 - 学生方法,因为大型模型列举了学生模型。该地区的参考工作是(Hinton等,2015)。

在实践中,假设我们有一个分类任务。假设我们的较小学生模型是$ f_ \ theta $,其中$ \ theta $是该组参数。我们采用大型模型或模型的集合(可能是具有不同初始化培训的相同模型),并调用它$ F $(我们不会担心其参数)。然后我们用以下损失训练学生网络:

其中$ f(x_i)$是通过通过网络传递示例$ x_i $创建的标签上的概率分布。如果您愿意,可以使用正确的标签在常规跨熵丢失中混合:

$$ \ mathcal {l} = \ sum_ {i = 1} ^ n \ left(\ operatorname {kl} \ left(f(x_i),f_ \ theta(x_i)\右) - \ beta \ cdot \ sum_ { k = 1} ^ k y_i [k] \ log f_ \ theta(x_i)[k] \右)$$

请注意,第二项术语只是“真实”分布(即从标签的单热分配)到学生模型的KL发散,因为$ Y_I $是一种热门。

为什么这项工作?据我所知,没有达成共识的最佳意见。到目前为止我读过的最引人注目的解释是蒸馏是一种粗糙数据增强的形式。我可以推荐这篇论文:在深入学习中了解集合,知识蒸馏和自我蒸馏,其专注于多种观点的想法。有长啰嗦的风险,这是一个可能解释的思想实验:

蒸馏思想实验:让我们说我们拥有一个培训的大师模型,以对图像进行分类(例如Cifar-100)。该模型隐含地具有一堆“特征探测器”内置,例如,一组卷积滤波器,当看到尖尖的耳朵时,这增加了标签的概率,如“猫”。让我们说蝙蝠侠面具的训练形象,标有“面具”。教师模型的尖锐耳朵过滤器可能仍然射击,告诉我们,该模型认为这看起来像猫10%。

当学生模型接受培训以匹配教师的概率分布时,因为分布是0.1只猫,它仍然可以获得这个图像是猫的小信号,这可能有助于学生模型比否则更好地识别猫。如果学生模型只是在真正的标签上接受培训,则不知道这个蝙蝠侠面具看起来有点像猫。

类似但略有不同的想法解释了为什么模型(甚至相同的架构)的合奏可能很好:

合奏思想实验:假设我们使用用于图像分类的数据集中有3个猫的图片。假设图像1具有具有特征A(例如耳朵)的猫,图像2具有特征B(例如晶须),并且图像3具有A和B。

然后,假设神经网络学习特征A(例如,通过看图像1)。当它看到图像3时,这组卷积滤波器将射击,因此图像将被正确分类。因此,即使一个好的网会学会,它也不会调整网络以识别特征B的梯度。

一旦神经网络变得足够好,它来自某些数据点的信号就会减少。

知识蒸馏是一个非常深入的研究区,触及对抗性攻击,知识转移和隐私。不幸的是,我无法涵盖任何真实细节的人,所以我会把它们留在未来的一天。

在实践中,我上面描述的方法称为基于响应的蒸馏。还有其他形式的蒸馏,包括基于特征和基于关系的知识蒸馏,这是基于学生和教师模型的哪些部分(或计算)的整个子场。

此外,在离线蒸馏(即老师之后的学生)之间存在一个分裂,在线蒸馏(一起培训学生和教师),以及自我蒸馏(教师模型与学生的建筑相同)。这使得这使得难以在实践中追踪;一组特定于adhoc模型的技术可能是最佳的一般性推荐。

事实上,(Cho& Hariharan,2019)发现,当学生模型的容量太低时,使用知识蒸馏实际上会对培训产生不利影响。他们发现知识蒸馏纸很少使用ImageNet,因此通常不适用于难题。令人困惑的是,这篇论文和(Mirzadeh等,2019)发现,更好的教师模型并不总是意味着更好的蒸馏,并且学生和教师模型的能力越远,蒸馏越少。您可以找到最近的调查(Tang等,2021)。

总而言之,到目前为止,我的理解是蒸馏相当困难。您可能能够通过培训具有稍微较小的容量,然后使用基于香草响应的离线蒸馏的学生获得一些免费的绩效点。

然而,深度学习研究人员花了很多时间使用模型的方法蒸馏大型模型,如果您需要获得一些性能,您可能能够找到您目前正在使用的大型模型的预先培训的蒸馏版本。例如,在NLP中,HuggingFace可以轻松访问Distilbert和Tinybert。在计算机愿景中,Facebook Research的D2Go拥有一堆预用的移动式模型,他们专注于Deit的一些蒸馏方法。

良好的学生学习更好:关于预训练专业模型的重要性,提出了一种推荐(具有高质量消融实验),用于训练BERT架构,最好的方法是:

在原始伯格纸开发的蒙面语言模型(MLM)目标上预先列车专门模型架构(Devlin等,2018)。

拍摄一个大的任务特定的教师模型(例如,如果任务是NLI,则输出是通过3类(征集,矛盾,中性))的分布,并在预训练的紧凑模型上执行基于基于响应的离线蒸馏步骤1。

最后,如果需要,请在特定于任务特定数据的步骤2上微调紧凑型模型(例如,如果任务是ner,则在Conll 2003数据集上培训)。这方法(他们调用预先训练的蒸馏(PD))这是它是架构不可知论者。我认为如果您将在实践中使用紧凑的NLP模型,值得撇去纸张,特别是第6节。