TensorFlowLite 的量化使用问题

我想问下,现在很多企业都说使用了 tensorflow lite,但他们使用的 tensorflow lite 是经过量化的吗?因为现在好多操作都不支持量化操作,比如 tf.Abs,tf.tf.Square 等等.如果是使用未量化的 tensorflow lite,那这样速度比起量化后的 pb 文件在 Android 手机上使用,速度有提高吗?提高多少? 还有一个问题.就是要使用 toco 工具生成量化的 lite 文件,是不是要求在训练过程中得先使用 tf.contrib.quantize.create_training_graph 和 tf.contrib.quantize.create_eval_graph () 进行伪量化训练?
麻烦各位大佬解答下.有没有大佬量化自己模型真正测试成功过的,回答下疑惑.或者能写下量化具体步骤教程.这还有一个我的相同的贴,可以一并回答 tensorflowLite 的量化使用


快到碗里来 2018-10-22 17:51:30

以下是我的个人理解,如果是在 Android 上,Tensorflow Lite (tflite) 可以利用 hardware accelerator,应该是更快的。量化只是一个 float32 到 uint8 的过程,本质上是 weights\bias 大小的变化(是原来的 25%),有的 microcontroller 太小,不量化根本就放不进去,并且 mircocontroller 大部分是 8bit 计算,float32 非常昂贵,所以需要量化。
用 toco 生成的量化有两个途径:

  1. 你提到的伪量化,这个确实在 training 时要调用你说的这两句。具体展开:要生成两个 graph,一个用于 training,在 compute gradients 前使用 create_training_graph,因为 forward 和 backward 都需要模拟量化。这个过程其实是找到需要量化的变量,插入 fake quantization nodes 来记录 min-max range information。再具体一点:见(*)。另一个 graph 用于 eval,要在 import_graph_def 后,saver.restore 前插入 create_eval_graph。后面如何 freeze 如何调用 toco,按照这个链接上说的即可:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/quantize

  2. post-training quantization。顾名思义,无需伪量化,是 toco 拿过来一个 pb file,直接把 toco 的 post-training quantization flag 设成 true 完成的。但是注意,这个方法只 quantize weights,而 1.是一个 fully quantized model. 并且 2.在 inference 时,会把 uint8 的 weights 再转换回 float32 来做矩阵乘法。所以,2.这个方法其实依然相当于没做 quantization。它的存在应当只是便于用户转移 model,计算时依然是个非量化 model。链接:https://www.tensorflow.org/performance/post_training_quantization

(*):再往前几个版本,tensorflow 其实有自己的量化,就叫做 quantize。他不是用伪量化,也无需 tflite 介入。这个文件在现在的 tensorflow github 中已经不存在了(半个月前移除的,pete warden 决心只搞 tflite 了)。但是他很好的解释了 tensorlow 和 tflite 量化的数学原理。如果不加入上述的 fake quantize node,tensorflow quantize 则需要 requantize 和 dequantize 的过程。其中 requantize 是必须的,因为 8bit 的两个整数相乘为 16bit 的数。int8 的两个矩阵相乘会得出一个 32bit 的结果,即一个 2d convolution layer 的输出结果是 int32 的。但是下一个 quantized op 需要一个 8bit 的输入,这就需要记录 float32 的 min 和 max,从而对这个 int32 的中间结果进行 requantization,重新获得 8bit 的数据继续往下传。当使用了 fake quantization 后,上述过程在训练过程中被模拟了。于是你会看到训练完了的图,不再存在 requantization node 和 dequantization node。有的只是那些 fake quantization nodes。值得注意的是,不加伪量化的量化,不是 fully quantized model,因为其涉及到了利用 float32 min 和 max 转换。而伪量化不存在该问题。简言之,目前唯一可行的 fully quantization method 就是在 tensorflow 下伪量化训练,再到 tflite 上转化。

(另外,之前还有一个做移动端的方法,即 tensorflow mobile。但是它会在 early 2019 deprecate (应该是被 tensorflow lite 取代),如图:)

对了,我补充一点,我 debug 的时候花了不少时间的一个地方:我用的是在 linux 下,安装 tf.nightly,然后 jupyter notebook 里调用 tf.lite.TFLiteConverter 这个东西。因为是 quantization,所以要求给出 quantized_input_stats,这是一个 tuple (med, std)平均值和标准差是要自己手动调的。我的是图片为输入,一开始给 (128,128),结果很惨。我调来调去,最后是(128,73)给出的 accuracy 达到了 fake quantization 的效果。


Zongjun 发表于 2018-10-24 05:38:19

OnceJune:

您好,请教一个问题,我在使用 create_training_graph 的时候,出现了 Cannot use ‘%s’ as input to ‘%s’ because ‘%s’ is in a while loop.这样的错。想请教一下在加 fake quant node 的时候,对于 op 的操作上面会和不加有什么区别吗?同样的结构直接训练是可以一路训通的。
2019-1-29 18:51

Zongjun: 回复 OnceJune :

如果用的是底层 tensorflow 的 api, ops 比如 conv2d 这些 layer,它们的写法和以前没有不同。只是在定义 loss 之后和定义 optimizer 之前加入 create_training_graph () 即可。这个函数会自动识别一些常用的 op,然后训练时在这些 op 之前插入 fake quant node。这个还和你 Model 的具体结构有关,比如 rcnn 有的 op 就不会被自动识别,需要手动插入 fake quant nodes。
你的这个报错我没有遇见过,估计是 model 结构不同吧。
2019-1-30 02:02

OnceJune:

回复 Zongjun :您好,感谢回答。我实际上是想对 tacotron2 的模型做量化,但是 while op 好像不被支持。手动插入 fake quant nodes 这块是在调用 create_training_graph 之前来操作吗?还是完全重新实现一套插入 fake quant nodes 的逻辑?或者说我可不可以通过某种方式在 CheckInputFromValidContext 这一步跳过不被支持的 op 呢?
2019-2-1 18:05

Zongjun: 回复 OnceJune :

果然是模型的问题。tacotron2 是不是还有 LSTM 的部分啊?这一部分官方的说法是自动插入 fake quant nodes 还没做到这一步。所以你除了 create_training_graph 之外(这个部分保留),还要在你定义 graph 的时候手动插入 fake quant nodes。
还有一种方法,和 quantization aware training 完全不同了。那就是用 Tensorflow 自带的 Graph Transform Tool. 链接: tensorflow/tensorflow/tools/graph_transforms at master · tensorflow/tensorflow · GitHub 重点看一下"Shrinking File Size"这里。还有这个链接:Can't do "weights" quantization on an LSTM RNN · Issue #7949 · tensorflow/tensorflow · GitHub 这个是有人问怎么量化 LSTM(LSTM 是不能自动识别添加 fake quant node 的),你看一下 pete warden 的官方回答。
量化这方面,我的模型一直是自动插入 fake quant node 的,所以我也只能给这些建议了。希望能帮到你一丢丢哈。Good Luck!
2019-2-2 06:22

OnceJune: 回复 Zongjun :

手动插入 fake quant nodes 的话是要完全抛弃 create_training_graph () 是吗?然后 eval 的时候再把手动插入的也手动删除?我看到有 FakeQuantWithMinMaxArgs 这个方法,具体操作是通过它来逐层修改还是要到更底层的地方啊?另外 GraphTransform 这个我也尝试过,模型的 size 确实和预期一样减小,但是本地运行的速度在 avx2 指令集下反而是慢了的,我理解这里也是一个 fake quant 吧,所以可能 norm/denorm 的运算开销冲抵了 8bit 带来的提升。
2019-2-2 16:45

Zongjun: 回复 OnceJune :

手动插入我没用过,但是我觉得首先可以尝试保留 create_training_graph () 和 create_eval_graph ()。在它们无法检测到的层的地方手动加入 fake quant node。看看会不会在 create_eval_graph () 的时候把所有的 fake quant node 都自动处理,不行的话再尝试手动删除。
graph transform 应该不是 fake quant。所谓 fake quant 其本质是 quantization-aware training,是 train 的过程中模拟量化。graph transform 是你拿着一个训练好的模型给它,它再进行处理。读一下 graph transform 的 Eight-bit Calculations 部分,它上面也说了:on many platforms the quantized code may actually be slower than the float equivalents, but this is a way of increasing performance substantially when all the circumstances are right. 下面一段简单讲了一下怎么手动插入 fake quant node。这一部分其实就是在说,如果想真正做到 fully quantization,那就需要 fake quant node,训练时就 train 好 min 和 max,然后用它说的那个指令量化。但是这个看上去又和 fake quantization 之后在用 tensorflow lite fully quanitze 并没有什么区别。具体实现要看底层代码了。
综上,只要你想 fully quantize 一个模型,那就必须手动插入 fake quant node 去训练。我建议你先放一放 graph transform,手动插入训练成功以后直接用 tensorflow lite 转化试试。
2019-2-5 02:09

OnceJune: 回复 Zongjun :

看了一下,确实是好简略一段啊……A full guide to optimizing for quantization is beyond the scope of this guide, but one thing that can help is using the FakeQuantWithMinMaxVars op after Conv2D or similar operations during training. This trains the min/max variables that control the range used for quantization, so that the range doesn’t have to be calculated dynamically by RequantizationRange during inference.我改改建图的脚本试试吧。多谢回答~~
2019-2-11 11:26

OnceJune: 回复 Zongjun :

今天上班又看了一下,发现有一个 experimental_create_training_graph 的等价方法,可以定义 scope,应该可以作为一个解法。把支持的 ops 放在一个 scope 里面,不支持的比如 lstm 放在另一个里面,然后只 transform 支持的。
2019-2-11 15:38

Zongjun: 回复 OnceJune :

嗯,你说的这个我也可以尝试一下哈!
2019-2-12 03:33

OnceJune: 回复 Zongjun :

另外看到您下面的回复中提到了 quant 的论文,可以发一下链接吗?我的理解比较粗,想再看一下细节。另外 tacotron 我做了 performance profiling,cost 的大户还是在 lstm 上面……最后可能还得手动去做 quant。
2019-2-13 14:33

Zongjun: 回复 OnceJune :

具体论文有两篇,一篇是谷歌原文,一篇来自 qualcomm,讲的都是 Tensorflow 的量化。谷歌原文讲的很系统,qualcomm 直接上简单易懂的公式。我的量化公式来自 qualcomm 的论文。谷歌原文:[1712.05877] Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference
qualcomm 论文:[1803.08607] A Quantization-Friendly Separable Convolution for MobileNets
2019-2-14 05:00

Zongjun: 回复 OnceJune :

lstm 的话,我个人认为目前应该还不存在一个用 TFlite 完全量化的方法。上个礼拜有人在 GitHub issue 里还问到了。链接:Can LSTM be fully quantized for inference? · Issue #25563 · tensorflow/tensorflow · GitHub
他用的是 lite/experimental/examples/lstm/unidirectional_sequence_lstm_test.py 这个文件去量化,做到 unstack 这一步的时候卡住的。谷歌应该还在做这一部分吧。
2019-2-14 05:05

sunyanli: 回复 OnceJune :

您好,我加了您的大神的聊天记录,看到您有用到手动插入伪量化操作,想问问您,你手动插入伪量化是如何操作的?我现在就是想单独分层量化模型,就是只量卷积层,或者只量化全连接层。所以我想再将伪量化节点加入到我自己想量化的地方。所以想请假一下您,希望可以得到您的指点。谢谢。
2019-3-22 22:45

sunyanli: 回复 OnceJune :

您好,想请问一下您,如何手动插入 fakequantnode 节点啊?我想在在自己想量化的地方加入伪量化节点,但不知道如何操作,请您知道一下。谢谢。
2019-3-26 08:57

hzq:

您好,非常感谢楼主的以及其他各位大佬的经验,帮助我这种小白学到了很多,少走了许多弯路。。我有个问题想咨询下您:我现在用 tensorflow 的 “toco” 指令 将 pb 转 tflite,这个命令是不是必须用 --input_shapes 指定输入的维度呢? 我的模型的输入是不定长的 tensor [1,32,None,1],是用来做 OCR 的模型。
如果我不指定维度的话,是能转化成 tflite 的,但是在 load tflite 模型的时候报错,Unexpected failure when preparing tensor allocations: tensorflow/lite/kernels/conv.cc:235 input->dims->size != 4 (0 != 4) Node number 0 (CONV_2D) failed to prepare. 还望指导,谢谢。
2019-9-6 10:10

hzq:

我还有个问题,我按 deeplabv3 作者的源码进行了 fake quantization training,在将 frezee 好的 pb 模型转为 tflite 以进行最终量化时,出现了一个错误:quantized aware trainning error:
Unimplemented: this graph contains an operator of type “Cast” for which the quantized form is not yet implemented. 难道是 cast 操作不支持量化吗,我的转化脚本如下:
converter = tf.lite.TFLiteConverter.from_frozen_graph (‘./frozen_inference_graph.pb’,[“ImageTensor”],[“SemanticPredictions”], input_shapes={“ImageTensor”:[1,257,257,3]})
converter.inference_type = tf.lite.constants.QUANTIZED_UINT8
converter.quantized_input_stats = {“ImageTensor” : (0., 42.5)}
converter.default_ranges_stats=(0, 6)
tflite_quantized_model=converter.convert ()
open (“./frozen_fake_qnt.tflite”, “wb”).write (tflite_quantized_model)
期待您在不忙的时候给我些建议,谢谢楼主。
2019-9-6 16:16

月下小黑锅:

您好!我在使用伪量化的过程中,将模型冻结成 pb 模型的时候出现

(0) Not found: Key mobilenetv2/res5_2/res5_2/add/activation_AddV2_quant/max not found in checkpoint
[[node save/RestoreV2 (defined at C:\ProgramData\Anaconda3\lib\site-packages\tensorflow_core\python\framework\ops.py:1748) ]]
(1) Not found: Key mobilenetv2/res5_2/res5_2/add/activation_AddV2_quant/max not found in checkpoint

这个问题,想问一下大家有什么解决方案吗?
2020-1-9 15:56

Aluds:

您好:
我目前有加入"tf.contrib.quantize.create_training_graph (input_graph=graph, quant_delay=delay_step) “進行 fake quantize 訓練,但結果不是很好,剛好有看到有以下這個說法"先训练 float,在训练好的 float 上微调 quant8,即尽量在 float 网络收敛后再进行量化训练”,我目前以 float 的格式訓練後得到 weight (.data, .meta, .index),想請問如何接著使用量化訓練,因為好像沒辦法直接加入"tf.contrib.quantize.create_training_graph (input_graph=graph, quant_delay=delay_step) "接著訓練,麻煩你了
2020-2-14 16:18

以上,帖子点评结束。


杨 7277:

请教一下大神,quantized_input_stats 中的 mean/stddev 的值的设定,有什么规律吗?如何正确 快速的接近合理的值?
2019-4-15 16:50

Zongjun: 回复 杨 7277 :

看这个链接:https://stackoverflow.com/questions/54261772/tensorflow-lite-toco-mean-values-std-values
这个公式是谷歌官方的回答。
2019-4-18 01:50

杨 7277: 回复 Zongjun :

不好意思回复晚了,多谢大神的分享,我学习一下
2019-4-25 15:43

Zongjun: 回复 杨 7277 :

客气。这个也是我好不容易才搞来的公式。。。
2019-4-26 00:45


内容转换自帖子点评。

您好,想请教您一个问题,我现在参照着官方的 mobilenet 训练例子在 mnist 里面加入了伪量化训练代码,但只加了 Create_training_graph 那部分,然后跑出了模型,但是在用工具转化 tflite 的时候,报错说不知道 FakeQuantWithMinMaxVars 的数据类型,这个是不是我训练的时候代码写错了?
我看了官方的两篇论文,还有各种社区的各种问题,但还是不确定步骤对不对,您是怎么做的怎么找的资料啊?还有什么我可以找的资料吗?或者您能把你的例子给我看一下吗?非常感谢~


九幽,发表于 2018-11-14 11:32:14

首先,我记得官方的 mobilenet 是用 slim 写的吧?slim 我没用过,但是 fake quantization 本质应该是一样的。
我下面写的是用纯 tensorflow(非 slim)做 fake quantization:

  1. 你的一个明显错误是,没有使用 create_eval_graph。你要创建两个 graph,一个只用来 training,在这个 graph 上使用 create_training_graph (). 另一个 graph,只用来 inference,在这个 graph 上使用 create_eval_graph (). 最后,Freeze 这个用来 inference 的 graph,再把它传给 toco。

  2. 使用 toco 需要 linux machine,linux 比较 stable。你要在这个 linux machine 上安装 tf.nightly 版本的 tensorflow,再使用 toco。(你的报错也有可能来自这一步。但是第 1 步是必须做的,不管用的什么版本的 tensorflow。)

  3. 我自己的例子不能贴出来,是公司的 project。但是,谷歌有一个现成的例子,代码非常基础并且清晰。例子链接。 你打开这个目录以后,先看 train.py,第 157 行(create_training_graph)。再去看 freeze.py,第 133 行 (create_eval_graph)。这个相当于,在 freeze 的文件里包含了 create_eval_graph。你可以发现,create_eval_graph()上面是调用 create_inference_graph()这个函数,显然,create_inference_graph 这个函数重新写了一个 graph,而不是用你 create_training_graph 的那个 graph。run 完 freeze.py 以后,生成的就是.pb 文件。可以直接把这个.pb 文件给 toco 转化了。(注意区分 create_eval_graph () 和 create_inference_graph ()。前者是现成的可以直接调用,后者是你自己写的一个普通 function。)

  4. 还有一个注意事项就是顺序,这个我在最开始的 post 里提到过了。create_training_graph,要出现在 cross_entropy 之后,tf.train.GradientDescentOptimizer 之前。而 create_eval_graph 则是出现在创建完 inference graph 之后,load checkpoint 之前。load checkpoint 之后是 freeze graph,freeze graph 用的是 graph_util.convert_variables_to_constants()。

我就是从这个例子学会怎么做 quantization-aware training 的。至于谷歌官方的 documentation,我实在是不敢恭维。(当然 paper 我也读了,更多的是从中学到背后的数学原理。)


Zongjun,发表于 2018-11-15 06:19:41

九幽:

嗯嗯嗯,读论文只是知道个原理,真的不知道具体怎么做的,,,我还刚开始学用 tf,好多程序源码可能看了但是并不能自己总结出用法规律啥的,身边还没有人问,就在这个问题上卡了好久,,,
经过这么多天的查找看代码啥的,我就是隐隐有些思路,知道两个 create 都要加,但就是不知道怎么加,果然看你说了之后,知道了这里面还是有不少坑的,是我这种小白不能看出来的,,,
非常感谢您的回答,帮了大忙了,思路瞬间感觉清晰了,我之前也是看了您上面的回答才去找的相关资料,我之前只找到了 mobilenet 那个,确实里面看到了 slim,我也觉得应该不是一种方法,不过也算是参考之后完成了一半的内容。
您的回答非常清晰,十分感谢!~碰到您这样做过量化又很会讲解描述的人,我这个小白实在幸运,多谢~
那我就不改那个错误了,先改正代码,照着您说的实现一下,希望可以完成,,再次感谢!
2018-11-15 12:26

九幽:

看到了那个例子,演讲的那个,我之前也看到了,也照着写了,但是就是因为着急和迷惑吧,就只看了有 create_training_graph 的部分,其他的没分析出来,,,我继续看去,,,
2018-11-15 12:29

Zongjun:

不用客气哈。对,那个例子的 create_eval_graph 藏的比较深,在 freeze 里面,相当于两件事儿在一个文件里做。Good luck!
2018-11-16 01:14

九幽:

回复 Zongjun :我仔细看了一下 train.py 和 freeze.py,这两个文件一个是训练,一个是推理 (验证:test)?就是在 train.py 里用 create_training_graph () 生成了训练图,然后 freeze 用 create_inference_graph () 重写了一个推理图,又用 create_eval_graph () 调用,生成最终的推理图?create_inference_graph () 这个只是给了一个图的定义是吗?我看它做了一个 output 给后面的 inference,为什么要加 create_inference_graph () 呢?可以就直接在保存 ckpt 之前加上 freeze.py 里面 create_eval_graph () 那一部分吗?
我 tf 语法比较差,刚接触没多久,,,只大概看懂了整体结构,但内部的关系还没太清楚,,,我做的项目是 keras 写的物体检测和分类训练,网络和训练是分开的,但训练里面是训练和验证一起的,结构上感觉跟 speech 这个例子差不多,但是 speech 是把训练和验证分开了是吧?
就是训练生成.pb 和.ckpt,然后验证推理的部分和 frozen 一起放到了 freeze 里是吗?我也可以把两部分放在一起是吧?就像我上面想的,不知道 create_inference_graph () 这个的含义,一直也不太清楚 eval 这部分该怎么和我之前写的训练的代码结合起来,,,
然后我试了个简单的例子,就是 mnist,它就两部分,一个是建立网络,然后就是训练,我把 create_training_graph 放到了建立网络里:

........
finaloutput = tf.nn.softmax (tf.nn.dropout (dense_layer2, keep_prob), name="softmax")
    g = tf.get_default_graph ()
    with tf.name_scope ('cross_entropy'):
        cross_entropy_mean = tf.reduce_mean (tf.nn.softmax_cross_entropy_with_logits_v2 (labels=y, logits=finaloutput))
    tf.contrib.quantize.create_training_graph (input_graph=g,
                                              quant_delay=0)
    optimize = tf.train.GradientDescentOptimizer (1e-5).minimize (cross_entropy_mean)
    prediction_labels = tf.argmax (finaloutput, axis=1, name="output")
    correct_prediction = tf.equal (tf.argmax (finaloutput, 1), tf.argmax (y, 1))
    accuracy = tf.reduce_mean (tf.cast (correct_prediction, "float"))

然后看你说的 eval 的放置位置注意,就是把 eval 放在第二部分的训练那里吗?因为模型保存部分在训练那里。
还有个问题,这个 eval 就是生成一个推理图吗?推理图和训练图是一样的吧?只是参数不一样,结构一样?这个的作用具体是什么呢?量化推理模型?
2018-11-19 17:43

Zongjun: 回复 九幽 :

freeze.py 没有在做实际的 testing,它只是生成了用于 testing 的那个 graph(create_inference_graph ()),它是一个图的定义。这个图将来可以用来做推理。可以理解为,训练图训练出来的参数保存在 checkpoint file 里,freeze.py 把这些 checkpoint file load 了进来,传给了用来 inference 的这个图。这里必须 create_inference_graph (),直接在训练图上调用 create_eval_graph () 是不对的。
对,你也完全可以把创建推理图和 freeze 放在一个文件中。eval 并不是生成推理图,生成推理图是 create_inference_graph (),eval 只是把这张推理图转换成了 tflite 能够识别并量化的格式。eval 要出现在 Load checkpoint 的地方而不是 save checkpoint 的地方。
2018-11-20 05:30

Zongjun: 回复 九幽 :

所以总结一下就是这样的:
1.创建训练图,其中加入 create_training_graph (),训练。把训练好的数据存在 checkpoint file 中。
2. 创建推理图,其中加入 create_eval_graph ()。把训练好的数据导入推理图中,即 load checkpoint。
3. Freeze 生成.pb file.(演讲的例子把 2,3 合成了一个文件中做。)
4. 在 linux machine 上把这个.pb file 转化成.tflite file。
5. 可以用 tflite python API 的 interpreter 读入这个.tflite 模型然后做 testing.
2018-11-20 05:40

九幽: 回复 Zongjun :

恩恩,这样清晰不少了,感谢~我继续写试试~
这些你都是通过读例子程序看出来的吗?还是程序和论文一起分析出来的,还是找到了很多相关资料呢?我看了好多还是不确定,分析不出来…
2018-11-20 10:09

九幽: 回复 Zongjun :

还有一个问题,我刚把 speech 的例子跑出来了,然后用 toco 工具转化了 tflite,freeze 运行出来的.pb 文件是 3.7M,然后 toco 转化出的 tflite 大小也是 3.7M,这样对吗?还是说生成量化的 tflite,是会变成 pb 文件的 1/4?
我用的指令,bazel 的,还是说要按你的方法,下载 tf.nightly 写程序转化?
我用你说的 Netron 工具看了转化前后的两个图,结构一样,只是转化之后出现了 fused 层,我觉得图应该是对的
2018-11-20 17:52

Zongjun: 回复 九幽 :

对,这些操作过程完全是读这个例子的程序看出来的,我从论文获得的只是它背后的数学原理。应该是 1/4,我猜你训练的时候是 python train.py。你应该是 python train.py --quantize=True. 你得告诉这个网络你要求它做 fake quantization 啊,不然它默认是不进行伪量化的。
2018-11-21 01:17

Zongjun: 回复 九幽 :

bazel 完全可以用。不用非要我说的那样,我说的是针对用 python 的玩家们。但是即便用 bazel,我也推荐用 tf.nightly 版本。Netron 打开发现结构一样就说明你没用伪量化。fake quantization 以后,你会看到.pb 文件中会出现 FakeQuantWithMinMaxVars node, 而.tflite 中是没有的。在 Netron 中展开每一个 node 看细节,信息量很大的。我自己写过一个 tensorflow 的 kernel,然后自己手动量化(用 Paper 中的公式),然后拿过来 netron 中显示的量化 min,max,用我的 function 输出的结果和 netron 中显示的量化结果一致。证明量化成功。
2018-11-21 01:23

九幽: 回复 Zongjun :

哦哦,这样啊,那就是了,我程序还是读的太浅,,,我之前只加了 create_training_graph 的那个 mnist 训练出来的 pb 我看了,是有 FakeQuantWithMinMaxVars node 的,应该就是这个例子我没开了,,,
你厉害啊~还可以自己手动量化,这感觉就得程序语法很会用,然后原理都清楚才能完成了,,我哪个都不行,,慢慢练吧,谢谢啦~我再试试去~
2018-11-21 10:34

九幽: 回复 Zongjun :

我打开量化了,图确实变了,出现了 FakeQuantWithMinMaxVars node,但就是 toco 转化的时候报错,说:
Unimplemented: this graph contains an operator of type (Unsupported TensorFlow op: DecodeWav) for which the quantized form is not yet implemented. Sorry, and patches welcome (that's a relatively fun patch to write, mostly providing the actual quantized arithmetic code for this op).
里面的 DecodeWav 运算符的量化形式还没实现,,,就是现在 tf 有一些层还没法实现量化是吧?
2018-11-21 11:21

Zongjun: 回复 九幽 :

你报这个错说明你的 toco 指令写错了,你在 toco 指令里加上这两个指令:
--input_arrays=Reshape_1 --output_arrays='labels_softmax'
这样你是从 reshape_1 这一层进入转换,前面的会忽略掉,毕竟你已经 train 完了,前面的没啥用了。output_arrays 就是对应你的 graph 的出口。所以最后 toco 应该长这样子:

bazel run tensorflow/lite/toco:toco -- \
--input_file=/tmp/my_frozen.pb --output_file=/tmp/my_tflite.tflite \
--input_shapes=1,49,43,1 --input_arrays=Reshape_1 --output_arrays='labels_softmax' \
--inference_type=QUANTIZED_UINT8 --mean_values=0 --std_values=2 \

2018-11-22 04:04

单人寸: 回复 Zongjun:

前辈您好!我想问一下我看了这几个例子,对于生成训练图和推理图的两条语句加入的位置比较明确,我现在遇到的问题时,现有的 train.py 文件和 eval.py 文件是基于 slim 实现的,我不知道在什么位置加入这两条语句,我试了几个位置,在 train.py 之后,查看了 tensorboard 并没有找到图中的伪量化节点信息,想问一下,如何在一些程序中确定位置加入位置呢,谢谢
2019-4-10 11:09

oliver.peng:

请问转化成 tflite 后的输出是不是没进行反量化,转换前我的输出是 softmax 分布在【0,1】之间,转换后变成了在 [0,255] 之间了
2019-9-19 15:39

oliver.peng: 回复 oliver.peng:

我也是照着层主例子做的
2019-9-19 15:39

oliver.peng: 回复 Zongjun:

hi,我想请教下,我也是用 mnist 来做量化,但是转为 tflite 后,testing 输出在 [0,255] 之间,和原 pb 模型 testing 输出 [0,1] 之间相悖,请问这是因为没有反量化吗?还是 mean_value,std_value 设的不对导致的
2019-9-19 16:16

sanshanxyz: 回复 九幽:

大佬,看了你们的讨论,收获很多,感谢!是这样的,我在做 bert 的 8bit 量化,我用了 tensorflow 的量化训练,加入伪量化节点以后,训练得到了参数 W 和输出 Activator 的 min/max,那么怎么得到输入 X 的 min/max 呢?X 也要 scale 的啊,有点疑惑,希望大佬可以帮我解答,谢谢!
2019-12-10 11:21

月下小黑锅: 回复 Zongjun:

您好!我看了那个例子中的 train.py。在训练的时候会创建一个训练的图通过 g = tf.get_default_graph () tf.contrib.quantize.create_training_graph (input_graph=g,quant_delay=0)。它在验证和测试的时候也是这个图实现的,我在用 mobilenet_v2 训练 5 类花时,发现验证准确率不变,训练准确率一直在上升。我想应该是 is_train=True 造成的原因。然而,例子中就是这么做的,想问问您是怎么解决的呢?
2020-1-18 09:00 回复


根据帖子点评整理。

你好,我想请教下,我使用 toco 工具将一个冻结了的 pb 文件,大概 4.3m,进行转换成 lite 文件

./bazel-bin/tensorflow/contrib/lite/toco/toco \
--input_file=frozen_eval_graph_test.pb \
--output_file=tflite_model.tflite \
--input_format=TENSORFLOW_GRAPHDEF \
--output_format=TFLITE \
--inference_type=FLOAT \
--input_shape="1,626,361,3" \
--input_array=input_image \
--output_array=result  \
--std_value=127.5 --mean_value=127.5 --default_ranges_min=-1.0 --default_ranges_max=1.0

过程中,我发现,只要我模型里面存在下面这段代码

relu = tf.where (tf.less (input, 0),  tf.zeros_like (input), input)

或者

nan_to_zero = tf.where (tf.equal (relu, relu), relu, tf.zeros_like (relu)) 

转换出来后的 lite 文件将会变成 100 多 m,如果去掉的话出来的 lite 文件就正常 4.3m 左右 ,我问了下别人,有人说是现在 tensorflow-lite 的 bug,里面有些操作影响到,我想问下你有没有出现过这个问题,会不会是你上面提到的 tensorflow 版本必须要 tf.nightly 版本才行,我的版本是从 GitHub 上下载源码编译的 2.11 版本.还有一个问题就是,你安装 tf.nightly 版本的 tensorflow 是用 pip install tf-nightly 直接安装的吗?谢谢


快到碗里来(提问者),发表于 2018-11-16 18:18:56

我是按照 官方教程 安装的 tf.nightly,基本和你说的一样,建个环境然后 pip 安装。我是一个 Ubuntu-16.04 的虚拟机。toco 我不是用的 bazel,我用的是 python api. 在这个 tf.nightly 的机器上,打开一个 jupyter notebook,import tensorflow as tf 之后,运行 tf.lite.TFLiteConverter.from_frozen_graph. 如果这句不报错那就是成功了。这句在 python 里就相当于 call 了 toco。

我读了一下你的 toco,你如果是 quantization-aware training 了,那你的 inference type 那里应该是 QUANTIZED_UINT8 而不是 FLOAT.

relu 的那个我木有碰到过,所以可能帮不上忙了。我推荐你下载 Netron.可以直接打开你的.pb 和.tflite 文件,先看一下.pb 文件是不是正常,有可能训练或者 freeze 的时候就出现问题了。Good luck!


Zongjun,发表于 2018-11-17 01:41:34

快到碗里来:

大神,你好,我测试了你说的 Netron 跟使用 tf.lite.TFLiteConverter.from_frozen_graph 去生成 lite,现在基本成功,只要去除一些操作就可以生成量化的 lite 文件.现在还有个问题想请教下,如果模型里面存在 tf.Square 操作,lite 文件生成就会报错,我查了下文档,里面有这么一句话不太理解,在 https://www.tensorflow.org/lite/tf_ops_compatibility 里面的"Here is a list of TensorFlow operations that are usually removed from the graph",什么叫做从通常从图中移除这些操作
2018-11-26 16:48

Zongjun: 回复 快到碗里来:

这个列表里面的操作应该是 toco 自动去删除,替代,融合的。比如 tf.relu,它可以 fuse 进上一层的 conv2d layer 中,成为上一层的输出。tf.Square 应该也是一样的,toco 会去处理掉,这样 tflite 的 model 中就看不到这个操作了。你报的错是啥啊?toco 没有自动处理 tf.Square 吗?
2018-11-27 02:02

快到碗里来: 回复 Zongjun:

你好,我上面的问题基本解决了,现在我生成了一个 lite 文件,用 python 脚本测试,测试代码如下:

interpreter = tf.contrib.lite.Interpreter (model_path="quantized_model.tflite")
interpreter.allocate_tensors ()

# Get input and output tensors.
input_details = interpreter.get_input_details ()
output_details = interpreter.get_output_details ()

image=scipy.misc.imread ("timg (11).jpg")
image_=np.array ([image.astype ('float32')])
print (image_.shape)
print (type (image_))
print (input_details)
interpreter.set_tensor (input_details [0]['index'], image_)

interpreter.invoke ()
output_data = interpreter.get_tensor (output_details [0]['index'])
scipy.misc.imsave ('res.jpg',output_data)

是能顺利运行并输出东西来的,但我交给我朋友做 Android 的在 Android 上运行 lite 文件,他是替换官方的 demo

tensorflow/tensorflow/lite/java/demo at master · tensorflow/tensorflow · GitHub 的文件去测试,报了以下错误:

Caused by: java.lang.IllegalArgumentException: ByteBuffer is not a valid flatbuffer model
                                                                                          at org.tensorflow.lite.NativeInterpreterWrapper.createModelWithBuffer (Native Method)
                                                                                          at org.tensorflow.lite.NativeInterpreterWrapper.<init>(NativeInterpreterWrapper.java:72)
                                                                                          at org.tensorflow.lite.NativeInterpreterWrapper.<init>(NativeInterpreterWrapper.java:52)
                                                                                          at org.tensorflow.lite.Interpreter.<init>(Interpreter.java:114)
                                                                                          at com.example.android.tflitecamerademo.ImageClassifier.<init>(ImageClassifier.java:98)
                                                                                          at com.example.android.tflitecamerademo.ImageClassifierQuantizedMobileNet.<init>(ImageClassifierQuantizedMobileNet.java:38)
                                                                                          at com.example.android.tflitecamerademo.Camera2BasicFragment.onActivityCreated (Camera2BasicFragment.java:334)
                                                                                          at android.app.Fragment.performActivityCreated (Fragment.java:2289)

我想问下,你有没有遇到这个问题或者知道怎么解决吗?有什么好的建议或者能不能分享下你调用 lite 文件的 Android 代码.谢谢大神
2018-12-1 11:52

Zongjun: 回复 快到碗里来:

我训练完的 model 是直接往单片机上放的,所以我没在 Android 上用过。我看了一下你给的链接里的代码,这个报错是说 bytebuffer 不是一个 flatbuffer model(flatbuffer model 是 tflite 的格式)。但是,我发现 bytebuffer 在代码中是输入图片的一个 buffer,里面存的是 image data,并不是 model 本身。所以你同事 Android 的 java 代码可能出了问题。
2018-12-4 02:08

快到碗里来: 回复 Zongjun:

好的,谢谢大神,我看下怎么处理.
2018-12-4 18:02

Zongjun: 回复 快到碗里来:

嗯嗯。不用客气!大神不敢当,只是碰巧平时工作会一些这方面的东西,尽可能帮忙而已。祝你们好运哈!
2018-12-5 02:27

快到碗里来: 回复 Zongjun:

哈哈,我觉得你有空可以写篇博客讲下 tensorflow-lite,现在 tensorflow-lite 的博客太少了,而且都不是很详细,很多都是抄来抄去的.这段时间我都会找时间研究下 tensorflow-lite,现在生成的 lite 文件还没能部署在 Android 跟 iOS 上,遇到问题到时还要请教你
2018-12-6 15:12

Zongjun: 回复 快到碗里来:

嗯嗯,模型都有了,部署在 Android 上应该就不难了,毕竟是 java 哈。
2018-12-7 02:32

快到碗里来: 回复 Zongjun:

你好,这几天我终于把 lite 文件部署到 Android 上,但经过测试,量化后的 pb 文件比量化后的 lite 文件速度更快,这是什么原因你知道吗?还有,就是现在官方不是说,现在 tensorflow-lite 只支持 mobilenet 及少量模型,但我们可以将我们的模型转换成 lite 文件,所以,我好奇的这个官方说的支持是不是指对 mobilenent 等这些模型进行优化,加速计算,而不是指单纯可以使用.
2018-12-13 20:38

Zongjun: 回复 快到碗里来:

具体哪些能转化成 tensorflow lite 不是看模型,而是看你的模型里面用的哪些 op。如果有 tflite 不能转化的 op,这个模型就不能被转化。能被 tflite 转化的 op 如下:https://www.tensorflow.org/lite/tf_ops_compatibility
量化后的 pb 文件指的是有 fakequant nodes 但还没被 toco 转化的.pb 文件吗?这个阶段它还是 floating point 的 model,所以称不上是被量化了。而.tflite 文件才是真正被量化了的 model。你的这个速度问题,我在网上见过类似的。所以在我最一开始的回答中就避开了速度的事儿,只是分析了大小变化。因为理论上应该是量化了更快,但是涉及到 Android 的内部实现问题,这方面我不懂,所以我不确定哪里出了问题。我能想到的是:tensorflow lite 要用最新版本,就是我之前说的用 tf.nightly 版本的 tensorflow。某些 kernel(比如 conv2d),新版本的 tensorflow lite 更高效。
2018-12-14 05:41

快到碗里来: 回复 Zongjun:

你好,我说的量化后的 pb 文件是指用 tensorflow/tools/quantization/quantize_graph 工具 (这个应该就是你指的旧版本的 tensorflow 的完全量化工具),量化后生成的 pb 文件会缩小到原来模型大小的 1/4,跟量化后的 lite 文件一样大.但在 Android 上跑的速度比 lite 文件快,我查了下资料,有人说有些操作确实 lite 会比 pb 跑得慢很多,如果这样的话,那感觉现在使用 lite 还不如使用我上面说的完全量化后的 pb 文件,模型大小一样,速度却更快.
我发现了一个问题,就是官方生成 lite 文件,里面的计算过程都是 uint8 格式,但我用
converter = tf.contrib.lite.TocoConverter.from_frozen_graph (‘tflite_graph.pb’,[“input_image”],[“result”], input_shapes={“input_image”:[1,350,202,3]})
converter.allow_custom_ops = True
converter.post_training_quantize = True
tflite_quantized_model=converter.convert ()
转换过来的 lite 文件里面都是 float32 格式的计算过程。
还有,我测试了,发现不管 pb 文件还是 lite 文件,量化后速度没提高,反而降低了.
2018-12-14 17:16

HuiyuanZhuo: 回复 Zongjun:

您好,我也有一些小问题想请教一下您。这两天我将 saved model 转化成了 tflite 模型,我使用的是伪量化(pos_training_quantize=True),然后我在笔记本的 CPU 上测试了一下速度,25 张图片,速度反而比 saved model 测试要慢很多。
我把时间打印了出来,每次 interpreter.invoke () 的时间在 7 秒左右。saved model sess.run 的时间在 0.53 秒左右。我的问题主要是两方面:

  1. 官方上 有句话:At inference, weights are converted from 8-bits of precision to floating-point and computed using floating point kernels. This conversion is done once and cached to reduce latency. 就是说即使伪量化不是真正的量化,但是 8bit 到 float 的转化只进行一次,可以减少延迟。
    那么我每次 invoke 的时候,是不是都在进行这样的转化,还是说我在 tf.contrib.lite.Interpreter 命令(相当于 restore weights?)之后就已经完成了转化,之后的 invoke(相当于 sess.run)只是一个前向传播的过程?

  2. 如果 invoke 只是 sess.run 的作用,那么为什么 invoke 在 CPU 上需要那么多的时间?是因为 CPU 限制(应该不太可能吧)?因为这不是 fully quantized?还是其他什么原因?

希望可以尽快得到您的答复,我把代码附上:

def post_training_convert (tag_set, signature_key, tflite_model_path):
    """
    quantize weights into 8-bit. at inference, weights are converted from 8-bits of precision to floating-point
    and computed using floating point kernels. This conversion is done once and cached to reduce latency.
   :param tag_set: Set of tags identifying the MetaGraphDef within the SavedModel to analyze. default:'serve'
   :param signature_key: Key identifying SignatureDef containing inputs and outputs. default DEFAULT_SERVING_SIGNATURE_DEF_KEY
   :return: .tflite model
    """
    converter = tf.contrib.lite.TocoConverter.from_saved_model (saved_model_path, tag_set = tag_set, signature_key = signature_key)
    converter.post_training_quantize = True
    tflite_post_quantized_model = converter.convert ()
    open (tflite_model_path, 'wb').write (tflite_post_quantized_model)
    print ('tflite model transformation done!')
    print ('format: ', converter.inference_type, 'post_training_quantize: ', str (converter.post_training_quantize))

# post_training_convert (['SERVING'],'predict_signature',tflite_model_path)

def image_preprocess (image, resized_height, resized_width):
    image = tf.image.resize_images (image, (resized_height, resized_width))
    image = tf.image.per_image_standardization (image)
    image = tf.cast (image, tf.float32)
    image = tf.expand_dims (image, axis = 0)
    return image

def main ():
    """
    use ./lite model to predict
    """
    with tf.Graph ().as_default () as g:
        with tf.Session () as sess:
            interpreter = tf.contrib.lite.Interpreter (model_path = tflite_model_path)
            interpreter.allocate_tensors ()

            input_details = interpreter.get_input_details ()
            output_details = interpreter.get_output_details ()
            print ('intput_details: ', input_details)
            print ('output_details: ', output_details)

            input_shape = input_details [0]['shape']
            print ('input_shape: ', input_shape)
            val_file_list = open (val_file, 'r').readlines ()

            count = 0
            time_count = []
            f = open ('./result.txt', 'a')
            for file in val_file_list:
                file_name, file_label = file.strip ('\n').split ()
                image = cv2.imread (os.path.join (images_root_path, file_name))
                if image is None:
                    print ('Warning, fail to read image: ', file_name)
                    continue
                image = image_preprocess (image, 224, 224)


                image_array = sess.run (image)
                start_time = time.time ()
                start_time1 = time.time ()
                interpreter.set_tensor (input_details [0]['index'], image_array)
                print ('set time: ', time.time ()-start_time1)
                start_time2 = time.time ()
                interpreter.invoke ()
                print ('invoke time: ', time.time ()-start_time2)
                start_time3 = time.time ()
                output_data = interpreter.get_tensor (output_details [0]['index'])
                print ('out time: ', time.time ()-start_time3)
                print (output_data)
                end_time = time.time ()
                duration = end_time - start_time
                count  = 1
                time_count.append (duration)
                f.write (file_name   ' '  
                        file_label  ' '  
                        str (output_data [0][0]) ' '  
                        str (round (duration, 4))   '\n')
            print ('total processing images: ', count)
            print ('average processing time: ', np.mean (time_count))
    f.close ()
main ()

2018-12-26 17:22

Zongjun: 回复 HuiyuanZhuo:

我建议您先看一下我在这个贴子的第一个回复哈。post_training_quantize=True 并不是我们所说的伪量化。真正的伪量化叫做 fake quantization 或者 quantization aware training. post_training_quantize 是我回答中提到的第二个方法。伪量化链接:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/quantize
所以,你的 model 并没有 fully quantized,依然可以看作是一个 float32 的 model.还有就是,如果是伪量化了,toco 那里的代码应去掉 post_training_quantize=True, 加上 inference_type=QUANTIZED_UINT8. 希望能帮到你。
2018-12-28 02:16

Zongjun: 回复 HuiyuanZhuo:

:所以,你的问题也就不成立了,如果是真正的伪量化,不存在来回转化的问题。我推荐你下载 Netron 这个小软件:GitHub - lutzroeder/netron: Visualizer for neural network, deep learning and machine learning models 。它可以帮你可视化整个 graph 包括其中的参数。你用 netron 打开一个伪量化的.pb 文件,你会发现里面有很多 fakequant nodes (我猜你的并没有,因为你用的是 post_training 那个方法), 再用 toco 转化以后,是很干净的.tflite model,其中所有的量化参数都有一个上下限,参数也都是 uint8 的了。这个就是完全量化好的 tflite 模型。和 post_training 是相互独立的两种方法。(注:伪量化是一开始训练时发生的事情,具体步骤这个帖子里详细讲了哈)
其次,我说一下我对 interpreter 的理解,您辩证的看哈。我认为 interpreter 会过一遍你的 graph,然后去调用你 graph 中每一个 op 所对应的 tensorflow lite kernel。invoke 时用这些 kernel 来做实际运算得出最终结果。而一个 tensorflow session,它就是简单的在做 computation,比如算一下两个矩阵相乘。所以 interpreter 是要比一个 tensorflow session 复杂很多的,速度上来说应该不可比。
2018-12-28 02:46

HuiyuanZhuo: 回复 Zongjun:

啊哈,这个帖子的回复之前我都看过了,包括您给的链接。可能回复的时候区分成了完全量化和伪量化(pos_training…)。ok 这不是重点。嗯嗯,我也认为 interpreter 不是 session,它更像在 load weights。然后 invoke 更像 session run,调用 weights 计算结果?
接着您的回复,您说” interpreter 是要比一个 tensorflow session 复杂很多的,速度上来说应该不可比。“。这句话不是很理解。为什么使用 pos_training_quantized=True 的方法转化的 tflite 会比 pb 慢?也比 sess run 慢。这是正常的现象吗
2018-12-29 10:47

Zongjun: 回复 HuiyuanZhuo:

速度这方面我属实没有仔细研究过,你做的是一个 Android app 嘛?如果你的 app 非常要求速度,我建议你可以考虑一下完全量化的方法,用 uint8 来做计算。如果还是比.pb 慢的话,我感觉你可以去 tensorflow 的 GitHub 填一个 issue 来问问官方为啥.pb 反而比所有的.tflite 都快(你不是第一个跟我说 tflite 慢的,所以我暂且认为它是真的慢。。),速度是否还和具体的模型有关。这些都可以问问,看官方给不给回答。
2018-12-30 13:25

HuiyuanZhuo: 回复 Zongjun:

好的呀~谢谢了
2019-1-2 14:05

HuiyuanZhuo: 回复 Zongjun:

hello,将分类模型使用 fully quantized 的方法转化为了 tflite (int8)模型后,测试精度下降不少。
这里我遇到一个问题,就是 inference_type=QUANTIZED_UINT8 的时候,inference_input_type 也得是 QUANTIZED_UINT8,quantized_input_stats 我设置的是(0,1)。感觉这一步相当于做了 per_image_standardization。所以把这一步放到 tflite 模型外面来做就好了。
另一点,输入图片的时候,还要加一步 tf.cast 将图片转化为 uint8 才能输入模型,否则会报错: ValueError: Cannot set tensor: Got tensor of type 1 but expected type 3 for input 312

我觉得可能就是输入也转化为了 int8,精度才会下降那么多。不知道你怎么看?
2019-1-3 15:15

HuiyuanZhuo: 回复 Zongjun:

我用的 tf 版本是 1.11.0。当然我在 CPU 上测试的时候速度还是不如直接 session 来的快,相差 100ms。牺牲了精度和时间,换来模型减小,好不值当。
2019-1-3 15:17

Zongjun: 回复 HuiyuanZhuo:

  1. 你的输入是图片的话,直接把图片 tf.cast 成 uint8 是不对的。给你一个 stack overflow 的链接:Input image of a fully quantized tensorflow lite model - Stack Overflow 这里,你需要完成对你的输入图片的量化,而不是直接 cast 成 uint8。
  2. quantized_input_stats,是对你的 training data 做完 data augmentation 以后的 mean 和 std。简单的 0,1 很有可能不正确。谷歌官方的 mobilenet 例子用的是 128,128.但是这个和你做过哪些 data augmentation 有关,要自己求的。你可以看一下这个贴子的第二个回复,我有说。
  3. 时间我不知道,但是 fake quantization 是很保精度的,具体跟你的模型相关。我的一个 cifar10 小模型,精度只相差 1%. 其次,即便是真的牺牲了时间和精度,能把模型减小并变成 uint8 来运算是很多物联网设备所必需的,这些芯片的主要要求就是功耗低,用 float32 来运算是很昂贵的。
    2019-1-9 01:32

根据帖子点评整理。

Zongjun:

just in case, 你没有新的问题了对吧?之前那一层楼点评太多了,我无法打开点评的第二页。。应该是卡 bug 了。所以你说了啥我都看不到。。

九幽:

是,我刷新了几次,试了别的浏览器也发现看不到第二页,,,之前说的没啥别的问题,就是重新训练了一遍,打开了伪量化的开关,然后 pb 模型图出现了 FakeQuantWithMinMaxVars node。但是用 toco 工具转化为 tflite 时报错,说 Unimplemented: this graph contains an operator of type (Unsupported TensorFlow op: DecodeWav) for which the quantized form is not yet implemented. 应该是有的层没法量化?还没有完善是吗?还是我这里出问题了?
另外就是那个 create_inference_graph,我自己建立一个推理图的话,就是重新建一个网络结构然后给个分类输出吗?那个 create_inference_graph 我看感觉含义就是说了几个关键的层,然后给了 logits,这个是搭建网络模型的意思?create_model?
我做的目标检测还有画框,然后显示分类和概率,不太清楚这个输出该怎么给,,,
2018-11-26 10:33

九幽:

还有一个,train.py 里面 create_training_graph () 就是建立训练图的意思,然后后面只要 get_default_graph () 来在这一个图里面实现功能就行了呗?但是 freeze.py 里的 create_eval_graph () 不能直接创建出推理图,所以需要自己写一个 create_inference_graph () 函数来创建推理图,然后 create_eval_graph () 只相当于 get_default_graph 的功能来实现你之前说的:给出一个输出作为 tflite 可接受的类型是吗?
create_training_graph 和 create_eval_graph 添加的 FakeQuantWithMinMaxVars 还是只是 create_training_graph 添加的?那 create_eval_graph 有添加什么吗?
如果 FakeQuantWithMinMaxVars 只是 create_training_graph 添加的,那为什么 train.py 里面要加 fingerprint_input = tf.fake_quant_with_min_max_args (
input_placeholder, fingerprint_min, fingerprint_max) 这一部分呢?
2018-11-26 11:13

Zongjun: 回复 九幽:

decodewav 这里,你的 toco 指令里要包含模型的输入和输出名字,这个模型的输入应当是:–input_arrays=Reshape_1,就是从 reshape 那一层算为进入,这样就不会有 decodewav 的报错了。create_inference_graph 就是 create model,我在 logits 后面手动加了 softmax 作为输出。所以我的 toco 输出是–output_arrays=labels_softmax。“labels_softmax” 是我给起的名字。
训练图不是 create_training_graph () 建立的,而是 train.py 中 models.create_model 这个函数建立的。其实,create_training_graph 和 create_eval_graph 都不能自己创建一个图出来,它们都必须有另一个函数为它们创造图,然后它们在各自的图中把操作改成伪量化版本的操作。input 那里它手动加了 fake_quant,我感觉是这个模型的输入需要处理,如果你的输入是图片,那应该不需要这一步,这里我不是很确定。我的模型没有这一步依然是工作的。
2018-11-27 02:34

Zongjun: 回复 九幽:

我感觉你的误区是看见 create_training_graph 就认为它是在 create graph,这个名字起的很有误导性。其实它根本没有创建一个 graph 出来,真正的 graph,每一层的定义都是在 models.py 这个文件中定义的。create_training_graph 只是让一个普通的 graph 在训练时加入伪量化而已。
同理,create_eval_graph 也只是把一个普通的 inference graph 变成伪量化版本,之前训练好的参数会传给这个图。再把这个图给 toco。
2018-11-27 02:38

九幽: 回复 Zongjun:

大神好厉害,每次都回答地直中要害~
恩,decodewav 那里确实换了输入就好了,但是我在程序和用工具来看 pb 的结构都写着输入是 wav_data,你怎么知道这个错误就是要把输入改成 Reshape_1 呢?…
我看普通的 tf 程序创建图是用 tf.Graph (),然后 speech 这个例子创建图是用 create_model 呗?他这个 create_model 是自己根据自己要用的网络在 models.py 先定义出来的呗?就是我现在想像他一样写一个 create_inference_graph () 加进去,因为我就剩下这部分没写进去,所以,我要是根据 mnist 网络自己定义一个 mnist 的 create_inference_graph 的话,就对照着 speech 例子的结构来写?
然后其实还要写一个 models.py 先定义好要用的网络层?…总感觉没这么复杂,因为我只是改下 mnist 的训练代码,让它实现伪量化,我觉得我应该抛掉 speech 的结构,然后只把它的 create_inference_graph 拿过来,然后在里面定义好 mnist 的网络结构,写好后,写 logits = tf.Graph (),然后再加上最后的输出: tf.nn.softmax (logits, name=‘output’),是这样吗?
我觉得我没有看透 create_inference_graph () 里面代码的含义,也不知道放到自己的网络里该怎么定义这一部分…
2018-11-27 11:53

九幽: 回复 Zongjun:

其实,我写 mnist 的伪量化是想测试一下这整个过程还有我用的工具对不对,测试下这种方法的效果怎么样,成功之后再用到我自己的代码里,,,
感觉现在 speech 已经成功测试好了上面那些,我现在是不是直接去想自己的代码该怎么改比较好?mnist 就先不要做了,也没什么帮助了是吗?
我的训练和网络结构代码都是 keras 写的,用的 vggA 网络且只有卷积和池化,然后要实现画框和分类
我现在修改了一部分代码,改了训练的部分,加了 Session 来执行训练,还有 create_training_graph,
感觉 keras 写的没有图,它应该不是用图的形式来计算实现的吧?,,,
你知道 keras 怎么加伪量化吗?还是说伪量化只能用图的形式来实现,用 TF 来实现?我需要把 keras 语句改成 tf 方法来写吗?
2018-11-27 12:16

Zongjun: 回复 九幽:

  1. 咱们这样盖楼,盖到第 10 层就另起一楼哈,别又像上次那样后面的评论全落地成盒了。。。
  2. 关于 decodewav,一开始这里我和你报错一样,所以我知道是啥问题。我解决的方法就是研究了一下这个例子处理 input 的代码,我发现给 toco reshape_1 就行了,不需要前面那些。试了几次才成功的。
  3. 你不直接调用 tf.Graph (),其实是用的 default graph,所以创建图的原理和以前是一样的。这个例子只是把 models.py 单独拿出来了,在一个独立的文件中创建 model,个人感觉这样设计更清晰。对,你其它都写了的话,只要额外加一个你自己的 create_inference_graph () 即可,注意去掉 dropout(如果训练图有的话)。
  4. speech 这个例子你如果吃透了的话,没必要再用 mnist 重复一遍了,核心思想是一样的。
  5. 重点来了,keras 不存在伪量化功能。这个我在 stack overflow 上和别人也讨论过。我同事一开始是用 keras,扔给我量化,我才发现 keras 没这功能。我们才慢慢用起来了 tf 的底层代码。
    6.有一种方法你可以试一下,它不用 tf 的底层代码定义 Model。你查一下 tf.kears.layers。这一套相当于在 tf 中直接调用高层 api。因为伪量化的操作出现在 loss 和 optimizer 那里,这一部分你要用 tf 的底层代码实现。这是和 keras sequential model 的区别。keras sequential model,我是没找到插入 create_training_graph () 的位置。。
    2018-11-28 03:05

Zongjun: 回复 九幽:

我今天学了一下 slim,感觉也很好上手。肯定是比 tf 的底层代码要简单实用一些。fake quantization 的步骤和 speech 的例子也完全一样。我就拿 mobilenet 举例吧:

  1. 第 139 行,在 loss 处插入了 create_training_graph ()
  1. 第 127 行在 build inference model 的函数内部同时插入了 create_eval_graph (),这个只是和 speech 的例子代码顺序不同,本质完全一样。

这样看来,slim 的地位应当是介于 tf 底层代码和 keras 之间的那个谷歌官方框架。感觉 documentation 写的也相当好。推荐你考虑一下哈。(上一层说的 tf.keras.layers 我也用过,同样可以用,看你个人喜好了。)
2018-11-28 08:55

九幽: 回复 Zongjun:

我现在就是 slim 那个和 speech 的例子一起看,结构大体相似,就是 eval 的顺序不一样,,,主要我代码基础也比较差,tf 又是刚接触,所以要查语法,要学使用,然后代码方面的理解力也差一点,所以学的比较慢,对于 keras 这边我的代码也是上边给我的,用 keras 写的,改了一段时间觉得找不到插入点,然后现在把 keras 写的训练那部分代码改掉,用 tf 来写了,但是 loss = tf.reduce_mean (tf.nn.softmax_cross_entropy_with_logits_v2 (labels=y, logits=finaloutput)) 这句话要调用那个 finaloutput,然后去找另一个文件写的 vgg 网络定义,还不知道 keras 写的哪个是输出,,,,
你说的两种方法我先去学一下吧,这两种你觉得我这种基础的大概要花多久啊?感觉最近可能要加班了,,,
2018-11-28 10:34 回复

  1. 对于 decodewav,确实这是输入那里的,就应该去查输入,然后还是要多尝试啊,懂了,对于这种情况,网上查不到,我自己又不确定,判断不出来,又觉得不知道是不是其他地方的错误导致的,就比较迷惑,,,这种情况还是因为踩得坑少,经验少是吗?
  2. 然后对于 create_inference_graph,我还是不太清楚具体什么流程,,,我想写好这部分就仔细研究 speech 或者 slim 的例子吗?感觉他们是写了大体框架,这里就是写的模型网络层级的定义吗?比如 mnist 这里,定义网络是这样的:
def build_network ():
    x = tf.placeholder ("float", shape=[None, 784], name='input')
    y = tf.placeholder ("float", shape=[None, 10], name='labels')
    keep_prob = tf.placeholder ("float", name='keep_prob')
    def weight_variable (shape):
       ......
    def bias_variable (shape):
        ......
    def conv2d (x, W):
        return tf.nn.conv2d (x, W, strides=[1, 1, 1, 1], padding='VALID')
    def max_pool_2x2 (x):
        return tf.nn.max_pool (x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')
    def lenet5_layer (layer, weight, bias):
        ......
    def dense_layer (layer, weight, bias):
        ......

然后 create_inference_graph 里面,就把 conv2d,max_pool_2x2,lenet5_layer,dense_layer 这几层按顺序写一下?然后写 logits = …,和 tf.nn.softmax (logits, name=‘output’)?
这个 logits 是一个建立网络结构的语句?看那两个例子,感觉每个里面都包含了好多东西,要想弄清楚这里怎么写还要看哪些知识呢?


九幽,发表于 2018-11-28 11:04:02

Zongjun:

这样的话,我建议你还是从 tf.keras.layers 开始吧,因为你已经知道 keras 怎么用了,这俩换汤不换药的。至于 slim 嘛,如果 tf.keras 最后出意料之外的问题了,你可以考虑把 slim 当做备选,实在不行再选择 tf 底层代码。(如果你的网络有 batch normalization,我还是建议你用 slim。)
对于 create_inference_graph,这个和你创建训练图是一样的套路。你看这个函数是不是还是调用了 models.create_model () 这个函数。其中 is_training 这个 flag,这里是 False,意思就是说我这里创建的是推理图。我觉得你还是先仔细研究完 speech 这个例子再看其他的哈。
logits 就是图的输出,最后把它传给 softmax 用于 inference。
你把创建图想成张量的流动,上一层的输出是下一次的输入。和 keras 的 sequential model 原理是一样的。
2018-11-29 03:47

zyc4me: 回复 Zongjun:

你好,我想请教你一个问题,就是关于卷积之后得到的 int32 的的结果,量化到 uint8 的方式。 我在 gemmlowp 的 example 中看到的矩阵相乘得到 int32 之后再根据论文公式(7)得到 uint8 是看明白了,但是当我把 tflite 模型放入 netron 查看的时候,发现没有 relu6 层,而下一层卷积的输入(也就是上一层卷积的 输出)的范围是(0-5.9997),我自己认为这个输出得到的就是论文中提到的 S3 和 Z3,但是我按照论文中的公式得到的 Q3 并不在 0-255 范围,我觉得是我忽略了有关 relu 的一些操作,你能指点我一下吗?谢谢!!
2018-11-29 10:45

九幽: 回复 Zongjun:

好的,keras 这块我还得好好看一下,,,说到底还是没看透例子,感觉我的问题就是写程序的问题
就是要先了解好整个原理和逻辑,然后根据逻辑来写 create_inference_graph 这部分,可能就是语法的问题了,,,我先看懂写写试试,有问题再说吧,感觉写程序总是不开窍啊,好像是没有理解本质上的东西,,总觉得理解之后编程应该是很简单的一件事,,,,
2018-11-29 10:51

九幽: 回复 Zongjun:

看了 tf.keras.layers,这个就是 keras 的网络结构定义呗?这个我对比了一下我代码里的 vgg.py 的网络定义,看起来是一样的,我的都是 keras 写的,
Conv2D (16, (3, 3), name=‘conv1_1’, **conv_args),
Conv2D (16, (3, 3), name=‘conv1_2’, **conv_args),
MaxPool2D ((2, 2), strides=(2, 2), name=‘pool1’),
这样的,我的网络都是卷积和池化,没别的层,然后用 model.compile 来配置学习过程参数的,如果加量化的话,你的意思是 loss 这里换成 tf 底层代码吗?就是 model.compile 这里,因为这里有 loss 和 optimizer
2018-11-29 15:54

九幽: 回复 Zongjun:

还有就是训练的部分,我的代码因为都是 keras 写的,所以训练感觉也要改成 tf 吧?因为伪量化是在图里面加了节点,然后还要保存成 pb 文件啥的,所以这部分是不是都得按照 tf 的底层代码来写啊?
2018-11-29 19:39

Zongjun: 回复 九幽:

嗯嗯。可以考虑强化一下你的 python 功力哈,对看或者写代码都是有帮助的。
对,loss 这里要换成 tf 底层代码的,这里要加 create_train_graph ()。都是 conv2d 和 maxpool 的话就好办了,应该问题不大。训练你可以先不改,试一试。要是报错了,看看是啥错,不行再转为底层代码。到了这一步以后就得具体情况具体分析啦。祝你好运!
2018-11-30 02:01

Zongjun: 回复 zyc4me:

relu6 应该是属于 fuse 进上一层的操作范畴吧。你看一下应该出现 relu6 的那一层的 output id 是不是写着 relu6?看你的结果 0 到 5.9997 应该就是这样的了。因为 relu6 的范围就是 0 到 6 啊。所以,你应该是带错变量了。
2018-11-30 02:07

Zongjun: 回复 zyc4me:

我推荐你看一下这一篇 paper: A Quantization-Friendly Separable Convolution for MobileNets
session 2.1 TensorFlow 8-bit Quantization Scheme, 我当时根据这个 session 的式子在 jupyter notebook 中手动实现了 tensorflow 的卷积和量化,亲测可行。
2018-11-30 02:10

Zongjun: 回复 九幽:

我更正一下,我之前说 slim 是谷歌官方支持的库,这个好像不对。貌似在 contrib 文件夹下的都是非官方的?我今天看到一篇文章说 slim 有自己的所有者。嗯,不过问题不大哈,好多 project 都在用的,依然可以作为你的 B 计划。
2018-11-30 03:07

zyc4me: 回复 Zongjun:

感谢回复,大神总是半夜干活啊。。 我看了 A Quantization-Friendly Separable Convolution for MobileNets
session 2.1 TensorFlow 8-bit Quantization Scheme 这个,是的,这个 session 几个公式没有问题,可以计算得到 uint8 的输出,这和 google 论文中的公式(7)是一致的,我的疑问是,这个 u8 输出是卷积层输出的对吧,然后它再经过 relu6 之后变成什么样子了呢?(我解析了量化训练的 tf ckpt 模型,发现里边有每个卷积 weight 的 min/max,还有每个 relu6 的 min/max(min 都是 0,max 基本都是 5.9997,也有 4.x,5.x 的,这个符合 relu6 的输出),但是我要计算卷积层的输出的话,应该是要知道卷积层输出的 min/max 吧,google 论文里说它是在训练过程中统计这个值,但是模型里并没有,只有 relu6 之后的 min/max),所以如何获得这个卷积输出的 min/max 呢,是类似 tensorrt 使用一些样本跑这个模型,来自己统计吗?还有 google 论文里有提到这个 fuse acvitation function,这个到底是怎么 fuse 的呢?(因为 netron 里 tflite 确实没看到有 relu 层,但是 conv 层的 output id 里边是有 relu6 的),简而言之,就是 tflite 它是如何前向的呢?
2018-11-30 09:57

Zongjun: 回复 zyc4me:

啊,不是大神不是大神,只是略知一二而已。我在美国,有时差的。这就对了,relu6 是被 fuse 进上一层了。卷积的 min 和 max 你如果用 netron 的话,.tflite 和.pb file 中都有的啊。在.pb file 中,conv2d layer 上一层会有一个 fakequant 的 node,里面就有 conv2d 的 weights, bias 和对应的 min /max,你可以找找看哈。应该是不需要自己统计的,我发现 netron 之前,尝试过自己统计,但是没成功。。
relu6 怎么 fuse 的,我建议你看一下 pete warden 的博客。tflite 具体怎么实现 fuse 的,这得看源代码了。你研究的好细致啊!
2018-12-1 01:09


根据帖子点评整理。

恩,语法编程能力啥的都得提高,,,我也觉得后面的问题涉及到我代码怎么写的,这都是细节问题,就得具体分析了,后面不好问了啊,,,
至于训练这部分,keras 保存的是 hdf5 模型,要保存成 pb 应该就要改成 tf 写的了吧?要用图,,,
keras 我之前查没看到直接保存成 pb 的,只有 hdf5 转化为 pb 的,,,


九幽,发表于 2018-11-30 10:44:19

九幽:

那个 create_inference_graph 好像知道怎么写了,其实明白过来感觉之前的问答都比较边缘化,,,我是一直没清楚这个推理图该怎么创建,里面该怎么写,然后今天又综合考虑了一下,感觉训练图和推理图应该是一个图啊,也就是你说的他们都用的 create_model,所以他们就是一样的网络结构,不是我之前问的说 create_inference_graph 里面是网络层结构定义,这个定义是在 create_model 里的,而我不明白的地方其实是 create_model 之前的那些语句,结果那些语句原来是数据处理,跟网络结构没关系,,,,
所以我的 mnist 里面就应该直接调用之前训练用的 build_network,那里面是网络定义,所以在推理图的部分应该再次调用 build_network,然后直接用训练时用的 output 给 create_eval_graph 来用
2018-11-30 16:01

九幽:

我还是在 mnist 里面练习了一下,想验证下上面说的对不对,但是怎么改都报错,我把所有步骤都放到一个文件里了,因为像 speech 那样分开写报错也一样
我觉得结构应该是没什么问题,就是细节的语法可能有问题,写好之后一直报的错误都是 ValueError: Training op found in graph, exiting {‘ApplyGradientDescent’}
然后我感觉是因为没有创建出两个图来,但是创建了两个图,又报错说数据应该来源于一个图,,,
不知道大神有没有时间看一下代码,,我是不知道怎么改了,,,不确定我写的对不对,,,先贴在这吧,,

import tensorflow as tf
import os.path
from tensorflow.python.framework import graph_util
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets ("MNIST_data/", one_hot=True)

x = tf.placeholder ("float", shape=[None, 784], name='input')
y = tf.placeholder ("float", shape=[None, 10], name='labels')
keep_prob = tf.placeholder ("float", name='keep_prob')


def build_network ():
    def weight_variable (shape):
        initial = tf.truncated_normal (shape, stddev=0.1)
        return tf.Variable (initial)

    def bias_variable (shape):
        initial = tf.constant (0.1, shape=shape)
        return tf.Variable (initial)

    # convolution and pooling
    def conv2d (x, W):
        return tf.nn.conv2d (x, W, strides=[1, 1, 1, 1], padding='VALID')

    def max_pool_2x2 (x):
        return tf.nn.max_pool (x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='VALID')

    # convolution layer
    def lenet5_layer (layer, weight, bias):
        W_conv = weight_variable (weight)
        b_conv = bias_variable (bias)
        h_conv = conv2d (layer, W_conv)   b_conv
        return max_pool_2x2 (h_conv)

    # connected layer
    def dense_layer (layer, weight, bias):
        W_fc = weight_variable (weight)
        b_fc = bias_variable (bias)
        return tf.matmul (layer, W_fc)   b_fc

    # first layer
    with tf.name_scope ('first') as scope:
        x_image = tf.pad (tf.reshape (x, [-1,28,28,1]), [[0,0],[2,2],[2,2],[0,0]])
        firstlayer = lenet5_layer (x_image, [5,5,1,6], [6])

    # second layer
    with tf.name_scope ('second') as scope:
        secondlayer = lenet5_layer (firstlayer, [5,5,6,16], [16])

    # third layer
    with tf.name_scope ('third') as scope:
        W_conv3 = weight_variable ([5,5,16,120])
        b_conv3 = bias_variable ([120])
        thirdlayerconv = conv2d (secondlayer, W_conv3)   b_conv3
        thirdlayer = tf.reshape (thirdlayerconv, [-1,120])

    # dense layer1
    with tf.name_scope ('dense1') as scope:
        dense_layer1 = dense_layer (thirdlayer, [120,84], [84])

    # dense layer2
    with tf.name_scope ('dense2') as scope:
        dense_layer2 = dense_layer (dense_layer1, [84,10], [10])

    finaloutput = tf.nn.softmax (tf.nn.dropout (dense_layer2, keep_prob), name="softmax")
    print ('finaloutput:', finaloutput)
    return finaloutput


def create_training_graph ():
    # g = tf.get_default_graph ()
    logits = build_network ()
    # Create the back propagation and training evaluation machinery in the graph.
    with tf.name_scope ('cross_entropy'):
        cross_entropy_mean = tf.reduce_mean (tf.nn.softmax_cross_entropy_with_logits_v2 (labels=y, logits=logits))
        print ('cost:', cross_entropy_mean)

    # if FLAGS.quantize:
    tf.contrib.quantize.create_training_graph (quant_delay=0)     # input_graph=g,
    optimize = tf.train.GradientDescentOptimizer (1e-5).minimize (cross_entropy_mean)

    prediction_labels = tf.argmax (logits, axis=1, name="output")
    correct_prediction = tf.equal (tf.argmax (logits, 1), tf.argmax (y, 1))
    accuracy = tf.reduce_mean (tf.cast (correct_prediction, "float"))

    with tf.get_default_graph ().name_scope ('eval'):
        tf.summary.scalar ('cross_entropy', cross_entropy_mean)
        tf.summary.scalar ('accuracy', accuracy)

    # if is_training:
    return dict (
        x=x,
        y=y,
        keep_prob=keep_prob,
        optimize=optimize,
        cost=cross_entropy_mean,
        correct_prediction=correct_prediction,
        accuracy=accuracy,
    )


def train_network (graph):
    init = tf.global_variables_initializer ()
    saver = tf.train.Saver ()

    with tf.Session () as sess:
        sess.run (init)
        for i in range (200):
            batch = mnist.train.next_batch (50)
            if i % 100 == 0:
                train_accuracy = sess.run ([graph ['accuracy']], feed_dict={
                                                                           graph ['x']:batch [0],
                                                                           graph ['y']:batch [1],
                                                                           graph ['keep_prob']: 1.0})
                print ("step %d, training accuracy %g"%(i, train_accuracy [0]))
            sess.run ([graph ['optimize']], feed_dict={
                                                       graph ['x']:batch [0],
                                                       graph ['y']:batch [1],
                                                       graph ['keep_prob']:0.5})

        test_accuracy = sess.run ([graph ['accuracy']], feed_dict={
                                                                  graph ['x']: mnist.test.images,
                                                                  graph ['y']: mnist.test.labels,
                                                                  graph ['keep_prob']: 1.0})
        print ("Test accuracy %g" % test_accuracy [0])

        saver.save (sess, '/home/angela/tensorflow/tensorflow/Mnist_train/mnist_fakequantize.ckpt')
        tf.train.write_graph (sess.graph_def, '/home/angela/tensorflow/tensorflow/Mnist_train/', 'mnist_fakequantize.pbtxt', True)


def main ():
    g1 = create_training_graph ()
    train_network (g1)

    sess = tf.InteractiveSession ()
    g2 = tf.Graph ()
    with g2.as_default ():
        build_network ()  # is_training=False
        # if FLAGS.quantize:
        tf.contrib.quantize.create_eval_graph ()
        # load_variables_from_checkpoint (sess, '/home/angela/tensorflow/tensorflow/Mnist_train/mnist_fakequantize.ckpt')
        # Turn all the variables into inline constants inside the graph and save it.
        frozen_graph_def = graph_util.convert_variables_to_constants (
            sess, sess.graph_def, ['softmax'])
        tf.train.write_graph (
            frozen_graph_def,
            os.path.dirname ('/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.pb'),
            os.path.basename ('/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.pb'),
            as_text=False)
        tf.logging.info ('Saved frozen graph to %s', '/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.pb')


main ()

2018-11-30 19:13

Zongjun: 回复 九幽:

感觉你彻底明白了哈。inference 用的图是用同一个 create_model 函数产生出来的,用来 inference 的图结构当然得和训练的时候的结构一样啊,要是图都不一样,训练了也没用啊。只是 create_training_graph 和 create_eval_graph 对各自的图的处理效果不同而已。所以还是必须要创建两个图出来。
我大体看了一下你的代码,你的这个报错的源代码是这个链接:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/quantize/python/quantize_graph.py
显然你这个报错来源于 create_training_graph 或者 create_eval_graph 或者两者皆报错。我的建议是和 speech 那样分开写两个文件,各创造一个图。看看这个报错是从 train 出来的还是从 eval 出来的。这样比较好 debug。
2018-12-1 01:49

九幽: 回复 Zongjun:

恩,现在不像之前思路不清无从下手了,至少可以动手写一写了,分开的我也写了,报的错误就是上面提到的,训练节点已经存在的问题
我看位置是 create_eval_graph 那里,因为 create_eval_graph 那部分单独写就是像 freeze.py 一样的结构,然后调用网络那里它又训练了,我写的 if is_training 也没用,可能没写对吧,,,我记得分开也是一样的,我再试试吧,应该就是推理图那里调用网络结构有问题,,,
2018-12-1 10:54

九幽: 回复 Zongjun:

分开写一直是训练不报错,freeze 报错,错误:
ValueError: Tensor (“first/Variable:0”, shape=(5, 5, 1, 6), dtype=float32_ref) must be from the same graph as Tensor (“Pad:0”, shape=(?, 32, 32, 1), dtype=float32).
这个 float32_ref 的错误,我之前用 frozen 指令转化 pb 模型的时候就报过这个错误,这个是训练的数据类型和 freeze 用的不一样吗?
2018-12-3 13:01

Zongjun: 回复 九幽:

这个报错我没遇见过,感觉和你的 Model 有关系。你得确保两个图是完全分开的,然后两个图的结构一致(除了 dropout 这些只属于 training 的 node)。我简单搜了一下,stackoverflow 上有一个类似的问题,看看能不能帮到你,链接:python 2.7 - ValueError: Tensor must be from the same graph as Tensor - Stack Overflow
2018-12-4 01:53

九幽: 回复 Zongjun:

多谢,改了确实不报错了,我分开写的完全是按照 speech 的例子写的,顺序结构都一样照搬过去的,但现在就是运行 freeze 就会重新训练,然后调试了一下,发现到 create_eval_graph 就不运行了,也保存不出最后的 pb 文件,,,
freeze 这里我只改了 create_inference_graph,写的是
logits = mnist_fakequantize.build_network (is_training=False)
tf.nn.softmax (logits, name=‘output’)
只调用 build_network,build_network 改成了去掉后面 optimize 那部分的,只到 finaloutput 那句话,就是这样,然后就不知道为啥不保存了,,,,
至于又训练一遍,难道是训练的开关没加好?但只调用 build_network,为什么还会训练呢?想不明白,,
最后,mnist 原来用的是 AdamOptimizer,几百次就到了 0.9 多,但是换成了 GradientDescentOptimizer,两万次准确率也很低,,只改这一处,别的没动,我看网上有人换了准确率也是 0.9 多,就是不知道我的为什么这么低,一直不收敛
2018-12-4 16:11

九幽: 回复 Zongjun:

重复训练的问题改好了,好开心~原来还是要把 build_network 单独提出来,我重写了一个文件放进去,然后再调用就好了~查看 pb 图和 tflite 转化的图也都是对的了~
至于那个 AdamOptimizer 和 GradientDescentOptimizer 的问题,是不是用哪个都行啊?根据自己的情况选择合适的优化器?
但是我这里两个出来的准确率相差那么悬殊是什么原因呢?
2018-12-4 17:44

Zongjun: 回复 九幽:

哈哈,恭喜你走完整个流程了。optimizer 还是用 mnist 原来的吧,具体差别可以看看 adam 的 paper。adam 应该用的是 moving average of parameters。用 gradient descent 那个你要调 learning rate 这个参数的,learning rate 过大就不会收敛。你用 adam 准确略正常的话就还是用 adam 吧,毕竟 mnist 默认的。
2018-12-5 02:25


根据帖子点评整理。

哦哦,也是,想了解具体的用法就得好好查查论文啥的了,用 GradientDescentOptimizer 的 learning rate 感觉也不大,之前是 1e-5,我看别人也是这样写的,结果却是准确率差不多,,,
mnist 这一块基本感觉差不多了,剩下的就是语法用法啥的了,,,
感觉大神你很适合做老师啊,又有耐心又会讲解,做领导肯定能把手下的人带的很好,我这几天都觉得提升不少,还得有名师点拨啊~再次感谢~

现在量化知道怎么用了,接下来就是改 keras 的程序了,vgg 网络结构要改,训练保存 pb 也要改,还要再加一个 eval.

1.loss 和 optimize 这部分:
(1)我看这几个例子,都放在了单独写好的网络定义之后,比如 create_model 之后,然后跟训练的代码放在一起。我的 mnist 也是把 build_network 和 loss 处理、添加伪量化语句分开来写的,所以 keras 这里我也把它跟训练代码写一起比较好吧?
keras 写的 vgg 模型定义这里,包括了 loss 处理,就是:

model = Model (inputs=img, outputs=olayer)
model.compile (loss=loss_cls,     # yolo_loss
                      optimizer=SGD (lr=0.01, momentum=0.9, clipnorm=2.0),
                      metrics=['mse', ])

如果是这样的话,那伪量化需要的训练图那部分的处理就要加在这里了呗?是把 loss 和 optimize 的处理加在 model.compile 里面 (我不知道怎么加进去,还要看语法,感觉固定的参数添加了其他语句会报错的吧?,,,) 还是要去掉这部分,直接用 tf 底层语句来写啊?像 speech 例子和我改的 mnist 那样写?,,,
综述:loss 这部分是跟模型写在一起(因为 vgg 带了 model.compile,如果在这里面改的话就放在这),还是拿走去跟训练写在一起 (去掉 model.compile,改成 tf 底层代码)?
【从我修改 mnist 的过程来看,处理 loss 这部分,也就是加了 create_training_graph 的这部分,不能跟模型定义放在一起,因为会出问题,后面 eval 的部分还要调用模型,所以检测到 create_training_graph 就会报错,所以,从这来看,还是改成 tf 代码,然后跟训练执行放到一起去?】
(2)我的 vgg 模型也就相当于 create_model 里面的东西,但是 loss 用的是 yolo 的 loss,普通的 loss 是交叉熵,那我这里可以改成自己要用的 yolo_loss 吗?,,,除了 create_training_graph 和 create_eval_graph 是必须用的,他们的位置是必须考虑的,其他的是不是都是根据自己的需求来用就好啊?比如之前说的 Adam 和 GradientDescent,还有这里的 loss?

2.训练图和保存:
创建训练图和保存 pb 文件,这里应该也得用 tf 来写,这一部分感觉又是具体问题,也不知道怎么问,,,

3.create_inference_graph:
我的 mnist 里面修改的很简单,logits = build_network (is_training=False),f.nn.softmax (logits, name=‘output’),就这两句话,,因为我的输出不只是分类,还有画框的 tensor,所以就不知道这里给输出该给什么,我的这里应该是画框和分类都放在一起了,有一个 14 位的一维数组,4 个:类别 one-hot,2 个 bbox:(1,1),8 个:(x,y,w,h)*2。所以觉得输出是不是就是这个一位数组了呢?
我先看看 keras 的 example 还有语法,看看能不能解决一些问题,,,


九幽,发表于 2018-12-5 13:00:10

Zongjun:

不敢当不敢当,略知一二而已。不要客气哈。

  1. (1)Exactly. Loss 的定义不能和模型定义放一起,不然你 eval 的时候 call 同一个 function 就会报错。Loss 要单独写,就是把 kears 的这一套换成 tf 底层代码或者 slim 代码的 loss 和 optimizer。(2)对的,loss 就是你训练用的那个 loss,所以你的如果是 yolo 的,那就换成 yolo 的。不过我不确定这样简单的直接替换会不会有什么意外,可能还需要后续调试啥的。顺序的话,你按照例子的顺序来即可。
  2. 创建训练图和推理图跟以前那两个例子是一样的啊,推荐用 tf.kears.layers 或者 tf.slim 写。如果有 batch normliazation 的话,优先用 slim 吧。freeze 的话可以考虑用例子里的方法,也可以用 tensorflow 的 freeze_graph.py 文件。推荐你先用例子的,毕竟那个你已经成功了。
  3. 输出我不确定,你可以先按照你说的试试,看看 toco 转化成.tflite 的时候报不报错吧。
    为啥还要看 keras 的语法呢,其实所有的 code 都可以用 tf.keras.layers 或者 tf.slim 或者 tf 底层代码搞定了。跟例子是一样的。
    2018-12-6 05:00

九幽: 回复 Zongjun :

恩,我今天看了 model.compile 的使用,可以在前面做 loss 和 optimize 的处理,然后在里面调用,但是 create_training_graph 肯定是没地方塞进去了,所以只能是把 model.compile 整个去掉,然后用 tf 来写了,因为这个我还算熟悉一点,slim 要再去看,我先把 slim 排在后面,tf 弄不好再说。
训练那部分是 keras 写的,你的意思是训练图可以用 tf.kears.layers 来建?或者我要是把训练和推理的部分都直接用 tf 底层重写呢?这个工作量会比在 keras 里面改大吗?
看 keras 语法因为我之前没用过 keras,只是看了下代码,但没有完全理解 keras 的整个过程,就是没把我的代码每一句都看懂,我只看懂了整体流程和每部分是什么功能,感觉它跟我今天去 keras 中文手册里面看的用法不太一样啊,你说的 tf.keras.layers 和 keras 是不一样的吗?
比如,keras 网络定义是:model.add (layers.Dense (16, activation=‘relu’, input_shape=(10,))),然后我的 vgg 定义是:
feat_layers = [Conv2D (16, (3, 3), name=‘model_weights/conv1_1/conv1_1’, **conv_args), …],没有 model.add,,,
感觉现在的问题依旧很边缘化,对 keras 我又不开窍了,,,
2018-12-6 18:42

Zongjun: 回复 九幽 :

对,我的意思就是用 tf.keras.layers 来建训练图和推理图,这个是 tensorflow 官方对 keras 的实现。这个不行,你再尝试底层代码写图吧,底层代码写图应该是最麻烦的。Model.add 是 keras sequential model 的用法,我个人不是很喜欢这个方式。
loss 和 optimizer 你就直接用 tf 底层代码即可,这个和例子是一样的,代码也不多。
2018-12-7 02:38


根据帖子点评整理。

  1. 对,quantize_graph 就是我说的旧版本的那个 quantization,现在的 tf github 中已经不存在了。注意,我没说过它是完全量化工具,因为它并不是。google 官方的意思是要放弃这个方法,以后都用 tflite。我在最一开始的回答中的(*)部分提到过,这个旧方法涉及到 requantize 和 dequantize nodes 这些都是含有 float32 计算的。目前我所知道的 fully quantization 应该只有伪量化加 tflite。
  2. 你生成的 lite 文件之所以有 float32,是因为你的指令中缺少了关键的一句:converter.inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8.这一句告诉 tflite 你要做 fully quantization。你选择了使用 post_training= True,这个我也在最一开始的回答中说过了,这个是一种独立的方法,它的计算 kernel 依然是 float32.既然伪量化了,你就不能再用 post_training 这个指令。我建议你再从头到尾读一遍我最一开始的回答哈。
  3. 关于速度问题,我想说,量化的目的并不只是为了速度。如果你的要求就是速度,那完全可以选择旧方法,毕竟经过你的测试,那个更快。但是,有的硬件,float32 计算会非常昂贵,比这两种方法本身的速度差异更昂贵,那么.tflite 的 fully quantize 模型就是更优解了。
  4. 关于速度慢的问题,应该是 tflite 还在开发之中,像你所说,有的操作速度就是慢。但是估计官方会优化吧,不然完全放弃旧方法,新方法速度又跟不上,拭目以待吧。

Zongjun,发表于 2018-12-15 02:21:08

大神,因为之前不是还不是很清楚,所以之前看你最开始的回复,没搞太明白,现在基本搞清楚了,谢谢大神,然后,我按你说的办法:

converter = tf.contrib.lite.TocoConverter.from_frozen_graph ('tflite_graph.pb',["input_image"],["result"], input_shapes={"input_image":[1,350,202,3]})
converter.allow_custom_ops = True
converter.inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8
converter.quantized_input_stats = {"input_image" : (0., 2.)}
converter.default_ranges_stats=(0, 6)
tflite_quantized_model=converter.convert ()
open ("model.tflite", "wb").write (tflite_quantized_model)

现在确实生成 uint8 的方法,但现在主要报错

2018-12-15 14:54:43.601740: F tensorflow/lite/toco/graph_transformations/quantize.cc:490] Unimplemented: this graph contains an operator of type Square for which the quantized form is not yet implemented. Sorry, and patches welcome (that's a relatively fun patch to write, mostly providing the actual quantized arithmetic code for this op).

Square 这个操作不支持量化,现在也只能这样了.等新版本更新或者大神有什么好办法,
还有,我想问下:

converter.quantized_input_stats = {"input_image" : (0., 2.)}
converter.default_ranges_stats=(0, 6)

这个 输入的 mean 跟 std,还有最小值跟最大值,这些值要怎么确定,比如我现在输入的是一张普通,未经过处理的图片,那么最小值是 0,最大值是 255?mean 跟 std 呢?请大神指导下.
挺佩服大神你这些资料是哪里获取到的,我看官方文档,google 上也没人详细介绍


快到碗里来,发表于 2018-12-15 15:02:20

  1. 请问你用的是什么版本的 tensorflow?你要去下载 tf.nightly 版本,这个我跟你建议过两三次了,你好像没给我反馈,所以我再问一遍哈。我个人认为 square 应该是支持的,你看这个链接:https://www.tensorflow.org/lite/tf_ops_compatibility 这个是谷歌官方说 tflite 支持的 op,里面确实有 tf.square 啊。还有一个可能就是,如果你去 tensorflow github 看底层代码,会发现你调用的这句 converter = tf.contrib.lite.TocoConverter.from_frozen_graph,其中 TocoConverter 属于 deprecate 了,你改成:converter = tf.contrib.lite. TFLiteConverter.from_frozen_graph 试一试。如果改完了报错,说明你可能还停留在 1.10 版本。。

  2. 我感觉你确实没用最新版本的 tensorflow,最新版本的 tensorflow 中,lite 这个文件夹已经从 contrib 移出去了,你调用 tf.contrib.lite 会报错的。现在都是直接 tf.lite…。所以还是先检查一下你的版本哈。

  3. 你看我这个贴子的第二个回答,mean 和 std 我是人工调试的,这个原理我也没搞清楚。。官方的说法是 mean 和 std 指的是 input 的 mean 和 std,但也没给个具体怎么求的例子。。这个 input 是 data augmentation 之前的还是之后的?官方也没有个说法,底层代码那里也是一句注释就带过了。你要是找到了详细解释,还希望跟我分享一下哈!converter.default_ranges_stats=(0, 6) 这一句是说你有的 op 没有 min 和 max,则用这一句给的参数自动补上。比如 relu6 就是默认 min=0, max=6. 你用 Netron 打开你的伪量化以后的.pb 文件,看看里面有没有 relu6,有的话看看它跟没跟着 fakequant node,如果没有的话,就得在 toco 这里加上 default_ranges_stats(0,6)了。

  4. 这些资料 90%都是我平时工作自己总结的,你这些 bug 我基本全都碰到过,见招拆招罢了。谷歌官方的 documentation 有时候确实让我感到很无奈,我的建议是,多去 tensorflow github 里研究底层代码,帮助会很大。


Zongjun,发表于 2018-12-17 02:23:03

你好,大神,实在抱歉,我忘记跟你说了,我之前就听从你的建议用了最新版的 tf-nightly.然后我一一回复下问题:

(1).我用了 pip 安装了 tf-nightly 1.13.0.dev20181216,我发现,他有 tensorflow/lite 文件夹,也有 tensorflow/contrib/lite 文件夹,所以 tf.lite 跟 tf.contrib.lite (应该是还没移除) 都没有报错,我看了 tensorflow 1.12.0 就只有 tf.contrib.lite.我看了官方 GitHub 上最新版,确实就只有 tensorflow/lite 文件夹了,tensorflow/contrib/lite 文件夹已经为空了,但我不知道怎么看 GitHub 上的 tensorflow 是什么版本,求大神指教下

(2).我用 tf-nightly 1.13.0 版本,然后使用 tf.lite.TFLiteConverter.from_frozen_graph 跟使用 tf.contrib.lite.TFLiteConverter.from_frozen_graph 还有 tf.contrib.lite.TocoConverter.from_frozen_graph,都还是报 Unimplemented: this graph contains an operator of type Square for which the quantized form is not yet implemented.
然后我使用 tensorflow 1.12.0,使用 tf.contrib.lite.TFLiteConverter.from_frozen_graph 还有 tf.contrib.lite.TocoConverter.from_frozen_graph 方法,报的错是

Traceback (most recent call last):
  File "test.py", line 24, in <module>
    tflite_quantized_model=converter.convert ()
  File "/home/zhoushaohuang/Virtualenv/py3/lib/python3.4/site-packages/tensorflow/contrib/lite/python/lite.py", line 453, in convert
    **converter_kwargs)
  File "/home/zhoushaohuang/Virtualenv/py3/lib/python3.4/site-packages/tensorflow/contrib/lite/python/convert.py", line 342, in toco_convert_impl
    input_data.SerializeToString ())
  File "/home/zhoushaohuang/Virtualenv/py3/lib/python3.4/site-packages/tensorflow/contrib/lite/python/convert.py", line 135, in toco_convert_protos
    (stdout, stderr))
RuntimeError: TOCO failed see console for info.

我查了下,网上人家也说是使用了不支持的操作,建议使用 tf-nightly 版本我把模型中 Square 操作去掉了,就不会报错了.

(3).我看了 https://www.tensorflow.org/lite/tf_ops_compatibility 这个链接,上面标题 Straightforward Conversions, Constant-Folding and Fusing 下面写着会报某些操作移除或融合进复杂模型里面,这个你前面有跟我解释过,但他下面有句话 Note that many of those operations don’t have TensorFlow Lite equivalents and the corresponding model will not be convertible if they can’t be elided or fused. 这是不是说明,如果你那个操作没办法融合,那么就会转换失败.而且我试了,如果只是伪量化 converter.post_training_quantize=True 转换成 lite,Square 是支持的不会报错,但如果 converter.inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8 完全量化,就会报错,所以我怀疑官方说的支持会不会是指伪量化而已.

(4).我们老大认识 google 的人,让我收集现在 tensorflowlite 现在的问题要去询问,等我得到 input 的 mean 和 std 的回复我再回复大神你.或者大神有什么需要询问的问题,我没注意到的问题也可以跟我说下.


快到碗里来,发表于 2018-12-17 20:31:23