当前位置: 首页 > article >正文

神经网络修剪实战

修剪神经网络是一个古老的想法,可以追溯到 1990 年(Yan Lecun 的最佳脑损伤工作)甚至更早。这个想法是,在网络中的众多参数中,有些是多余的,对输出的贡献不大。

如果你可以根据网络中神经元的贡献程度对它们进行排序,那么你就可以从网络中删除排名较低的神经元,从而得到一个更小、更快的网络。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 

获得更快/更小的网络对于在移动设备上运行这些深度学习网络非常重要。

可以根据神经元权重的 L1/L2 平均值、它们的平均激活、神经元在某些验证集上不为零的次数和其他创造性方法进行排名。修剪后,准确度会下降(如果排名巧妙,希望不会下降太多),并且网络通常会经过更多训练以恢复。

如果我们一次修剪太多,网络可能会受到严重损坏,无法恢复。

因此,在实践中,这是一个迭代过程 - 通常称为“迭代修剪”:修剪/训练/重复。

图片取自 [1611.06440 修剪卷积神经网络以实现资源高效推理]

1、听起来不错,为什么它不那么流行呢?

有很多关于修剪的论文,但我从未在现实生活中的深度学习项目中遇到过修剪。

考虑到在移动设备上运行深度学习的所有努力,这令人惊讶。我猜原因是以下几点:

  • 到目前为止,排名方法还不够好,导致准确率下降太大。
  • 实施起来很麻烦。
  • 那些使用修剪的人,把它当作自己的秘密优势。

所以,我决定自己实施修剪,看看我是否能用它得到好的结果。

在这篇文章中,我们将介绍几种修剪方法,然后深入研究其中一种方法的实施细节。

我们将微调 VGG 网络以在 Kaggle Dogs vs Cats 数据集上对猫/狗进行分类,这代表了一种我认为在实践中非常常见的迁移学习。

然后我们将修剪网络,使其速度提高近 3 倍,并将大小缩小近 4 倍!

2、修剪目标:速度 vs. 尺寸

在 VGG16 中,90% 的权重位于完全连接的层中,但这些层仅占总浮点运算的 1%。

直到最近,大多数工作都集中在修剪完全连接的层上。通过修剪这些层,可以大大减小模型大小。

我们将重点介绍如何修剪卷积层中的整个过滤器。

但这也有减少内存的酷副作用。正如 [1611.06440 修剪卷积神经网络以实现资源高效推理] 论文中所观察到的,层越深,修剪的就越多。

这意味着最后一个卷积层将被大量修剪,并且其后的完全连接层中的许多神经元也将被丢弃!

在修剪卷积滤波器时,另一种选择是减少每个滤波器中的权重,或删除单个内核的特定维度。最终可能会得到稀疏的滤波器,但计算速度的提高并非易事。最近的研究提倡“结构化稀疏性”,即修剪整个滤波器。

这些论文中有几个表明了一件重要的事情,即通过训练然后修剪更大的网络,特别是在迁移学习的情况下,他们得到的结果比从头开始训练较小的网络要好得多。

现在让我们简要回顾一下几种方法。

  • [1608.08710 修剪滤波器以实现高效的卷积网络]

在这项工作中,他们提倡修剪整个卷积滤波器。 修剪索引为 k 的滤波器会影响它所在的​​层和下一层。 下一层索引 k 处的所有输入通道都必须删除,因为修剪后它们将不再存在。

图片来自 [1608.08710 修剪过滤器以实现高效卷积网络]

如果下一层是全连接层,并且该通道的特征图大小为 MxN,则从全连接层中删除 MxN 个神经元。

这项工作中的神经元排名相当简单。它是每个过滤器权重的 L1 范数。

在每次修剪迭代中,他们对所有过滤器进行排名,在所有层中全局修剪排名最低的 m 个过滤器,重新训练并重复。

  • [1512.08571 深度卷积神经网络的结构化修剪]

这项工作看起来很相似,但排名要复杂得多。他们保留一组 N 个粒子过滤器,它们代表要修剪的 N 个卷积过滤器。

当粒子所代表的过滤器未被屏蔽时,根据验证集上的网络准确度为每个粒子分配一个分数。然后根据新的分数对新的修剪掩码进行采样。

由于运行此过程很繁重,他们使用了一个小型验证集来测量粒子分数。

  • [1611.06440 修剪卷积神经网络以实现资源高效推理]

这是 Nvidia 的一项非常酷的工作。

首先,他们将修剪问题表述为组合优化问题:选择权重 B 的子集,这样在修剪它们时网络成本变化将最小。

请注意他们如何使用绝对差异而不仅仅是差异。使用绝对差异可以确保修剪后的网络不会过多地降低网络性能,但也不应该增加网络性能。他们在论文中表明这会产生更好的结果,大概是因为它更稳定。

现在,所有排名方法都可以通过这个成本函数来判断。

  • Oracle 修剪

VGG16 有 4224 个卷积滤波器。“理想”的排名方法将是强力的 - 修剪每个滤波器,然后观察在训练集上运行时成本函数如何变化。由于他们来自 Nvidia,并且可以使用数以亿计的 GPU,所以他们就是这么做的。这称为 oracle 排名 - 最小化网络成本变化的最佳排名。现在,为了衡量其他排名方法的有效性,他们计算了与 oracle 的 Spearman 相关性。令人惊讶的是,他们提出的排名方法(下文介绍)与 oracle 的相关性最高。

他们提出了一种新的神经元排名方法,该方法基于网络成本函数的一阶(意味着计算速度快)泰勒展开。

修剪滤波器 h 与将其归零相同。

当网络权重设置为 W 时,C(W, D) 是数据集 D 上的平均网络成本函数。现在我们可以将 C(W, D) 评估为 C(W, D, h = 0) 周围的扩展。它们应该非常接近,因为删除单个过滤器不会对成本产生太大影响。

h 的排名为 abs(C(W, D, h = 0) - C(W, D))。

然后通过该层中排名的 L2 范数对每一层的排名进行归一化。我猜这是一种经验主义,我不确定为什么需要这样做,但它极大地影响了修剪的质量。

这个排名非常直观。我们本可以将激活和梯度都用作排名方法。如果它们中的任何一个很高,则意味着它们对输出很重要。将它们相乘可以让我们有一种在梯度或激活非常低或很高时丢弃/保留过滤器的方法。

这让我很疑惑——他们是否将修剪问题提出为最小化网络成本差异,然后提出泰勒扩展方法,还是相反,而网络成本差异预言是支持他们的新方法的一种方法?:-)

在论文中,他们的方法在准确性方面也优于其他方法,因此看起来预言是一个很好的指标。

无论如何,我认为这是一种比粒子过滤器更易于编码和测试的好方法,所以我们将进一步探索这一点!

3、使用泰勒标准排名修剪猫与狗分类器

假设我们有一个迁移学习任务,需要从相对较小的数据集中创建分类器。就像在这篇 Keras 博客文章中一样。

我们可以使用像 VGG 这样的强大的预训练网络进行迁移学习,然后修剪网络吗?

如果在 VGG16 中学习到的许多特征都是关于汽车、人和房屋的 - 它们对简单的狗/猫分类器的贡献有多大?

我认为这是一种非常常见的问题。

作为训练集,我们将使用来自 Kaggle Dogs vs Cats 数据集的 1000 张猫图像和 1000 张狗图像。作为测试集,我们将使用 400 张猫图像和 400 张狗图像。

首先让我们展示一些统计数据:

  • 准确率从 98.7% 下降到 97.5%。
  • 网络大小从 538 MB 减小到 150 MB。
  • 在 i7 CPU 上,单个图像推理时间从 0.78 秒缩短至 0.277 秒,几乎缩短了 3 倍!

3.1 训练大型网络

我们将采用 VGG16,删除完全连接的层,并添加三个新的完全连接的层。我们将冻结卷积层,并仅重新训练新的完全连接的层。在 PyTorch 中,新层如下所示:

self.classifier = nn.Sequential(
	    nn.Dropout(),
	    nn.Linear(25088, 4096),
	    nn.ReLU(inplace=True),
	    nn.Dropout(),
	    nn.Linear(4096, 4096),
	    nn.ReLU(inplace=True),
	    nn.Linear(4096, 2))

经过 20 个数据增强训练后,我们在测试集上获得了 98.7% 的准确率。

3.2 排序滤波器

要计算泰勒标准,我们需要对数据集执行前向+后向传递(如果数据集太大,则对数据集的较小部分执行前向+后向传递。但由于我们只有 2000 张图像,因此我们使用这个)。

现在我们需要以某种方式获取卷积层的梯度和激活。在 PyTorch 中,我们可以在梯度计算上注册一个钩子,因此当它们准备就绪时会调用回调:

for layer, (name, module) in enumerate(self.model.features._modules.items()):
	x = module(x)
	if isinstance(module, torch.nn.modules.conv.Conv2d):
		x.register_hook(self.compute_rank)
		self.activations.append(x)
		self.activation_to_layer[activation_index] = layer
		activation_index += 1

现在我们在 self.activations 中有了激活,当梯度准备就绪时,将调用 compute_rank

def compute_rank(self, grad):
	activation_index = len(self.activations) - self.grad_index - 1
	activation = self.activations[activation_index]
	values = \
		torch.sum((activation * grad), dim = 0).\
			sum(dim=2).sum(dim=3)[0, :, 0, 0].data
	
	# Normalize the rank by the filter dimensions
	values = \
		values / (activation.size(0) * activation.size(2) * activation.size(3))

	if activation_index not in self.filter_ranks:
		self.filter_ranks[activation_index] = \
			torch.FloatTensor(activation.size(1)).zero_().cuda()

	self.filter_ranks[activation_index] += values
	self.grad_index += 1

这会将批次中的每个激活及其梯度逐点相乘,然后对于每个激活(即卷积的输出),我们在输出维度之外的所有维度上求和。

例如,如果批次大小为 32,特定激活的输出数量为 256,该激活的空间大小为 112x112,因此激活/梯度形状为 32x256x112x112,则输出将是一个 256 大小的向量,表示此层中 256 个滤波器的排名。

现在我们有了排名,我们可以使用最小堆来获取 N 个排名最低的滤波器。与 Nvidia 论文中在每次迭代中使用 N=1 不同,为了更快地获得结果,我们将使用 N=512!这意味着每次修剪迭代,我们都将从 4224 个卷积滤波器的原始数量中删除 12%。

低排名滤波器的分布很有趣。大部分被修剪的滤波器都来自更深的层。以下是第一次迭代后被修剪的滤波器:

层编号修剪掉的滤波器数量
Layer 06
Layer 21
Layer 54
Layer 73
Layer 1023
Layer 1213
Layer 149
Layer 1751
Layer 1935
Layer 2152
Layer 2468
Layer 2674
Layer 2873

3.3  微调并重复

在此阶段,我们解冻所有层并重新训练网络 10 个时期,这足以在此数据集上获得良好的结果。然后我们回到步骤 1,使用修改后的网络并重复。

这是我们付出的真正代价 - 这是用于训练网络的时期数的 50%,在一次迭代中。在这个玩具数据集中,我们可以侥幸逃脱,因为数据集很小。如果你对一个巨大的数据集这样做,你最好有很多 GPU。

4、结束语

我认为修剪是一种被忽视的方法,它将得到更多的关注和在实践中的使用。我们展示了如何在玩具数据集上获得良好的结果。我认为深度学习在实践中解决的许多问题与这个问题类似,在有限的数据集上使用迁移学习,因此它们也可以从修剪中受益。


原文链接:神经网络修剪实践 - BimAnt


http://www.kler.cn/a/315899.html

相关文章:

  • 创建vue插件,发布npm
  • 新版Apache tomcat服务安装 Mac+Window双环境(笔记)
  • Android setTheme设置透明主题无效
  • Ubuntu24.04上安装和配置MariaDB
  • [DB]
  • 蓝桥杯竞赛单片机组备赛【经验帖】
  • ubuntu安装docker compose
  • 解决 TortoiseGitPlink Fatal Error:深入解析
  • JS巧用.padStart()|.padEnd()方法用另一个字符串填充当前字符串
  • 9月16日笔记
  • 工作笔记:Vue 3 中使用 vue-router 进行导航与监听路由变化
  • 关于 Qt运行加载内存较大崩溃添加扩大运行内存 的解决方法
  • 使用Stream实现事件流
  • Django一分钟:借助Django的认证系统快速实现RBAC权限校验以及Session会话
  • 深入浅出:Eclipse 中配置 Maven 与 Spark 应用开发全指南
  • 一个能同时to B和to C、批发零售一体化的需求分析和系统设计
  • 达梦数据库对象管理(三):索引
  • 使用vue创建项目
  • 蓝桥杯模块一:LED指示灯的基本控制
  • JavaEE: 深入探索TCP网络编程的奇妙世界(四)
  • 视频工具EasyDarwin将本地视频生成RTSP给WVP拉流列表
  • 基于51单片机的手环设计仿真
  • LeetCode 热题 100 回顾8
  • 【STM32】TIM定时器定时中断与定时器外部时钟的使用
  • ICM20948 DMP代码详解(38)
  • go libreoffice word 转pdf