Tensorflow 模型优化

最近一段时间找实习的经历真的是艰难困苦,大家对机电这个专业本身的认知度就不高。确实机电从名字上看是机械和电子,好像是偏硬件更多点,但实际上这个专业的核心应该是感知和控制,像自动化一样,怎样更好的结合软硬件是我们学习的内容。

像我们实验室现在偏向感知更多点,就是使用机器视觉去感知环境,进行图像处理,让控制器做出判断。但是计算机学院有了专门的模式识别,做这个更厉害一些。我们也只能更加努力的去学习弥补差距。

寻找实习的过程是一个了解当前企业,用人单位更需要什么技术的最直观的一种方式。实验室里所学的,所用的不能和社会脱节,毕竟小硕大部分去搞工程了,博士才安心在实验中做研究。

不得不说说最近的电话面试。电话面试其实要比当面面试更难一点。电话面时很容易出现因为听不清楚,或者听不明白问题(不像面试时可以用纸笔,画图,准确的描述好问题)导致一个问题就打蒙的情况,然后剩下的也就没什么好的发挥了。只能提高自己的抗压和理解能力,然后不清楚的问题要多问,还是能理解题目更主要一点吧应该。。。还有就是好好准备一下自我介绍,最好能介绍的完善一点,不要让面试官问更多边角的问题,主要集中在技术上,想到哪写到哪。。。加油啊少年!

对于图像处理里深度学习这一部分内容来讲,企业现在所需要的是小的网络模型,可以将其布置到实际的现场环境,例如手机,嵌入式设备当中去,也就是边缘计算,不再需要将数据上传到云端,这是现在的一个趋势。但是话又说回来,如果5G普及了的话,上传可能也方便很多,不再是问题,边缘计算还重要么,也就剩安全一个特点了?反正这是现在的两个趋势。

面试官现在的关注点通常在于SSD,YOLO这样的快速实时目标检测算法,前置网络通常为mobilenet,shufflenet,darknet19,darknet53这样的小型网络,有可能部署到移动端或者嵌入式设备中。如果有模型压缩的相关知识,更容易受到面试官的青睐。

同时我也受到了同学的启发,我觉得他说的非常有道理,天天看旧的网络,虽然比较经典,但是在上面进行一些简单的改进没什么大的意义,实际操作一次,写明白改进代码搞清楚逻辑就行了,更多的是要看最新的论文和代码,了解现在的前沿是什么样的,更能开阔眼界,更能提升自己。代码可以通过几个月的集中训练得到提升,但是知识和眼界是一个漫长的积累的过程。我也觉得这种能力和学习能力更是研究生应该掌握的东西。(虽然研究不出来,但是看的够多,用的实验的更多也很好)

one-stage的目标检测网络复习完了,小网络mobilenet,darknet也复习了,所以接下的时间,两个任务:1.系统的学习模型的方法理论和操作(剪枝要重新整理一下)。2.学习新的网络,像最近很流行的anchor-free的方法,都没有认真静下心来看看。

这篇博文就从模型优化开始学起,操作理论结合着来。

首先Tensorflow中提供了模型压缩工具Graph Transform Tool,https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/graph_transforms/README.md#using-the-graph-transform-tool
介绍里说的很明确
完成模型训练并希望将其部署到生产中后,您通常需要对其进行修改以便在最终环境中更好地运行。 例如,如果您要定位手机,则可能需要通过量化权重来缩小文件大小,或者优化批量标准化或其他仅限培训功能。 Graph Transform框架提供了一套用于修改计算图的工具,以及一个可以轻松编写自己的修改的框架。

本指南分为三个主要部分,第一部分是关于如何执行常见任务的教程,第二部分是涵盖所有不同转换的参考,以及适用于它们的选项,第三部分是创建自己的选项的指南变换。

需要使用bazel编译源码,在前几篇博文中配置过tensorflow的c++接口,所以源码和工具都是现成的,而且都是配套的。看起来编译的是不同的部分,应该可以直接编译一下试试。希望不要把我的c++接口搞崩了,重新配一次还是有点麻烦的。

依照官方说明进行编译:
bazel build tensorflow/tools/graph_transforms:transform_graph
这一步需要进行很久。很好,编译完后c++接口依旧可以使用。
接下来进行测试,版本不一样,使用的测试指令有些许的不同。我是用官方提供.pb——http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz先进行测试。代码如下:
bazel-bin/tensorflow/tools/graph_transforms/transform_graph
–in_graph="/home/omnisky/gonqimin/test_graph transform tool/classify_image_graph_def.pb"
–out_graph="/home/omnisky/gonqimin/test_graph transform tool/optimized_inception_graph.pb"
–inputs=“Mul:0”
–outputs=“softmax:0”
–transforms="
add_default_attributes
fold_constants(ignore_errors=true)
fold_batch_norms
fold_old_batch_norms
quantize_weights
quantize_nodes
strip_unused_nodes
sort_by_execution_order
"
模型大小从95.7M压缩到了24.5M。
先针对该工具的参数进行分析(其实就是翻译一下说明文档),然后再针对目标检测框架做测试。
1.
该工具支持的许多变换需要知道模型的输入和输出层是什么。 这些的最佳来源是模型训练过程,对于分类器,输入将是从训练集接收数据的节点,输出将是预测。 如果您不确定,summarize_graph工具可以检查模型并提供有关可能的输入和输出节点的猜测,以及对调试有用的其他信息。 以下是如何在Inception V3图表中使用它的示例:

bazel build tensorflow/tools/graph_transforms:summarize_graph
bazel-bin/tensorflow/tools/graph_transforms/summarize_graph --in_graph=tensorflow_inception_graph.pb

缩小文件大小
如果您希望将模型部署为移动应用程序的一部分,那么保持下载大小尽可能小是非常重要的。对于大多数TensorFlow模型,文件大小的最大部分是传递到卷积层和全连接层的权重,因此任何可以减少这些层的存储大小的东西都非常有用。幸运的是,大多数神经网络都能抵抗噪声,因此可以有损地改变这些权重的表示,而不会在整体上损失很多精度。
在iOS和Android应用程序包上都会在下载之前进行压缩,因此减少用户接收应用程序所需带宽的最简单方法是提供更容易压缩的原始数据。默认情况下,权重存储为浮点值,即使数字之间的微小差异也会导致非常不同的位模式,因此这些不能很好地压缩。如果您对权重进行舍入以使附近的数字存储为完全相同的值,则生成的比特流会有更多的重复,因此可以更有效地压缩。要在模型上尝试此技术,请运行round_weights变换。

bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=tensorflow_inception_graph.pb \
--out_graph=optimized_inception_graph.pb \
--inputs='Mul' \
--outputs='softmax' \
--transforms='
  strip_unused_nodes(type=float, shape="1,299,299,3")
  fold_constants(ignore_errors=true)
  fold_batch_norms
  fold_old_batch_norms
  round_weights(num_steps=256)'

这里面的strip_unused_nodes参数存疑,因为使用该参数会报错,可能是版本迭代的原因?
您应该看到optimized_inception_graph.pb输出文件与输入的大小相同,但是如果您在其上运行zip来压缩它,它比压缩原始文件时要小70%! 这种转换的好处在于它根本不会改变图形的结构,因此它运行完全相同的操作,并且应该具有与以前相同的延迟和内存使用。 您可以调整num_steps参数以控制每个权重缓冲区舍入到的值的数量,因此较低的数字将以精度为代价增加压缩。
作为进一步的步骤,您可以直接将权重存储为八位值。 :

bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=tensorflow_inception_graph.pb \
--out_graph=optimized_inception_graph.pb \
--inputs='Mul' \
--outputs='softmax' \
--transforms='
  strip_unused_nodes(type=float, shape="1,299,299,3")
  fold_constants(ignore_errors=true)
  fold_batch_norms
  fold_old_batch_norms
  quantize_weights'

您应该看到输出图形的大小约为原始图形的四分之一。 与round_weights相比,此方法的缺点是插入额外的解压缩操作以将8位值转换回浮点,但TensorFlow运行时中的优化应确保缓存这些结果,因此您不应再看到图表运行慢。

到目前为止,我们一直专注于重量,因为那些通常占据最大的空间。 如果你有一个包含很多小节点的图形,那么这些节点的名称也会开始占用大量的空间。 要缩小它们,可以运行obfuscate_names转换,它用短的,神秘但唯一的ID替换所有名称(输入和输出除外):

bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=tensorflow_inception_graph.pb \
--out_graph=optimized_inception_graph.pb \
--inputs='Mul:0' \
--outputs='softmax:0' \
--transforms='
  obfuscate_names'

这一小节主要整体上给出了缩小模型的方案,例如变换权值,八位量化,替换名称等等,给出了简单的示例,在后面还会有更详细的单个参数的介绍。
3.
八位计算
对于某些平台,能够以8位而不是浮点进行尽可能多的计算是非常有帮助的。 TensorFlow对此的支持仍然是实验性和不断发展的,但您可以使用图形转换工具将模型转换为量化形式:

bazel build tensorflow/tools/graph_transforms:transform_graph
bazel-bin/tensorflow/tools/graph_transforms/transform_graph \
--in_graph=tensorflow_inception_graph.pb \
--out_graph=optimized_inception_graph.pb \
--inputs='Mul' \
--outputs='softmax' \
--transforms='
  add_default_attributes
  strip_unused_nodes(type=float, shape="1,299,299,3")
  remove_nodes(op=Identity, op=CheckNumerics)
  fold_constants(ignore_errors=true)
  fold_batch_norms
  fold_old_batch_norms
  quantize_weights
  quantize_nodes
  strip_unused_nodes
  sort_by_execution_order'

此过程转换图中具有8位量化的所有操作,并将其余操作保留为浮点。 仅支持一部分操作,并且在许多平台上,量化代码实际上可能比浮动等效代码慢,但这是在所有情况都正确时大幅提高性能的方法。

优化量化的完整指南超出了本指南的范围,但有一点可以帮助您在Conv2D或训练期间的类似操作后使用FakeQuantWithMinMaxVars操作。 这将训练控制用于量化的范围的最小/最大变量,以便在推理期间不必通过RequantizationRange动态计算范围。
4.
变换参考(参数)
–transforms字符串被解析为一系列变换名称,每个变换名称在括号内可以有多个命名参数。参数以逗号分隔,如果参数值本身包含逗号(例如形状定义),则可以使用双引号(“)来保存参数值。

–inputs和–outputs在所有变换中共享,因为通常需要知道图中的输入和输出节点是什么。您应该确保在调用图形转换工具之前正确设置这些,如果您有疑问,请检查模型的作者,或使用summarize_graph工具检查可能的输入和输出。

所有转换都可以传递ignore_errors标志,值设置为true或false。默认情况下,转换中发生的任何错误都将中止整个过程,但如果启用此错误,则只会记录错误并跳过转换。这对于版本错误或其他不重要问题可能触发错误的可选转换特别有用。
5.
add_default_attributes
Args:没有

在新版本的TensorFlow中将属性添加到操作系统时,它们通常具有默认值以确保与其原始版本的向后兼容行为。 这些默认值通常在运行时加载图形时添加,但如果要在主TensorFlow框架之外处理模型,则将此更新过程作为转换运行会很有用。 此过程查找在当前TensorFlow操作列表中定义但未在已保存模型中定义的任何op属性,并将它们设置为该属性的已定义默认值。
6.
backport_concatv2
Args:没有

如果您有一个由较新版本的TensorFlow框架生成并包含ConcatV2的GraphDef文件,并且您希望在仅支持Concat的旧版本上运行它,则此转换将负责将这些较新的操作转换为等效的 旧形式。
7.
flatten_atrous_conv
Args:没有
先决条件:fold_constants

该变换使对应于一系列SpaceToBatchND-Conv2D-BatchToSpaceND操作的转化空洞卷积,将其转换为具有上采样滤波器的常规Conv2D操作。 此变换仅应用于在尚未本身支持SpaceToBatchND和BatchToSpaceND操作的平台上运行具有空洞卷积的图形。 您需要确保在此转换后运行fold_constants。 如果适用,您应该在fold_batch_norms之前运行此转换。
8.
fold_batch_norms(优化BN)
Args:没有
先决条件:fold_constants

当在训练期间使用批量标准化时,该变换试图优化在Conv2D(或MatMul)之后引入的Mul。 它会在卷积后立即扫描图形中的任何通道乘法,并将卷积(或矩阵乘法)权重与Mul相乘,因此可以在推理时将其省略。 您需要确保首先运行fold_constants,因为只有在通过训练为Mul输入生成的正常复杂表达式折叠成一个简单常量时才能发现该模式。
9.
fold_constants
Args:
clear_output_shapes:清除保存为属性的张量形状信息。 某些较旧的图表包含过时的信息,可能会导致导入错误。 默认为true。
先决条件:无

查找模型中始终求值为常量表达式的任何子图,并用这些常量替换它们。 这个优化总是在加载图形后的运行时执行,因此首先离线运行它不会有助于延迟,但它可以简化图形,从而使进一步处理更容易。 通常使用fold_constants(ignore_errors = true)来调用,因为这只是一个优化阶段。
10.
fold_old_batch_norms
Args:没有
先决条件:无

在TensorFlow的早期阶段,使用BatchNormWithGlobalNormalization或FusedBatchNorm等单一操作来实现批量标准化。 在现代版本中,从Python添加批量规范化将为您提供一系列较小的数学运算,以实现相同的效果而无需专用代码。 如果您有一个使用旧式的图形,则此变换将识别并优化这些操作以进行推理,其方式与fold_batch_norms变换为新方法所做的相同。
11.
fuse_convolutions
Args:没有
先决条件:无

对于在卷积之前使用ResizeBilinear或MirrorPad ops的图形(例如,在图像样式传输模型的后期阶段进行放大),它可以改善内存使用和延迟,以将空间变换与卷积的im2col补丁生成相结合。 此变换查找ops的特定模式,并将其替换为融合版本,将调整大小和填充与卷积相结合。
12.
insert_logging
ARGS:

op:每次出现此op类型后插入一个Print。可以重复覆盖多种类型。如果不存在,则将检测所有操作类型。
prefix:在名称以此值开头的每个节点后插入Print。可以重复覆盖多个节点。如果不存在,则将匹配所有节点名称。
show_op:如果为true,则op类型将被添加到所有日志消息之前。
show_name:如果为true,则节点的名称将被添加到所有日志消息之前。
message:在值之前记录的任意文本。
first_n:在抑制之前打印多少次。默认为-1,表示永不停止。
总结:数字结果在被截断之前可以有多长。默认为1024。
当打印机在图形中运行时,它会将字符串写入stderr,并打印出它正在读取的节点的数值结果。当您正在调试并希望在图形运行时遵循特定的内部值时,这非常有用。此转换允许您在图形中的特定点插入这些操作,并自定义显示的消息。它还与freeze_requantization_ranges转换结合使用,以输出所需的信息。
13.
merge_duplicate_nodes
Args:没有
先决条件:无

如果存在具有相同类型和内容的Const节点,或具有相同输入和属性的节点,则此变换将它们合并在一起。 当你想减少具有大量冗余的图中节点的数量时,它会很有用(例如,这个变换总是作为quantize_nodes的一部分运行,因为那里的处理可能会引入量化中使用的常量重复/ 反量化过程)。
14.
obfuscate_names
Args:没有
先决条件:无

用输入和输出以外的短生成ID替换所有节点的名称。 这也会更新图中的所有引用,以便保留结构。 如果要缩小文件大小,或者如果要在发布模型之前更难理解模型的体系结构,这可能很有用。(检测网络中会很有用,其中有很多名字很长的变量)
15.
quantize_nodes
Args:

input_min:任何量化占位符输入的最低浮点值。
input_max:任何量化占位符输入的最高浮点值。如果同时设置了input_min和input_max,则图形中的任何浮动占位符都将替换为量化版本,并且将创建consts以将范围传递给后续操作。
fallback_min:用于重新量化激活层的最低浮点值。
fallback_max:用于重新量化激活层的最高浮点值。如果同时设置了fallback_min和fallback_max,那么在转换像QuantizedConv2D和QuantizedBiasAdd这样的操作的32位输出时,不是使用RequantizationRange操作来动态地计算出有用范围,而是使用具有这些值的硬连线consts。如果您提前知道激活层的范围,这可以帮助提高性能。
先决条件:quantize_weights

用8位当量(如果可用)替换任何计算节点,并添加转换层以允许剩余的浮点运算进行互操作。这是最复杂的转换之一,涉及多次传递和大量重写。它仍然是一个活跃的研究领域,因此结果可能会根据您在模型中使用的平台和操作而有所不同。您应首先运行quantize_weights以确保您的Const操作是八位形式。
16.
quantize_weights
Args:
minimum_size:元素少于此数的张量不会被量化(默认为1024)
先决条件:无
将任何大的(超过minimum_size)浮点Const op转换为8位等效值,然后进行浮点转换操作,以便后续节点可以使用该结果。 这对于缩小文件大小非常有用,但也有助于更高级的quantize_nodes转换。 即使没有先决条件,建议运行fold_batch_norms或fold_old_batch_norms,因为将方差舍入为零可能会导致精度的显着降低。
17.
remove_attribute
Args:
attribute_name:要删除的属性的名称。
op_name:用于限制删除的单个操作的可选名称。
先决条件:无
从所有节点删除给定属性,或者只删除op_name中指定的属性。 这可能是一种危险的转换,因为如果删除必需的属性,很容易使图形处于无效状态。 但它在特殊情况下可能很有用。
18.
remove_control_dependencies
Args:没有
先决条件:无
从图中删除所有控件依赖项。
19.
remove_nodes
Args:
op:要删除的操作的名称。 可以重复删除多个操作。
先决条件:无
这是一个潜在危险的转换,它查找具有给定名称的单输入,单输出操作,从图中删除它们,并重新连接用于从它们拉出以从前一节点拉出的所有输入。 这对于摆脱像CheckNumerics这样在训练期间很有用的操作非常有用,但只会使图形复杂化并增加推理期间的延迟。 它很危险,因为删除某些操作可能会改变图形的输出,因此请确保在使用后检查整体精度。
20.
rename_attribute
Args:
old_attribute_name:要重命名的属性的当前名称。
new_attribute_name:您希望该属性现在具有的名称。
op_name:如果设置了此选项,则仅更改给定操作类型的属性,否则应用于属性名称匹配的所有节点。
先决条件:无
更改给定属性的名称。 这通常对于升级图形文件很有用,因为op定义会更改版本,因为重命名通常足以处理微小的更改。
21.
rename_op
Args:

old_op_name:操作的当前名称。
new_op_name:要更改为的名称。
先决条件:无
查找具有给定名称的所有操作,并将其更改为新操作。 如果操作之间的更改与名称之间的较小,则这对于版本升级很有用。
22.
round_weights
Args:

num_steps:每个缓冲区中使用的唯一值数。
先决条件:无
将大型Const操作(超过15个元素)中的所有浮点值舍入到给定的步数。 通过在存在的最大值和最小值之间线性分配,每个缓冲区选择唯一值。 当您要在移动设备上部署时,这非常有用,并且您需要一个能够有效压缩的模型。 有关详细信息,请参阅缩小文件大小。 即使没有先决条件,建议运行fold_batch_norms或fold_old_batch_norms,因为将方差舍入为零可能会导致精度的显着降低。
23.
sparsify_gather
Args:没有
先决条件:无

将’Gather’操作转换为稀疏版本,其中’params’输入’Gather’从密集的’Const’替换为’HashTable’。 'Gather’操作本身被哈希表查找取代。 这对于减少稀疏TF.learn线性模型内存占用大多数非常有用。
24.
sort_by_execution_order
Args:没有
先决条件:无

按拓扑顺序排列GraphDef中的节点,以便任何给定节点的输入始终早于节点本身。 当您定位最小推理引擎时,这尤其有用,因为您可以按给定顺序执行节点,因为知道输入将在需要之前计算。
25.
strip_unused_nodes
Args:
type:生成的任何新占位符节点的默认类型,例如int32,float,quint8。
shape:生成的任何新占位符节点的默认形状,以逗号分隔的尺寸。 例如shape =“1,299,299,3”。 双引号很重要,否则逗号将被视为参数分隔符。
name:占位符参数的标识符。
type_for_name:用于以前给定名称的类型。
shape_for_name:用于以前给定名称的形状。
先决条件:无

删除计算-outputs中给出的图层中未使用的所有节点,由–inputs提供。 这对于删除仅限培训的节点(如保存和恢复或摘要操作)通常很有用。 当在推理路径中不需要解码或其他操作时,它对于解决缺少的内核错误问题也很方便。
最大的复杂因素是它有时必须创建新的Placeholder操作,因此可以选择控制它们的特性。 例如,如果通过在网络中指定更深的输入层来绕过DecodeJpeg操作,则会发生这种情况,因此您可以传入原始图像数组而不是编码字符串作为输入。 解码操作将与提供它的占位符一起被删除,但是您指定的输入图层需要新的占位符。 使用类型和形状参数可以控制所创建的任何新占位符的属性。 普通类型和形状设置全局默认值,但如果您具有不同特征的不同输入,则需要传入参数列表,其中前面的名称指定每个适用的层。 例如,如果你有两个输入in1和in2,你可以调用strip_unused_nodes(name = in1,type_for_name = int32,shape_for_name =“2,3”,name = in2,type_for_name = float,shape_for_name =“1,10,10,3")。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值