对于任何想要创建可扩展服务的人来说,部署大内存的深度学习算法是一项挑战。 从长远来看,云服务是昂贵的。 在边缘设备上离线部署模型更便宜,并且还有其他好处。 唯一的缺点是它们缺乏内存和计算能力。
本文探讨了一些可用于在内存受限设置中拟合神经网络的技术。 不同的技术用于“训练”和“推理”阶段,因此分别讨论。
Training
某些应用程序需要在线学习。也就是说,模型会根据反馈或附加数据进行改进。在边缘部署此类应用程序会对模型造成有形的资源限制。这里有 4 种方法可以减少此类模型的内存消耗。
1. Gradient Checkpointing
TensorFlow 等框架会消耗大量内存进行训练。在前向传播期间,图中每个节点的值都会被评估并保存在内存中。这是在反向传播期间计算梯度所必需的。
通常情况下这没什么问题,但当模型变得更深更复杂时,内存消耗会急剧增加。对此的一个巧妙的回避解决方案是在需要时重新计算节点的值,而不是将它们保存到内存中。
但是,如上所示,计算成本显着增加。 一个好的权衡是只在内存中保存一些节点,而在需要时重新计算其他节点。 这些保存的节点称为检查点。 这大大减少了深度神经网络内存消耗。 这如下图所示:欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新技术跟踪、经典论文解读。
2. 牺牲速度换内存(重新计算)
扩展上述思想,我们可以重新计算某些操作以节省时间。一个很好的例子是 《Memory-Efficient Implementation of DenseNets 》这篇论文。
DenseNets 参数效率很高,但内存效率也很低,这种现象是由concatenation和batchnorm两项操作的性质引起的。
为了使 GPU 上的卷积高效,这些值必须连续放置。因此,在concatenation之后,cudNN 在 GPU 上连续排列这些值。这涉及到大量的冗余内存分配。同样,batchnorm 涉及过多的内存分配,如本文所述。这两种操作都会导致内存的二次方增长。DenseNets 有大量的concatenation和batchnorm,因此它们的内存效率很低。
上述问题的巧妙解决方案涉及两个关键观察
首先,concatenation和batchnorm不是时间密集型的。因此,我们可以在需要时重新计算值,而不是存储所有冗余内存。其次,我们可以使用“共享内存空间”来转储输出,而不是为输出分配“新”内存空间。
我们可以覆盖这个共享空间来存储其他连接操作的输出。我们可以在需要时重新计算用于梯度计算的串联操作。 类似地,我们可以将其扩展为 batchnorm 操作。这个简单的技巧节省了大量 GPU 内存,以换取略微增加的计算时间。
3. 降低精度
在一篇优秀的博客中,Pete Warden 解释了如何使用 8 位浮点值训练神经网络。由于精度降低会产生许多问题,其中一些问题列在下面:
- 如《Training deep neural networks with low precision multiplications》论文中所述,“激活值、梯度和参数”具有完全不同的范围。 定点表示并不理想。论文声称“动态定点”表示非常适合低精度神经网络。
- 正如 Pete Warden 的另一篇博客中所述,较低的精度意味着与精确值的偏差较大。通常,如果错误是完全随机的,它们很有可能相互抵消。然而,零被广泛用于padding、dropout和ReLU。在较低精度的浮点格式中精确表示零可能是不可能的,因此可能会在性能中引入整体偏差。
4. 神经网络架构工程
架构工程(Architecture engineering)涉及设计最优化准确度、内存和速度的神经网络结构。
有以下几种方法可以在空间和时间上优化卷积。
- 将 NxN 卷积分解为 Nx1 和 1xN 卷积的组合。这节省了大量空间,同时也提高了计算速度。在Inceptionv2-v4网络中都使用了这个和其他几个优化技巧。
- 在 MobileNet 和 Xception Net 中使用 Depthwise Separable 卷积。
- 使用 1x1 卷积作为瓶颈来减少传入通道的数量。该技术用于很多经典的神经网络。
一个有意思的解决方案是让机器为特定问题选择最佳架构。神经架构搜索使用机器学习来为给定的分类问题找到最佳的神经网络架构。在 ImageNet 上使用时,由此形成的网络 (NASNet) 是迄今为止创建的性能最佳的模型之一。Google 的 AutoML 有着相同的工作原理。
Inference
边缘推断的拟合模型相对容易。 本节介绍可用于针对此类边缘设备优化神经网络的技术。
1. 去掉 “Bloatware”
TensorFlow 等机器学习框架会消耗大量内存空间来创建图。这个额外的空间对于加速训练过程很有用,但它不用于推理。因此,可以剪掉专门用于训练的图部分。我们将这部分称为graph bloatware。
对于 TensorFlow,建议将模型检查点转换为冻结推理图。此过程会自动删除占用大量内存的bloatware。 当转换为冻结推理图时,来自模型检查点的引发资源耗尽错误的图有时可以满足内存。
2. 特征剪枝
Scikit-Learn 上的一些机器学习模型(如随机森林和 XGBoost)输出名为 feature_importances_ 的属性。该属性表示每个特征对于分类或回归任务的重要性。我们可以简单地修剪最不重要的特征。如果模型具有无法通过任何其他方法减少的过多特征,这将非常有用。
同样,在神经网络中,很多权重值都接近于零。 我们可以简单地修剪这些连接。 但是,删除层之间的单个连接会创建稀疏矩阵。 目前正在努力创建可以无缝处理稀疏操作的高效推理引擎(硬件)。 然而,大多数机器学习框架在将稀疏矩阵传输到 GPU 之前就已经将它们转换为密集形式。
相反,我们可以移除无关紧要的神经元并稍微重新训练模型。 对于 CNN,我们也可以删除整个卷积核。 研究和实验表明,通过使用这种方法,我们可以保留大部分精度,同时大幅减小尺寸。
3. 权重共享
一个 4x4 权重矩阵。它有 16 个 32 位浮点值。我们需要 512 位 (16 * 32) 来表示矩阵。
将权重值量化为 4 个级别,但保留它们的 32 位性质。现在,4x4 权重矩阵只有 4 个唯一值。这 4 个唯一值存储在单独的(共享)内存空间中。我们可以为 4 个唯一值中的每一个指定一个 2 位地址(可能的地址值为 0、1、2 和 3)。
我们可以通过使用 2 位地址来引用权重值。因此,我们获得了一个具有 2 位地址的新 4x4 矩阵,矩阵中的每个位置都指向共享内存空间中的一个位置。此方法需要 160 位(16 * 2 + 4 * 32)用于整个表示。缩减因子为3.2 。
不用说,这种尺寸的减小伴随着时间复杂度的增加。 但是,访问共享内存的时间不会是严重的时间损失。
4. 量化
回想一下,本文的训练部分介绍了降低精度。 对于推理,精度的降低并不像训练那样麻烦。 权重可以只转换为较低精度的格式,然后进行推理。 但是,精度的急剧下降可能需要对weights进行轻微的重新调整。
5. Encoding
修剪和量化的权重可以通过使用编码进一步进行大小优化。 霍夫曼编码可以用较少的位数表示最常见的权重值。 因此,在位级别上,霍夫曼编码的字符串比普通字符串占用的空间更小。
深度压缩探索使用无损压缩技术(如霍夫曼)进行编码。 然而,研究也探索了有损压缩技术的使用。 这两种方法的缺点是翻译的开销。
6. 推理优化器
到目前为止,我们已经讨论了一些很不错的想法,但是从头开始实施它们需要相当长的时间。 这就是推理优化器发挥作用的地方。例如,英伟达的 TensorRT 融合了所有这些想法(以及更多),并在训练好的神经网络的情况下提供了一个优化的推理引擎。
此外,TensorRT 可以优化模型,以便更好地利用 Nvidia 的硬件。下面是一个示例,其中使用 TensorRT 优化的模型更有效地使用 Nvidia 的 V100。
7. 知识蒸馏
我们可以教授较小的模型来模仿强大的较大模型的性能,而不是执行花哨的优化技术。 这种技术称为知识蒸馏,它是 Google Learn2Compress 的一个组成部分。
通过使用这种方法,我们可以强制适合边缘设备的较小模型达到较大模型的性能水平,而准确度的下降很小。
具体可以参考公众号的另一篇文章《知识蒸馏简要概述》。