TensorFlowLite 的量化使用问题

Zongjun:

是 tf.nightly 的话,问题就不是出在版本上了。那应该是你模型的问题了,你这个模型长啥样,方便放上来吗,我可以看看为啥 square 不给融合。或者你可以把你的问题发到 GitHub issue 上去问问看。(如果你的 model 去掉了 square 还工作的话,那干脆把 square 去掉算了。)
mean 和 std 那里就等你的回复啦,多谢!
2018-12-18 01:50

九幽: 回复 Zongjun:

大神,这里三个人我都问过问题,回复了东西,但除了你都没人回我,他们也只跟你说话,看来,其他人都是只关注对自己有用的啊,我在别的地方问也是这种情况,,难道是我问问题的方式不对,内容不对?要么就是我太小白了,跟我说也没意义,,,大神,再次深刻地感受到你这种人太少了,,希望我以后也可以成为大神,变成你这样乐于助人的人~我把之前做的写了个博客发出去了,但是感觉写的好像还不够细致,应该是不够深刻吧,你要有时间可以看看我写的有没有啥错误,不过不知道为什么在百度查不到,只有链接能查到:Tensorflow量化步骤及生成量化的tflite(2)_tensorflow 量化训练代码-CSDN博客 ,看不看都行,我会继续努力的,谢啦~,,,,(不过 keras 的我还没写出来,,,)
2018-12-18 11:12

九幽: 回复 Zongjun:

对啦,看你们在说–std_value=127.5 --mean_value=127.5 这两个东西,我看 Quantization-aware training 的 git 上 toco 这部分的指令给出了那两个,但我之前转化的时候没用,这个是必须的嘛?我看百度上说是标准差和均值,这个是处理输入数据的吗?
2018-12-18 12:41

快到碗里来: 回复 九幽:

哈哈哈,之前有看你的提问,不是不想回复你,是觉得自己也不是很了解所以不敢误导你,所以等大神回复你,至于你说的–std_value=127.5 --mean_value=127.5,伪量化才不需要,如果完全量化的话,我都会报需要这两个参数的错误,所以不知道为什么你可以成功不使用这两个参数,还有,我发现你引用我的博客,哈哈哈,https://blog.csdn.net/qq_16564093/article/details/78996563.有缘有缘
2018-12-18 14:57

九幽: 回复 快到碗里来:

噗…我没想着你能回答我问题的事,毕竟这个东西可能知道也可能不知道…我之前就在跟你说博客的事,,写了一堆发现你的过程,,然后你回复了大神,没回我,然后我才又说没人理我的,,,然后我就把那条回复你的删了,,,
2018-12-18 17:48

九幽: 回复 快到碗里来:

我的 mnist 量化训练,然后再 freeze 之后生成 pb,把这个转化成 tflite 的指令是这样写的:

bazel-bin/tensorflow/contrib/lite/toco/toco --
input_file=/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.pb
--output_file=/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.tflite
--input_format=TENSORFLOW_GRAPHDEF
--output_format=TFLITE
--inference_type=QUANTIZED_UINT8
--input_shapes=1,28,28,1 --input_arrays=input --output_arrays=output
--allow_custom_ops

然后什么报错都没有,用 netron 看图也都和 speech 那个例子跑出来的一样,,就像我博客里贴的图片那样,但是以前的 pb 和 tflite 图里面都有 input,就比如 speech.tflite 的倒数第二层 (除了 Reshape_1 和 labels_softmax 层其他的都有 input),是 softmax input,两个长方块连在一起的,但今天又弄了一下上面的指令,结果图里就没有 input 了,不知道这个是什么情况,,
2018-12-18 17:58

九幽: 回复 快到碗里来:

刚看了一下,speech 那个例子也是上面那个指令,但是运行出来前面有点不一样的东西,写的是: Converting unsupported operation: DecodeWav,Converting unsupported operation: AudioSpectrogram, Converting unsupported operation: Mfcc 后面都是正常的。
然后感觉应该是指令的问题吧,因为我试着加了另外的指令:–inference_input_type=QUANTIZED_UINT8 --input_type=QUANTIZED_UINT8
然后就会报 std 和 mean 的问题,不过如果你指令对的话,我就不知道什么问题了
2018-12-18 18:04

九幽: 回复 快到碗里来:

我还是再说一下博客的事吧,,白写那么多了,,,,,
我最开始弄量化,就看了你的博客,然后照着把指令弄对了,但结果只是 float 型的好用,然后没看懂里面你说的生成量化的 tflite 的步骤,因为照着弄不出来,,现在知道了,原来是里面坑太多,,,,
然后又找了很久,才确定原来你说的就是量化训练的方法,然后到处去找怎么实现,就发现了这里,然后才解决了我的问题
然后开始想写博客,又去看了你的,然后发现你更新了,最开始看你写的语气还以为你是 Zongjun 大神,不过点开你的头像发现原来你就是这里问问题的楼主。。。
所以就跑来跟你说这件事了,然后你也没理我。。。
最后的感觉就是全网量化训练这块也就你写的有用了,又通过你找到了 Zongjun 大神,也很谢谢你啊,写了启发我的博客,也让我找到可以问的人~
2018-12-18 18:10

Zongjun: 回复 九幽:

别客气哈,互帮互助应该的嘛。我有时间一定去看你的博客!mean 和 std 我也不是很清楚,你看我这个贴子的第二个回答,我的 mean 和 std 是手动试出来的。。这个应该和你具体的 input, data augmentation 有关。楼主也在问谷歌这个问题,希望他能得到答案回来分享一下哈。
2018-12-19 04:33

Zongjun: 回复 九幽:

对了,其实完整过程中,还有一步,我一直没说。。。就是生成了 freeze 的.pb 文件以后,你可以用 optimize_for_inference.py 这个文件处理一下你的.pb 文件,把新生成的这个 opt.pb 文件传给 toco。有的 toco 不认的操作就会被 opt 文件干掉。。
2018-12-19 04:42

Zongjun: 回复 快到碗里来:

你们俩是大作家啊,我就不能写这种系统的博客了,公司合同规定。。不过出来回答回答问题还是可以的。。。
2018-12-19 05:10

九幽: 回复 Zongjun:

这种不涉及公司内容,只有学习内容的都不能写啊?…好吧,我们这边没说过这个问题,我写的这点东西应该根本造不成威胁吧,,感觉也没啥技术含量,只是把工具用会,现在还只停留在简单的模型修改上面。
2018-12-19 10:37

九幽: 回复 Zongjun:

std 和 mean 我没用,不知道为什么也成功了,,,那我就先等着吧,反正重点还是在于 keras 这块,,,然后处理 pb 这里我没见过这种方法,我再查查看看~
2018-12-19 10:38

快到碗里来: 回复 九幽:

哈哈哈,抱歉抱歉,我不知道你回复了我,可能是周六周日我不看,也可能是消息太多我没注意到,至于我的博客,因为我也是不断在尝试,所以一直在更改,现在太少关于 tensorflowlite 有用的博客.所以我才建议大神有空可以去写个博客造福一下我们.
2018-12-19 13:49

wsxwd: 回复 Zongjun:

因为量化 tflite 需要输入也是 uint8, 但是实际上你训练时输入范围可能是 [-1, 1],这时候就需要使用 std、mean 进行 rescale。可以简单类比 std=1/scale,mean 就是 zero_point
2018-12-19 15:27

Zongjun: 回复 九幽:

没办法,美国这边公司就是这样滴。。
2018-12-20 02:00

Zongjun: 回复 wsxwd:

你好,如果我推理时的输入是一个 uint8 的图片,值就是 0-255。请问你说的 rescale 是推理时,对 tflite 输入的这个 uint8 图片进行 rescale 吗?std, mean 是训练开始前,对输入图片做 data augmentation 以后的 std 和 mean 吗?std = 1/scale,这里的 scale 指的又是啥啊?zero_point 是谁的 zero_point 呢?
2018-12-20 02:24

Zongjun: 回复 wsxwd:

我感觉我的疑问是,给 toco mean 和 std,这一步到底是对谁做了处理?mean 和 std 只是在转换过程中用到了,还是将来推理时也用于对输入的处理?如果是对推理时的输入做处理,那原来输入的 uint8 图片减去 mean 再除以 std 不就变成了 float32 的数据类型了?可是我们用的是完全量化的 tflite 模型,参数也都是 uint8 类型的,而处理完的输入确是 float32 类型的,这样不是出问题了吗?这应该是我最困惑的地方了。。感谢!
2018-12-20 03:25

import tensorflow as tf

def conv2d (x, input_filters, output_filters, kernel, strides, mode='REFLECT'):
    with tf.variable_scope ('conv'):

        shape = [kernel, kernel, input_filters, output_filters]
        weight = tf.Variable (tf.truncated_normal (shape, stddev=0.1), name='weight')
        x_padded = tf.pad (x, [[0, 0], [kernel // 2, kernel // 2], [kernel // 2, kernel // 2], [0, 0]])
        return tf.nn.conv2d (x_padded, weight, strides=[1, strides, strides, 1], padding='VALID', name='conv')

def resize_conv2d (x, input_filters, output_filters, kernel, strides,height , width , training):
    with tf.variable_scope ('conv_transpose'):

        new_height = height * strides * 2
        new_width = width * strides * 2

        x_resized = tf.image.resize_images (x, [new_height, new_width])
        return conv2d (x_resized, input_filters, output_filters, kernel, strides)


def instance_norm (x):
    epsilon = 1e-9

    # 之前使用这个,但是报 SquaredDifference 这个操作不支持才自己写方法去方差求均值.SquaredDifference 这个应该里面也使用到 square 原理
    #mean, var = tf.nn.moments (x, [1, 2], keep_dims=True)
    mean = tf.reduce_mean (x,[1,2])
    mean = tf.expand_dims (mean,[1])
    mean = tf.expand_dims (mean,[1])
    s = x.get_shape ()
    ### 在这里用了 square.
    var = tf.reduce_sum (tf.square (tf.subtract (x, mean)), [1,2], keep_dims=True)/(s [1].value*s [2].value)
    #mean = 1.0
    #var = 1.0
    result = tf.div (tf.subtract (x, mean), tf.sqrt (tf.add (var, epsilon)))
    return result

def relu (input):
    relu = tf.nn.relu (input)
    return relu

def residual (x, filters, kernel, strides):
    with tf.variable_scope ('residual'):
        conv1 = conv2d (x, filters, filters, kernel, strides)
        conv2 = conv2d (relu (conv1), filters, filters, kernel, strides)
        residual = x + conv2
        return residual

def net (image, heigh_image, width_image ,training):
    image = tf.pad (image, [[0, 0], [10, 10], [10, 10], [0, 0]])
    with tf.variable_scope ('conv1'):
        conv1 = relu (instance_norm (conv2d (image, 3, 32, 9, 1)))
    with tf.variable_scope ('conv2'):
        conv2 = relu (instance_norm (conv2d (conv1, 32, 64, 3, 2)))
    with tf.variable_scope ('conv3'):
        conv3 = relu (instance_norm (conv2d (conv2, 64, 128, 3, 2)))
    with tf.variable_scope ('res1'):
        res1 = residual (conv3, 128, 3, 1)
    with tf.variable_scope ('res2'):
        res2 = residual (res1, 128, 3, 1)
    with tf.variable_scope ('res3'):
        res3 = residual (res2, 128, 3, 1)
    with tf.variable_scope ('deconv1'):
        deconv1 = relu (instance_norm (resize_conv2d (res3, 128, 64, 3, 1,(heigh_image+20)//4, (width_image+20)//4, training)))
    with tf.variable_scope ('deconv2'):
        deconv2 = relu (instance_norm (resize_conv2d (deconv1, 64, 32, 3, 1, (heigh_image+20)//2 , (width_image+20)//2 ,training)))
    with tf.variable_scope ('deconv3'):
        deconv3 = tf.nn.tanh (instance_norm (conv2d (deconv2, 32, 3, 3, 1)))

    y = (deconv3 + 1) * 127.5

    height = tf.shape (y)[1]
    width = tf.shape (y)[2]
    y = tf.slice (y, [0, 10, 10, 0], tf.convert_to_tensor ([-1, height - 20, width - 20, -1]))

    return y

大神.代码发给你了,net 是主方法,调用上面定义的一些方法,使用 square 的地方我加了标注了.


快到碗里来,发表于 2018-12-18 15:06:28

大神,我刚刚又试了下,我大概可以确定是模型融合不了导致那些操作没办法量化,我做了下面的测试,我把 square 去掉,改为 a*a 去求,他反而报 div 不支持量化操作,这说明应该是结构问题导致了量化不了.

(1) 我把 var 设置为 1.0,这个是可以量化成功的

def instance_norm (x):
    epsilon = 1e-9
    mean = tf.reduce_mean (x,[1,2])
    mean = tf.expand_dims (mean,[1])
    mean = tf.expand_dims (mean,[1])
    s = x.get_shape ()
    a = tf.subtract (x, mean)
    b = a*a
    var = tf.reduce_sum (b, [1,2], keep_dims=True)/(s [1].value*s [2].value)
    result = tf.div (tf.subtract (x, mean), tf.sqrt (tf.add (1.0, epsilon)))
    return result

(2) 我把 var 要自己求的话,就会报 div 操作不支持量化

def instance_norm (x):
    epsilon = 1e-9
    mean = tf.reduce_mean (x,[1,2])
    mean = tf.expand_dims (mean,[1])
    mean = tf.expand_dims (mean,[1])
    s = x.get_shape ()
    a = tf.subtract (x, mean)
    b = a*a
    var = tf.reduce_sum (b, [1,2], keep_dims=True)/(s [1].value*s [2].value)
    result = tf.div (tf.subtract (x, mean), tf.sqrt (tf.add (var, epsilon)))
    return result

想麻烦大神帮我看下为什么不可以量化,我自己用 netron 看了,但看不出问题所在.


快到碗里来,发表于 2018-12-18 18:03:22

Zongjun:

不对吧,链接 https://www.tensorflow.org/lite/tf_ops_compatibility 中说,tf.square_difference 是直接可兼容的。不知道你的 square difference 为何会报错。我觉得解决这个问题,要比解决你现在的这个问题更直接一些。
看你的 netron 图,感觉你的操作好多啊。比如 conv2d 上面有一个 pad,用 tf.nn.conv2d 的话 padding 是自动在 conv2d 里的。所以我建议你从已知的谷歌例子入手,以谷歌的例子为框架,把你的代码填上去。
再就是,生成了 freeze 的.pb 文件以后,你可以用 optimize_for_inference.py 这个文件处理一下你的.pb 文件,把新生成的这个 opt.pb 文件传给 toco。有的 toco 不认的操作会被 optimize_for_inference.py 干掉。。文件链接:tensorflow/tensorflow/python/tools/optimize_for_inference.py at master · tensorflow/tensorflow · GitHub
2018-12-19 05:04

快到碗里来: 回复 Zongjun:

嗯嗯,大神,我看了 square difference 确实是说支持的,但就是用了 tf-nightly 1.13 进行量化出现以下报错,所以我觉得是不是某个结构限制了:

2018-12-19 15:17:22.162240: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2018-12-19 15:17:22.165892: I tensorflow/core/platform/profile_utils/cpu_utils.cc:94] CPU Frequency: 3392410000 Hz
2018-12-19 15:17:22.166176: I tensorflow/compiler/xla/service/service.cc:150] XLA service 0x447daf0 executing computations on platform Host. Devices:
2018-12-19 15:17:22.166209: I tensorflow/compiler/xla/service/service.cc:158]   StreamExecutor device (0): <undefined>, <undefined>
Traceback (most recent call last):
  File "test.py", line 24, in <module>
    tflite_quantized_model=converter.convert ()
  File "/home/zhoushaohuang/Virtualenv/python3.4/lib/python3.4/site-packages/tensorflow/lite/python/lite.py", line 455, in convert
    **converter_kwargs)
  File "/home/zhoushaohuang/Virtualenv/python3.4/lib/python3.4/site-packages/tensorflow/lite/python/convert.py", line 442, in toco_convert_impl
    input_data.SerializeToString ())
  File "/home/zhoushaohuang/Virtualenv/python3.4/lib/python3.4/site-packages/tensorflow/lite/python/convert.py", line 205, in toco_convert_protos
    "TOCO failed. See console for info.\n%s\n%s\n" % (stdout, stderr))
tensorflow.lite.python.convert.ConverterError: TOCO failed. See console for info.
2018-12-19 15:17:23.094945: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] Before Removing unused ops: 156 operators, 241 arrays (0 quantized)
2018-12-19 15:17:23.096349: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] Before general graph transformations: 156 operators, 241 arrays (0 quantized)
2018-12-19 15:17:23.105274: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] After general graph transformations pass 1: 84 operators, 147 arrays (1 quantized)
2018-12-19 15:17:23.106310: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] Before pre-quantization graph transformations: 84 operators, 147 arrays (1 quantized)
2018-12-19 15:17:23.106844: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] After pre-quantization graph transformations pass 1: 78 operators, 141 arrays (1 quantized)
2018-12-19 15:17:23.107561: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] Before default min-max range propagation graph transformations: 78 operators, 141 arrays (1 quantized)
2018-12-19 15:17:23.108139: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] After default min-max range propagation graph transformations pass 1: 78 operators, 141 arrays (1 quantized)
2018-12-19 15:17:23.108795: I tensorflow/lite/toco/graph_transformations/graph_transformations.cc:39] Before quantization graph transformations: 78 operators, 141 arrays (1 quantized)
2018-12-19 15:17:23.108860: W tensorflow/lite/toco/graph_transformations/quantize.cc:127] Constant array conv1/conv/weight lacks MinMax information. To make up for that, we will now compute the MinMax from actual array elements. That will result in quantization parameters that probably do not match whichever arithmetic was used during training, and thus will probably be a cause of poor inference accuracy.
2018-12-19 15:17:23.108993: F tensorflow/lite/toco/graph_transformations/quantize.cc:491] Unimplemented: this graph contains an operator of type SquaredDifference 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).
Aborted (core dumped)

然后用 pad 是因为想使用 pad 操作里面的某个扩大图片的方式.
然后我使用了 optimize_for_inference.py,但还是报一样的错误,我看了,对我现在生成的 pb 文件感觉没什么变化,就输出变成了一个 placehold.其他没什么改变.
2018-12-19 20:46

Zongjun: 回复 快到碗里来:

问一下,你的 squareddifference 用的是 tf.squared_difference 语句吗?如果是的话,我就不知道了,我木有用过 squared difference。我感觉可以去 GitHub 上建一个 issue 来问了。让官方的人回答一下哈。最好是把你的 traceback 和代码都贴上去。
2018-12-20 01:55

快到碗里来: 回复 Zongjun:

大神,我用的是 tf.nn.moments,里面应该用到 tf.squared_difference,我去把问题丢到 GitHub 上去问问,谢谢大神。
2018-12-20 14:53

Zongjun: 回复 快到碗里来:

哦,tf.nn.moments 应该是不兼容的。你何不尝试直接用 tf.squared_difference。不客气哈,good luck!
2018-12-21 01:10

快到碗里来: 回复 Zongjun:

SquaredDifference 这个 ops 目前没有被支持 TF Lite (已经支持的 ops 可以在这里看到)。不过 tf.nn.moments 比较简单,可以用其他两个 ops reduce_mean 和 tf_square 组合代替
mean = tf.reduce_mean (x, axis=ax, keepdims=True)
variance = tf.reduce_mean (tf.square (x-mean), axis=ax)
当然也可以自己实现一个 custom ops
mean/std_dev 使用训练数据输入的统计数据
min/max 使用估算的 activation 范围
这是 google 回复的 mean 跟 std_dev 的值,但我 min/max 有个问题,你说的是这个是如果图中存在多个没有 min 跟 max 的 activation,那么 min 跟 max 是不是应该是所有 activation 的最小 min 跟最大的 max.还有,你知道怎么自己实现 custom ops,这个自己 i 实现是不是有些难的
2018-12-29 17:30

快到碗里来: 回复 快到碗里来:

还有,他说的支持的操作网站在 https://www.tensorflow.org/lite/tf_ops_compatibility ,但上面不也写着 SquaredDifference 支持.
2018-12-29 17:43

Zongjun: 回复 快到碗里来:

好的,“mean/std_dev 是使用训练数据输入的统计数据”,这个是在做完 data augmentation 以后的统计数据吗?比如我的输入是一张图片,我训练前对图片做旋转,左右对称,切割等操作,这个时候我要再多加一步,去求我整个数据集的 mean 和 std?我这里一直不是很清楚。。
2018-12-30 13:42

快到碗里来: 回复 Zongjun:

我觉得是 data augmentation 以后的统计数据,而至于求数据集的 mean 和 std,可以重新写个脚本求。
2018-12-31 20:53


根据帖子点评整理。

九幽:

上面那个又出 bug 了,回复不了,写在这了,,,
没事了,从大神和你的对话来看,也知道你的性格了,你应该细节的地方注意的少,,,
博客我这也不断修改,趁着最近这段时间,争取把这部分学的透彻一点吧,不过我没有项目,涉及的东西少,看你问的问题我都没考虑过,你做的是什么啊?你要从源码那里来看懂量化的原理吗?还要通过源码的东西来实现一些功能?

九幽:

感觉你可能又没看到我在上面回你的,所以再在这里写一下吧:
我的 mnist 量化训练,然后再 freeze 之后生成 pb,把这个转化成 tflite 的指令是这样写的:
bazel-bin/tensorflow/contrib/lite/toco/toco –
input_file=/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.pb
–output_file=/home/angela/tensorflow/tensorflow/Mnist_train/mnist_frozen_graph.tflite
–input_format=TENSORFLOW_GRAPHDEF
–output_format=TFLITE
–inference_type=QUANTIZED_UINT8
–input_shapes=1,28,28,1 --input_arrays=input --output_arrays=output
–allow_custom_ops
然后什么报错都没有,用 netron 看图也都和 speech 那个例子跑出来的一样,,就像我博客里贴的图片那样,但是以前的 pb 和 tflite 图里面都有 input,就比如 speech.tflite 的倒数第二层 (除了 Reshape_1 和 labels_softmax 层其他的都有 input),是 softmax input,两个长方块连在一起的,但今天又弄了一下上面的指令,结果图里就没有 input 了,不知道这个是什么情况,,
2018-12-19 14:54

九幽:

刚看了一下,speech 那个例子也是上面那个指令,但是运行出来前面有点不一样的东西,写的是: Converting unsupported operation: DecodeWav,Converting unsupported operation: AudioSpectrogram, Converting unsupported operation: Mfcc 后面都是正常的。
然后感觉应该是指令的问题吧,因为我试着加了另外的指令:–inference_input_type=QUANTIZED_UINT8 --input_type=QUANTIZED_UINT8
然后就会报 std 和 mean 的问题,不过如果你指令对的话,我就不知道什么问题了
2018-12-19 14:54

快到碗里来: 回复 九幽:

因为业务需要,我们是想把公司一些模型放到手机端运行,而用之前的伪量化的 pb 文件,会速度跟内存都有一定限制,所以想尝试 lite 来实现在手机端运行,而对于你说结果图里就没有 input 了,这个我也不太清楚,但应该是你指令或者哪里改了,应该不会平白无故就输出模型的结构就变了,还有你说的 std 和 mean,我还在测试,等有结果我再告诉你或 csdn 私信给你说.
2018-12-19 20:35

九幽: 回复 快到碗里来:

恩,我们也是放到手机里运行,再加上模型优化,所以要生成 tflite 文件,但是我只是在我们的代码里面加上伪量化操作,感觉步骤没有你的那么复杂,你提到的问题我也没遇到过,用的工具也没有,应该是你们都习惯写代码来实现功能,我能用工具指令的都用指令来完成了,,,,
上面那个 input 的问题,我用的指令就是我给出来的,除了没用 std 和 mean,其余的就是上面写的那样了,跟你用的一样吗?我的意思是你生成的 tflite 图有 input 吗?还是也是没有的?或者生成之后你是怎么测试是否生成正确的,直接把 tflite 放到 AndroidStudio 然后生成 APP 测试吗?还是写脚本看识别准确率来试?
2018-12-20 10:54

快到碗里来: 回复 九幽:

你好,可能我用的是我自己写的模型,你用的是官方的例子吗?所以我遇到了一些奇葩的坑,只能慢慢摸索,我之前是用 toco 工具的,但感觉有些麻烦,要编译工具,又怕版本问题,所以上面大神说他是用代码 tf.lite.TFLiteConverter.from_frozen_graph 生成的,具体代码可以看我博客,我都更新了,我觉得,调用 toco 跟使用代码其实原理差不多,我测试了也感觉效果是一样的,而且我更偏向使用代码去生成。然后你说的 std 跟 mean,我之前也用 toco 生成 lite,具体生成命令在博客里面,现在我拿不到我的笔记,你可以对比下。我用代码接口生成的 lite 是有 input 的,测试是用 python 脚本本地测试后我再放到 Android 上测试的。
2018-12-20 15:05

九幽: 回复 快到碗里来:

恩,这样就差不多了,我就是按照大神说的,照着 speech 那个例子改的,然后在 mnist 上面实现的。自己的模型肯定有些不常用的层,我这只有卷积和池化,,,,工具我是之前编译好的,不过不是 clone 下来的源码,是 download 的,之后还用 pip 安装了一遍 tensorflow,然后动了下 Python,感觉某些地方版本坏了,,,然后现在大神说的有一步之前没说的,就是 optimize_for_inference.py,我现在用 bazel 编译不通过了,估计可以用代码试试,不过等有时间再试吧,这步应该是优化吧?是不是加不加都行啊?
指令这一块,我是结合你的博客和官方文档用的,感觉应该没什么问题,std 和 mean 我没用,测试之后,生成的 tflite 是正确的,所以我也暂时不考虑这部分了,,
等有时间,我也试试用代码来实现转换~
2018-12-20 16:02


根据帖子点评整理。

大神,你好,我最近也在做模型的伪量化训练,在这上面折腾了快两个月了,能转换生成 tflite 模型,但用 python 的 API 调用测试会报错,不知大神碰到过没有,现把我的问题描述一下:我用的是 models/research/slim 里面的 inception_resnet_v1 模型,tensorflow 版本是 1.12.0

第一步:在 train.py 中的 loss 之后,train_op 之前加入 tf.contrib.quantize.create_training_graph (input_graph=tf.get_default_graph (), quant_delay=20000),训练模型,保存 ckpt 文件。
第二步:在 freeze.py 中,构建 inception_resnet_v1 的 inference_graph,加入 tf.contrib.quantize.create_eval_graph (input_graph=tf.get_default_graph ()),restore 之前训练保存的 ckpt 文件,冻结生成 pb 文件。

pb 文件用 netron 显示如下:
第三步:用 bazel 编译 tensorflow1.12.0 的源码,生成 toco,用 toco 工具转换,转换命令如下:

./bazel-bin/tensorflow/contrib/lite/toco/toco \
--input_file=inception_resnet_v1_fake_quantized_eval.pb \
--output_file=tflite_model.tflite \
--input_format=TENSORFLOW_GRAPHDEF \
--output_format=TFLITE \
--inference_type=QUANTIZED_UINT8 \
--input_shape="1,160,160,3" \
--input_array=input \
--output_array=embeddings \
--std_value=127.5 \
--mean_value=127.5 \
--default_ranges_min=-1.0 \
--default_ranges_max=1.0

最后能生成 tflite_model.tflite 文件,大小约为转换之前的 eval.pb 的 1/4 左右。用 netron 看结构如下:

第四步:test_tflite.py,调用 python 的 API 加载 tflite 文件,用图片测试。代码如下:

import numpy as np
import tensorflow as tf
import scipy

# Load TFLite model and allocate tensors.
interpreter = tf.contrib.lite.Interpreter (model_path="tflite_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 ("src/151105230861_0_76.536.jpg")
image_ = np.array ([image.astype ('uint8')])

print (image_.shape)
print (type (image_))
print (input_details)
print (output_details)
interpreter.set_tensor (input_details [0]['index'], image_)
interpreter.invoke ()
output_data = interpreter.get_tensor (output_details [0]['index'])
print (output_data)

但是就在测试的时候报错:

eval.pb 网络结构

tflite_model.tflite 网络结构


lishanlu136,发表于 2019-2-13 11:05:34

lishanlu136:

对了,我还尝试用了 slim 里面的 inception_v3,mobileV2,inception_resnet_v2 都会出现类似的错误。
2019-2-13 11:08

lishanlu136:

自问自答,我用 tensorflow1.12.0 的 python api 转 tflite,测试成功了,tf.contrib.lite.TFLiteConverter.from_frozen_graph (),最后测试能出来结果,还得进一步测试精度。
2019-2-13 14:21

Zongjun: 回复 lishanlu136:

对。我用的也是 tf 1.12.0 版本。Tflite toco 这里,我用的一直都是 python api 的 tf.contrib.lite.TFLiteConverter.from_frozen_graph ()。我不推荐用 bazel,有的时候 bazel 自己都存在 bug,github issue 上都能查到的。
其次就是精度,这个就涉及到 quatized_input_stats 这个参数的设定了。这个和你训练网络时用的输入和 data augmentation 有关。你如果测试精度不对,可能就是这里出了问题,可以再来发帖问。正常的话,fully quantized 的测试精度和你 quantization aware training 后的 test accuracy 应该一致。
我用过 slim 的 mobilenet v1,调完这两个参数以后是完全成功的哈。
2019-2-14 05:20

Zongjun: 回复 lishanlu136:

其实,我记得楼上的小伙伴和我都用 bazel 成功过的,但是仅限于 1.12.0 版本的 tensorflow。我再尝试用 tensorflow-master 去 bazel,结果就不是很开心了。所以我最后就决定所有版本我都用 python api,不去管 bazel 了。。。
2019-2-14 05:35

lishanlu136: 回复 Zongjun:

均值和方差应该就是 0 和 1 了,因为我的训练图片在进入训练网络之前都做过标准化的;但这个 default_ranges_stats 的 (min, max) range values 不好确定呀,这个好像是用来量化激活后的值的,我用的是 relu 激活函数;难道要把我的测试集全部跑一次,看看 relu 激活后的值的分布范围,然后再来确定最大,最小值吗?大神有什么建议呢?
2019-2-14 10:05

Zongjun: 回复 lishanlu136:

不需要的,如果是 quantization aware training,你的.pb 文件中的 relu 后面会跟着 fake quant node 来自动识别激活范围。所以我转化的时候没有用到 default_ranges_stats。
均值和方差你的理解可能有偏差。需要看 training input 的 float range。如果你标准化做完 data augmentation 以后的 float32 range 是(0,1)那么 toco 这里的 mean = 0, std = 255,而不是 mean=0, std=1。这个是和 tensorflow 的量化机制有关的。量化的具体公式论文中有。
2019-2-15 02:13

lishanlu136: 回复 Zongjun:

我用的网络是带有残差模块的,inception_resnet_v1,其中有 add 和 concat 操作之后再用 relu 激活的操作,就这一步无法自动识别激活范围,其它的常规卷积操作加激活确实能自动确定激活范围。
2019-2-15 21:52

Zongjun: 回复 lishanlu136:

哦哦,这种情况我还是第一次遇到。学习了!
2019-2-20 01:36

sunyanli:

楼主,您好,我看了您的图,.pb 文件里面 conv2D 转变成.tflite 文件,卷积核依然是 conv2D。而我的卷积核变成 ddepthconv2D。我用的网络本身没有深度卷积。不知道这个是什么问题,希望楼主可以指点一下。谢谢啦。
2019-3-14 10:38

lishanlu136: 回复 sunyanli:

你好好看看你构建网络的代码,应该是用了深度卷积的函数;转换不可能把卷积操作的性质都会改变的,也不可能改名字。
2019-3-14 13:44

sunyanli: 回复 lishanlu136:

我看了一下,构建的网络里面没有深度卷积的函数,都是 conv2D。代码如下:

# 定义 mnist 网络结构
def build_network (is_training):
# 定义网络类型
    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])。

我是在网上看的例子,而且官网上那个 speech 的那个例子,好像也是这个样子。
2019-3-14 14:35

lishanlu136:

你的 pb 中的所有 conv2D 都变成 ddepthconv2D 了吗?能否截个图看看?@sunyanli
2019-3-19 16:54

sunyanli:

不还意思,最近忙其他的事,没有看到,我的没有全部变成 depthconv2D,只有第一个。我问了大神,还有当通道数是 1 时,就好是这种情况。具体你可以看看大神的的回复。
2019-5-9 22:37

我本是山下那棵: 回复 lishanlu136:

您好,你做完测试工作了吗?我现在在测试时出现了精度非常差的问题,不同分类识别结果为同一标签。你的也是这样吗?
2019-6-19 21:26

lishanlu136: 回复 我本是山下那棵:

嗯,测试工作已经做完了,精度还可以的,不会下降很多,我用 inception_v3 量化训练成功了,并做了测试。你可以看看我写的博客记录:tensorflow实现quantization-aware training(伪量化,fake quantization)_伪量化节点-CSDN博客
2019-6-21 18:01


根据帖子点评整理。

各位大神好~有幸在刚接触 tensorflow 量化工作之初就看到了这篇帖子和相关的博客,避开了很多弯路,非常感谢各位的详细分享!我在这想抛出一个问题并分享下自己量化工作的理解。我的问题是:CNN 和 RNN 网络在使用 tfliteconverter 量化为 INT8 类型的 tflite 后,推断精确度的损失为何相差这么大? (我使用同一套量化和转换 API 代码,并使用最简单的 CNN 网络(conv2d+maxpool+dense)和最简单的 RNN 网络(BasicLSTM+static_rnn)分别进行伪量化训练和冻结、转换,最后在 MNIST 数据集上调用解释器来验证转换后的 INT8 类型 tflite 推断准确度,发现 CNN 模型量化后精度损失几乎为 0(mean 和 stderr 使用 0 和 1 即可),但 RNN 模型量化后精度损失从训练的 90%跌到了 11%。。。) 是否 RNN 本身的网络结构特点导致了 INT8 的量化会非常不稳定?期待各位大神的指点~

然后再分享下自己近期的量化工作理解。我使用 tf-nightly 二进制版本然后调用 tf.lite.TFLiteConverter api 函数来进行模型的量化转换。在量化转换过程中关键的几个点分别为:输入输出张量的 name,输入张量的 shape,输入的 mean 和 stderr。 其中输入张量的 shape 要求不能存在-1 这类值,必须指定具体的维数。 输入的 mean 和 stderr 会直接影响到最终的推断准确度,从 tflite 官网的解释和 tf 源码的注释中理解,这两个参数起到了对输入数据的归一化作用,参考 BatchNormalization 原理(BN 层在训练时会对每一个 minibatch 为单位进行归一化处理加速训练,在推断时对单个的样本则需要我们估算均值和方差来实现归一化)。

个人理解此处的两个参数跟 BN 层的均值方差略有不同,只是对输入数据做一次归一化。并且根据 CNN 简单模型的量化验证结果来看,如果模型本身不存在 BN 层的话,均值和方差取 0 和 1 即可以不引起精确度损失。如果模型本身有 BN 层的话,这两个参数就必须要经过估算得到以免精确度损失太大。根据吴恩达的 DL 课程,这两个参数可以通过对训练集的多个 mini-batch 进行均值和方差的指数加权平均来估算(具体操作还未理解。。。) 另外 default_ranges_stats 应该就是针对伪量化过程中没有收集到 min 和 max 的 ops 来手动指定量化范围的。

总的来说感觉 TFlite 的量化转换工具对 RNN 网络不太友好…期待各位大神的意见建议,感谢!


ake123100,发表于 2019-2-16 11:34:37

Zongjun:

你理解的基本透彻,mean 和 std 可以直接影响 inference accuracy。但是 mean 和 std 并不是简单的对输入数据做 normalization 而得到的。做完 normalization,如果输入的值的范围是 float32 (0.0, 1.0),那么用 toco 转化成 uint8 时,mean = 0, std = 255. 所以要看的是最终输入的极值。
mean = the uint8 value in the range [0, 255] that corresponds to floating point 0.0. So if the float range is [0.0, 1.0], then mean_value = 0.
std = (uint8_max - uint8_min) / (float_max - float_min). So if the float range is [0.0, 1.0], then std_value = 255 / 1.0 = 255.
(这两个公式来自谷歌,应用在我的项目上也是成立的。)
没有收集到的 min 和 max,应该使用手动插入的方式进行伪量化吧。default_ranges_stats 准确度应该没有那么高。
RNN 的话,这个按照谷歌的说法好像是正在开发中,不知道何时才能完善。
2019-2-20 01:51

ake123100: 回复 Zongjun:

感谢大神的回答~mean 和 stderr 我再继续学习下. RNN 的 INT8 量化业界例子貌似很少…只有 TensorRT3.0 官方说是可以支持,还需要特定类的 GPU 支持…Orz
2019-2-20 09:21

Zongjun: 回复 ake123100:

TensorRT 是 NVIDIA 的那个吧。我当时觉得用起来有点麻烦就还是选择用传统的 tensorflow 了。祝你成功哈!
2019-2-21 02:53

yunxinmengze: 回复 Zongjun:

大神,你好!
我在做全量化的时候,网络模型中加入 slim 的 batch_norm 层,然后通过 speech_commands 里面的 freeze.py 生成.pb 文件
最后 toco 工具转换,就会出现下面这个问题

Array DS-CNN/conv_1/batch_norm/FusedBatchNorm_mul_0, which is an input to the Add operator producing the output array DS-CNN/conv_1/batch_norm/FusedBatchNorm, is lacking min/max data, which is necessary for quantization. If accuracy matters, either target a non-quantized output format, or run quantized training with your model from a floating point checkpoint to change the input graph to contain min/max information. If you don't care about accuracy, you can pass --default_ranges_min= and --default_ranges_max= for easy experimentation.

要是去掉 bn 层,就可以正确的转换,请问这是什么原因导致的
2019-2-25 16:07

Zongjun: 回复 yunxinmengze:

你这个报错说的是在伪量化训练的时候,你的这个 Node 没有训练它的 min 和 max。你用一个叫 Netron 的软件打开你的.pb 文件看一看,伪量化成功的 Node 是带有 fakequant node 的,你报错的这个应该没有。
如果你是在做 speech commands 这个 project 的话,batch_norm 应该有自己较底层的 API 来实现而不是用比较高等级的 slim api。还不行的话,可以尝试一下在你报错的这个 node 前面或后面手动插入 fakequant node 来训练它的 min 和 max,这个理论上一定会成功。
如果你的 project 都是用 slim 写的,别用 speech_commands 的 freeze,自己用 slim 写一个 freeze 比较好,我有一个 project 完全是用 slim 写的,然后去 tflite 做量化,batch_norm 是不存在你这个错误的。
2019-2-26 01:42

yunxinmengze: 回复 Zongjun:

通过查看 pb 文件发现 slim.batch_norm 层变成了 FusedBatchNorm,不明白为什么会出现这个问题?请大神指点迷津

然后我底层实现了 bn 层就可以顺利的生成 tflite 文件。

还有一点不太明白的是 speech_commads 里面的 freeze.py 和您说的自己实现的 slim 版本的 freeze 有什么区别,前向网络模型就是用 slim 写的啊,其他的函数就只有 tf.contrib.quantize.create_eval_graph () 和 graph_util.convert_variables_to_constants 这两个函数了。不太明白大神说的用 slim 实现 freeze 是怎么的一个改法?
2019-2-26 11:50

Zongjun: 回复 yunxinmengze:

fused batch 是 tensorflow 的优化。具体链接:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/quantize 看 folding batch norm layers 那里。
底层实现顺利的话你可以用 interpreter 测一测整个模型,看看推理正确率是不是正常。(但是理论上,你的模型是 slim 写的, slim batch_norm 应该可以的。要看你具体的项目和代码细节了。)
至于 freeze,你不是说你用的是 speech_commands 的 freeze.py 嘛,那个文件非常的 project specific,比如里面包含了对音频的 preprocess 啥的。既然你模型都是用的 slim,那你就不是在做 speech_commands 这个例子了。那肯定要改一改那个文件再用,我是这个意思。。其实就是你说的那样,需要 create_eval_graph () 和 graph_util…这两个函数。
2019-2-28 03:31

龙仔: 回复 yunxinmengze:

您好,请问一下的关于 bacth_norml 的层的问题你是怎么解决的呢?
2019-4-20 16:04

小坏坏: 回复 龙仔:

其实,通过我的摸索,想要 bn 量化成功,只能按照 这个里面的写法进行, models/research/slim/nets/mobilenet_v1.py at 5fd32ef62e37a8124bf8849f7bea65fbd8cd7bdd · tensorflow/models · GitHub
同时,我尝试成功的模型里面,还只能使用上面链接中的 slim.conv2d 和 slim.separable_conv2d 才能成功,写法也非常严格,只能按照这个里面的标准写法,才能成功。我想使用 slim.conv2d_transpose 都没法成功,最后改成了 resize conv2d 的方法才成功将上采样量化成功
2019-11-22 16:29

sanshanxyz: 回复 Zongjun:

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


根据帖子点评整理。

人工智障_(:」ㄥ:

请问一下 train graph 和 eval graph 是同时创建还是先训练好了再创建 eval graph,加载训练的 checkpoint 呢?另外我这边量化进行到最后一步用 toco convert to tflite 的时候总是出现 up supported operator ‘Cast’,请问您知道是怎么回事吗?我猜可能是 train 时 fake quant 节点在图里面没有删除掉

Zongjun:

我的项目基本是有一个函数,它叫做 create_model,专门用来生成图。训练时调用这个函数,生成训练图然后训练,存出来 checkpoint files。然后我在另一个完全独立的文件里,再调用 create_model 这个函数,生成推理图。然后把刚才训练好的 checkpoint 导入到这个新的推理图中,然后在这个文件里做 freeze 生成 .pb 文件。
至于 fake quant node 是否处理正确了,这个和你模型的具体结构有关。我推荐你下载一个叫 Netron 的软件,用这个软件直接打开你的 .pb 文件。你可以直接看到你图的结构,在需要量化的层 (即,数据类型为 float32 的层) 是有 fake quant node 出现的。如果没有,可能就需要手动插入 fake quant node 了。
2019-3-11 05:41

人工智障_(:」ㄥ: 回复 Zongjun:

谢谢您的回复,我去用 netron 看看
2019-3-11 16:34

jinyuprincess: 回复 Zongjun:

大神你好,想请问一下,用 speech-commands 例子跑的模型,训练的时候 train.py --quanlize=True,但是用 freeze.py 生成.pb 文件后,用 netron 看模型,并没有 quantize node,想请问会是什么原因呢?
2019-3-13 20:14

Zongjun: 回复 jinyuprincess:

这个不应该啊。你 freeze.py 的指令也 --quantize=True 了嘛?
2019-3-16 01:31

jinyuprincess: 回复 Zongjun:

谢谢大神,解决了,没注意到这里,大神每次发现问题都是一针见血~
2019-3-20 11:18

小祥:

我也在最后一步试图转换 pb 到 tflite 模型时出现 unsupported operator ‘Cast’, 请问你最后解决这个问题了吗。
2019-6-13 11:49

小坏坏: 回复 小祥:

这个不支持 cast 的问题,是 tf 的量化本身还不支持这个,你可以手动添加,去掉 tf.cast


由人工智障_(:」ㄥ跟帖提问,根据帖子点评整理。

sunyanli:

大神,想请教您一个问题。我按照伪量化的方式量化了 lenet 网络,但是量化之后,用 Netron 查看时发现 conv2D 卷积变成了 depthconv2D 卷积。depthconv2D 卷积不是深度可分离卷积吗?不是在 inception 网络中设计出来的吗?希望大神可以指点一下。谢谢。

Zongjun:

我没有用过 Lenet,所以没有深度研究过这个网络。但是,对于你的问题,我有一个猜测,在.tflite 里如果 conv2d 变成了 depthwise conv2d,你需要看一下你的 conv2d 的输入是不是只有一个 channel,比如一张黑白图片,它就是一个 channel 的.如果是的话, toco 会自动做一个 optimization 把它变成 depthwise conv2d。官方实现代码:https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/toco/graph_transformations/convert_pure_conv_to_depthwise.cc。
2019-3-16 01:37

Zongjun:

然后就是 depthconv2d 还不是深度可分离卷积,深度可分离卷积叫做 depthwise separable conv2d.后者是有一个 depthconv2d pointwiseconv2d 生成的。pointwise conv2d 本质是一个 1x1 的 conv2d。depthwise separable conv2d 是可以用来替代 conv2d 的,而 depthwise conv2d 自己只能替代 input 为 单个 channel 的 conv2d,就像楼上我说的那样,官方会在后台自动帮你转化。
还有就是,我感觉一个 kernel 设计出来,其目的肯定不是只服务于一个网络的。可能是因为 Inception 搞出来的,但是也完全可以用于其它网络的。嗯,一点个人见解哈。
2019-3-16 01:43

sunyanli: 回复 Zongjun:

谢谢您的回复。您说的我再去理解理解。就是 depthwise2D。我之前写错啦!小白对底层代码还不熟,您说的我还得在看看,理解理解。再次谢谢您。
2019-3-17 22:55

sunyanli: 回复 Zongjun:

大神,谢谢您,我这个的 lenet 的输入通道就是 1。今天大致看了一下这个实现过程,对于这个也有点理解啦。既然深度分离卷积可以大大减少计算量,是不是也可由实现当输入通道数为 3 时,也可以转变成 depthwise separable conv2d 卷积啊?
2019-3-18 16:01

sunyanli:

大神,想再请教一个问题,如果我在量化的过程中只量化卷积层,或是只量化全连接层,应该就是在只量化的层加入伪量化节点,请问这个是在哪里改啊?
是直接改掉对应的这个函数吗

_InsertQuantOp (
            post_activation_bypass_context,
            'post_activation_bypass_quant',
            layer_match.post_activation_bypass_op,
            consumers,
            is_training,
            moving_avg=True,
            ema_decay=ema_decay,
            quant_delay=quant_delay,
            vars_collection=vars_collection,
            bits=activation_bits,
            symmetric=symmetric,
            producer_scope=scope)
        quantized_ops.add (layer_match.post_activation_bypass_op)

2019-3-21 20:53

Zongjun: 回复 sunyanli:

只量化某一个层的话。。我觉得肯定是不能用 create_training_graph 这个 API 了。要手动在你需要量化的层插入 fake quant node。话说你的问题好奇特哈!
2019-3-27 12:53


由 sunyanli 跟帖提问,根据帖子点评整理。

sunyanli,发表于 2019-3-14 16:30:35 跟帖提问:

大神,您好,我最近也在在关于量化的东西,看了您的博客以及另外一个大神的博客,感觉对于这个 tensorflow 的量化,越来越模糊啦。现在我根据我的认知说一下我的理解(底层代码看了一些,但是看得稀里糊涂的)。
1、google 的论文(Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference )和 tensorflow 的 quantization-aware-training 的量化方法是相同的。

2、我看了您的解答,您说的这个加入伪量化是在原来图中加入 fakequantizationnode,记录该节点的最小值与最大值,模拟了 quantized—graph.py 工具中的逆量化和反量化操作,这个确实也是,我自己也做了这两个实验,确实 quantization-aware-training 量化之后的 tflite 文件只有伪量化节点,并且有最大值和最小值,而 quantized—graph.py 的 pb 文件里面有 requantization 和 dequantization。但是我在看其他博主在将 google 量化算法时,都有逆量化这个操作,比如:MVision/CNN/Deep_Compression/quantization/IAO at master · Ewenwan/MVision · GitHub

3、现在我在想是不是 quantization-aware-training 和 quantized—graph.py 方法是不是内部实现的算法都是一样的,我也去看了他们的代码,可是我这种小白看的都是模模糊糊的,不能从大的框架上看整体。

4、有个问题请教您一下,加入伪量化记录节点大小,这个大小是怎么记录的啊?我去看的 quantize.py 代码,里面只有一个_InsertQuantOp 函数,目前我看这个 d 代码也只是大概知道他的意思,把每一个需要插入伪量化的节点,插入伪量化,更详细的还的看代码。因为自己水平有限,我目前只知道 quantization-aware-training 方法具体的做法,其中的原理只知道是用的均匀量化器,不知道大神那边有没有其他文档版的资料,可以提供我这个小白参考一下。

5、还有一个问题,quantization-aware-training 和 quantized—graph.py 还有那个 post-quantization 量化方法是不是其量化的方案都是相同的,都是 image

Zongjun:

你这些问题咱俩如果能语音就好了,几句话就讲明白了。打字就很痛苦了,我慢慢回复哈。
2019-3-16 01:46

Zongjun:

首先,我没有写过任何这方面的博客,不知道你看了谁的,反正那不是我哦。我这边公司有严格规定的。我属实是不敢和公司打擦边球。。
然后我尝试着解答一下你的疑惑哈。
1.底层代码是你最好的老师,我也是一行一行代码研究出来的,我做的还是把这些东西全放进单片机里。所以,你得逼着自己看底层代码,看不懂的话反复看。比如你上面那个问题,答案确实是在底层的 c 代码中。
2. 你可以放下 quantized_graph.py 这个文件了,现在官方的 GitHub 中都没有它了。原因就是,它被 tensorflow lite 完全取代了。你问题中提到的这个 GitHub 链接,它是 7 个月之前的方法。
3. 那么现在就剩下了 quantization-aware training = fake quantization = 伪量化这个方法。它也是现阶段 tensorflow 唯一能实现 fully quantized 的方法。其通过训练时加入 fake quant node 来学习 op 的 min 和 max。注意,是学习,也就是说你的网络是怎么学习的,你的 min 和 max 就跟着怎么学习,比如 gradient descent。
而之前 quantized_graph.py 是在你训练后来进行量化,所以它要一路带着 requantize 和 dequantize。所以,我建议还是转到 tensorflow lite 吧。
2019-3-16 02:10

Zongjun:
4. 还有一个不是方法的方法,就是你最后提到的 post-training quantization,它去年刚出来时让我一度看到了希望,然而它在推理时居然还是用 float32 kernel 来做计算。。。这一下子就用处不大了,因为在我的微控制器上,float32 计算极其昂贵。
所以这就要看你做量化的最初目的是啥了。我的就是要求完全量化,那伪量化就是我的唯一选择。。除非我完全放弃 tensorflow 转去 Arm 的 CMSIS。但是那个的可用性和简洁性肯定是不如 tensorflow 的(前期选工具时,两者我做过比较)。嗯,还是看你项目的具体要求了。
2019-3-16 02:21

sunyanli: 回复 Zongjun:

非常感谢大神的回答。我是通过博客找到这里的,那个博主也在这里问了很多问题,我入门完成伪量化的量化也是通过他。真的非常感谢你们这些大神。我是一名学生,对这个理解的不是很到位。我看量化的知识,是因为我的论文方向是关于量化相关的。我要以它发论文的。我本来是想将剪枝的方法和量化结合一下发一篇论文。1、不知道把这个伪量化方法加入裁剪操作是否可行?
2、上文我提到的那个连接里面说的是关于 google 的那边论文的量化方法,这篇论文我看了,它讲的也是在训练过程中加入的伪量化操作,所以我把这个和 quantization-aware-training 归为一个方法。
3、加入 fake quant node 学习最大值和最小值,它是自动识别网络中需要量化的节点吗?比如权重和激活值。还有您说他说自己学习最大值与最小值,是在训练过程中每迭代一次,权重更改之后 fake quant node 都会自动记录他的最大值与最小值,是吗,
4、在反向传播过程中,论文(量化白皮书)中有提到直通滤波器(straight through estimator),这个操作是在求梯度时,用全精度值,
5、还有一个问题,关于数据溢出。这个问题是调用了 gemmlowp 库,解决的吗?比如在卷积操作之后,两个 8bit 的值相乘,会变成 32bit,此处的是如何解决的啊?是通过只取重要的 8bit 吗?
6、我用 quantization-aware_training 量化,生成的 pb 文件与 tflite 文件,其中的卷积节点的名称发成了改变,从 conv2D 变成啦 depthconv2D,这个是什么原因啊?
7、那个 quantize_graph.py 方法虽然官网上已经溢出,但是我还有一个移位,就是那个量化之后,为什么还要逆量化为 float 型?
再次感谢您的回复。
2019-3-17 15:07

sunyanli:

大神,您说的我那个连接是 7 个月之前的量化方法,那那个方法原理和 quantization—aware-training 有什么区别吗?我看了他提供的源码,也看了 quantization-aware-training,都是调用了 gemmlowp 库啊?大神希望有时间可以指点一下我。要是大神感觉我的问题打字回答不方便,可以加微信聊一下吗?我的微信:syl0318521。谢谢您啦。
2019-3-19 14:44

Zongjun: 回复 sunyanli:

这两天在休假,回复的有点迟哈。裁剪的英文是啥啊,pruning 吗?如果是 pruning 的话,伪量化加裁剪我觉得没什么冲突。我也没尝试过,这个肯定是自己上手试一试最好了。
fake quant node 自动识别某些节点,具体识别哪些底层代码中是有的。不被自动识别的需要手动插入 fake quant node。学习应该就是 back propagation 的时候自动更新 min max 的。
两个 8bit 相乘,这个操作本身也是要在 tensorflow lite toco 转化之后发生了,因为伪量化只是记录 min 和 max,训练过程中全程依然是 float32 的计算。toco 完成 fully quantization 之后,做推理时才会用到 uint8 的计算 kernel. 此时,我们可以接受 int32 的数据类型,你打开完全量化好的一个.tflite 文件,会发现 bias 的数据类型就是 int32 的。
这个第 6 点,你看了的话,我就不说啦。
第 7 点,你读一遍 pete warden 的 blog 就明白啦,他讲得挺清楚的。How to Quantize Neural Networks with TensorFlow « Pete Warden's blog 这篇讲的就是以前那个方法的具体操作。你可以瞅瞅哈。

2019-3-20 01:07

sunyanli:

好的大神,pete 的这个博客我之前有看过。逆量化是因为在进入下一个操作的输入也是要量化后的,而量化需要最大值和最小值(float),所以才逆量化的。
还有一个问题,大神,关于那个那个后向传播的直通滤波器(straight through estimator),是怎么理解的呢?我看的白皮书原文:We model the effect of quantization using simulated quantization operations on
both weights and activations. For the backward pass, we use the straight through estimator (see section 2.4) to model quantization。这个我真的不是很理解。希望大神再指点一下。
2019-3-21 13:25

sunyanli:

大神,我再请教一个问题,我将模型量化 4bit,我该了 quantize.py 代码里面的 Quantize 函数里面的 bit,将之前的 8 改成 4。但是没有量化成功。我分析了一下,我们之前加入伪量化操作,只是获取需要量化节点的最大值最小值,那我们量化成几 bit,主要是看用 toco 工具里面的操作,是吗?void Transform (const TocoFlags
2019-3-22 10:20

sunyanli: 回复 Zongjun:

大神,不知道您有没有看到我的回复,我之前打开这个网页,看到一闪的回复提醒,可是点进去没有看到。
2019-3-27 10:59

Zongjun: 回复 sunyanli:

嗯。刚度假回来。。整个人都变成烤地瓜了。。量化的话,目前只能是 8 bit 吧。我目前还没遇到过 4 bit 量化成功的例子。toco 里也只有两个选择 float32 和 uint8.
2019-3-27 12:45

sunyanli: 回复 Zongjun:

哈哈,那大神一定要做好防晒啊,不好意思啊,您休假还打扰您。我是看量化白皮书上关于 quantizatuoin-aware-training 方法,有关于权重与激活值分布量化 8,4bit 的。(就是论文里的表 6)。
还有您说我的问题问的奇特,我觉得应该是我对这个方法量化还不是很熟悉。我现在有 2 种想法:
(1)quantization-aware-training 方法不是在需要量化的节点加入伪量化节点吗,要是我只量化网络中的某些层(比如卷积层),那就只在想量化的地方加入伪量化操作,之后再 toco。这个应该也是要手动加入伪量化节点,所以我想问一下如何手动加入该节点。
(2)完全量化方法大概分两步:一是加入伪量化,这一步网络中的参数还是浮点,二是用 toco 使网络参数变成 8bit,所以我就想要是改成其他 bit 的是不是只改关于 toco 的底层代码。
另外,关于量化代码,有好几个文件,我也只看了关于 contrib/quatize/python/quantize.py,我是不知道那个 gemmlowp 和这个文件是什么关系,论文中不是说,矩阵相乘,调用的 gemmlowp 库吗?
虽然也看了关于量化的一些东西,但是基础太差,一直看稀里糊涂。
2019-3-27 15:31

Zongjun: 回复 sunyanli:

不打扰不打扰。关于 4 bit 的具体实现应该还没有吧。这个最好问一问官方,比如去 GitHub 上填一个 issue,他们应该会很快回复这种功能性问题。如果你比较急的话,也可以试试改底层代码,toco 这里肯定是要改的了,inference type 那里要添加 4 bit,然后还需要手动添加 4 bit 的量化具体实现,这个我就没有尝试过了,感觉可以参考 8 bit 的具体实现。
手动插入量化看这里:tf.quantization.fake_quant_with_min_max_vars  |  TensorFlow v2.15.0.post1
这里要注意,不要把 min 和 max 设成 constants,它们必须是 tf.Variable 类型。
2019-3-29 01:50

sunyanli: 回复 Zongjun:

大神,最近几天在试着该底层的代码,改成 4bit 的。在改的过程中,我将 quantize_graph.py 里面的函数_create_graph 里面的 weight_bits=8 和 activation_bits=8 改成了 4,toco 工具也改了一下,但是量化之后,tflite 文件里面权重全是空的,你可以知道一下吗?
2、第二个问题,今天又看了一下代码,看到 quant_ops.py 里面的 LastValueQuantize 函数,里面有一个参数 num_bits,num_bits: Number of bits to use for quantization, must be between 2 and 8。他这个意思不是只能量化成 2 和 8bit 吗?如果是这样,这个和我之前看的量化白皮书又有了冲突,因为之前白皮书上有量化成 4bit 的。现在搞的我不知道这个量化 4bit 能不能做啦
2019-4-7 20:35

九幽: 回复 Zongjun:

好久没过来看了,,,他说的那个 “大神” 是我,,我之前解答了他一些问题
2019-4-8 11:43

九幽: 回复 sunyanli:

那个博主是我,我可不是大神,我就是个刚入门的水平
2019-4-8 11:45

Zongjun: 回复 九幽:

你咋不是大神了,能出博客还能回答各种问题,很厉害的好吧。。
2019-4-9 07:11

Zongjun: 回复 sunyanli:

这个我真不会。。我的操作止步于 8-bit。有关 4-bit 的,我建议去 GitHub 上填一个 issue 或者 feature request 吧。stackoverflow 也是个好去处,tag 打上 tensorflow 的话会有 tensorflow 的官方人员回答问题。我个人而言,我还真没见过 4-bit 的例子。。Good Luck!
2019-4-9 07:14

sunyanli: 回复 Zongjun:

哈哈,我已经提问论文 issue,但是等官方回复真的是好慢。我就自己试了试。可是不成功。
2019-4-9 11:13

sunyanli: 回复 九幽:

你们都很厉害。真的很感谢你们的回答,不然我自己一个小白不知道要捣鼓多久呢。虽然现在的这个还没做出来。
2019-4-9 11:14

sunyanli:

大神,您好,我查看 tf.contrib.quantize.create_training_graph () 和 tf.contrib.quantize.create_eval_graph () 两句的定义,找到 tensorflow/contrib/quantize/python/quantize.py 和 quantize_graph.py 两个代码,这里面有一些定义的函数,比如 def Quantize()和 def _create_graph,这些定义的函数里面都有 weight_bits 和 activation_bits 参数,他们都是直接设置为 8.但是我没有找到论文中提到的关于缩放因子 S 和零点的设置,请问大神,这些量化参数,代码是时候实现的,不知道呢您是否有过研究。我目前身边真的没有可问啦,希望大神不要觉得麻烦
2019-4-24 11:22

我本是山下那棵: 回复 sunyanli:

你好,我们做的工作相似,我也是个小白,可以加个好友交流一下吗?同学都不是这个方向,自己每天摸索感觉做了好多无用功。
2019-6-19 21:33

sunyanli: 回复 我本是山下那棵:

不好意思,我好久没过来看这个啦,回复完啦。加好友当然可以啦。
2019-6-21 12:34

sunyanli: 回复 我本是山下那棵:

正好我也有问题想请教你
2019-6-21 15:44

九幽: 回复 Zongjun:

好久没做这方面了,,换了公司之后就不做这个了,,现在再看到感觉很亲切啊,大神真的教了我很多~我给别人解答的,都是在你这学会的,然后刚入个门现在就不做了
2019-7-8 15:27 回复


由 sunyanli 跟帖提问,根据帖子点评整理。

我这边卡了两周 bug 以后走通了,讲一下我遇到的问题吧,tensorflow 挺坑的,而且一些琐碎问题非常 non-sense。我是做卷及网络的量化,基于 mobilenet v2 做的一些改进,操作基本上还是 mobilenet v2 的那些操作(conv2d, separate_conv2d, relu6, batch_norm),版本是 tf1.12 和 tf1.13(这两个版本 tf1.12 里是 tf.contrib.lite,tf1.13 就直接 tf.lite 了),用的 API 是 tf.contrib.slim(2.0 以后 contrib 没有了,也不知道该怎么办)
为了成功量化,网络不能按常规方法定义没,而是要写成这样,我举个例子:

with slim.arg_scope ([slim.separable_convolution2d],
                            normalizer_fn=slim.batch_norm,
                            activation_fn=tf.nn.relu6):
                with slim.arg_scope ([slim.batch_norm],
                                      is_training=is_training,
                                      center=True, scale=True):
                            x = slim.separable_convolution2d (x, out_channel, [3, 3],
                                                                 depth_multiplier=1, stride=stride,
                                                                 biases_initializer=None, biases_regularizer=None)

而且我每一层要这样写,最后在整体网络上还要加上类似的 with slim.arg_scope 来定义卷积层的 activation 和 norm,但是在中间一层调用 Backbone 的时候却不用写这个,我也不知道为什么。试了很多次不同的排列组合,最后玄学成功了。

后面的训练和量化就是直接调用 creat_training_graph 和 creat_eval_graph,然后用 python api 的 tflite_converter。最开始在 Mac 上面出问题,在 linux 上面不能直接用 python api 的 convert_from_session,必须要导出 frozen graph 再用命令行工具转换。后来 Mac 上可以成功转换了,但是 linux 好像还是得用命令行 covnert from frozen graph。

说了这么多,总结一句话就是 tensorflow 太坑了,caffe2 的量化工具开源以后大家可以去试试 fb 的那一套。


人工智障_(:」ㄥ,发表于 2019-3-17 12:03:22

shenyichen105,发表于 2019-3-23 07:22:35

同卡在这个坑里两个星期, 今天终于爬出来了。 我网络是用 keras 写的一维 mobilenetV2。 上来先花了一个半星期研究如何让 create_training_graph 和 create_eval_graph 两个 API 创建正确的 fake quantized graph。读了半天源码最后发现那个 API 没法识别 keras batch normalization 层, 只能自己用 tf 的 API 重写了一个网络然后把 keras 网络里用 float32 预训练好的权重转进去, 因为有 bn 层的存在,training 和 eval 的图还不一样,只能分别建图然后互相转 weights。试了很多错以后终于成功转换 。 不过在第二步 convert 成 tflite 全量化模型之后发现精度完全不行。整整 debug 了一星期之后最后发现是 input_stats 没调好。这个参数相当重要!差一点点最后 inference 结果就差很多。 最后是手动先把我的数据量化成(0,255)然后设置 input_stats = (0,1)终于成功。感觉 tf 的工具真的挺难用的,不过似乎目前在量化的方向没有其他的选择。


Zongjun:

Exactly…我当时调 input_stats 也调了好长好长时间,然后终于搞明白了。因为它决定了 float32 如何量化至 uint8,所以差一点结果就会差很多。。
2019-3-27 12:49

shenyichen105: 回复 Zongjun :

没错。。btw 还是要感谢大神你的之前的回复提醒了我 否则我可能还不知道要同时创建 training 和 eval 两个不同的图才能正确转换,估计现在还陷在坑里呢。。
2019-3-28 08:34

Zongjun: 回复 shenyichen105 :

不谢不谢!互相帮助哈!
2019-3-29 01:39

九幽:

您好,我之前是 keras 写的 yolo,但是到了量化阶段就完全不知道怎么写那个 output 了,,,想问下您的 keras 那个版本是怎么创建两个图的啊?
2019-4-8 11:53

小坏坏:

这种方法的出来的,基本不会比量化训练得到的结果好,所以最好还是通过量化训练,得到量化好的 pb,再通过 toco 转化,这种方法得到的量化结果才比较好
2019-11-22 16:33

Hi,
@Zongjun 感谢大神给大家耐心的答疑,我最近在做基于 ssd 的量化训练的东西,您应该是我能求助到的最靠谱的人了
我现在的情况是,将 ssd 模型改成多输出模式 (因为用的第三方的代码,包含了一些不支持的 op),已经量化训练,生成了 tflite 文件,我想求教的问题是:
1.如何在本地测试 tflite 文件呢?您有什么例子可以提供吗?目前没什么方向。。
2.不太清楚对于多输出的目标检测网络,怎么来写测试脚本……把 softmax 的结果直接合并就可以吗?
3.我看上面有个人贴出来的代码,有 tf.contrib.lite.Interpreter,可是我这里报错’tensorflow.contrib’ has no attribute ‘lite’,难道是版本问题?
4.最后一个问题是十分好奇的了,网上也没有搜到,用 toco 或者 bazel 工具时候,–mean_values --std_dev_value 这两个参数是根据经验选取的吗?有什么常用参数呢?

谢谢!期待您的回复。


Smit,发表于 2019-3-29 14:44:29

Zongjun:

  1. 测试.tflite 模型,我用过 python interpreter,基本就是调用 api,你可以试试哈!
  2. tensorflow GitHub 里原先 lite 这个文件夹是存在于 contrib 文件夹中的,现在 lite 不在 contrib 中了,直接调用 tf.lite.interpreter 试一下?
  3. 这个问题我研究了很长时间,目前得到了确切答案。你看这个链接:https://stackoverflow.com/questions/54261772/tensorflow-lite-toco-mean-values-std-values. 这个问题的答案是谷歌官方的回答。
    2019-4-1 13:08

Zongjun:

  1. 我目前没有做过多输出的目标检测网络,所以给不了什么好的建议。感觉可以 Stack Overflow 上搜一下或者问一下?嘿嘿。
    2019-4-1 13:10

Smit: 回复 Zongjun:

谢谢!我在网上找到分类模型测试 tflite 的代码,但是迁移到检测模型上我就有些糊涂了,我想请教一下:
1.本地测试 tflite,并不是使用 sess-run 的方式?似乎是先 allocate_tensors,读取数据后,再 set_tensor-invoke-get_tensor,是这样的流程吗?
2.我再写测试脚本的时候,意识到好像 tflite 要包含 location 信息,这样加载 tflite 脚本中 get_output_details 的时候,location 和 classification 都要直接获取,不知道我理解的对不对。。
3.这样就引出我的最不知道如何解决的问题了…我 TF 使用时间不久,不太熟悉,转 tflite 的过程中,将 inference 阶段的图 freeze 到 pb 的时候,我只保存了 softmax 信息,location 不知道怎么保存。。。我感觉说的语无伦次,我贴一下代码,麻烦大神帮忙看看
2019-4-1 17:19

Zongjun: 回复 Smit:

  1. tflite 的测试可以考虑使用 python tensorflow lite interpreter api。这个也是在 sess 下运行的。
    2.如果用上述 interpreter 的方法,我没有碰到需要 location 信息的地方,可能我不是很理解何谓 location?
    不清楚 location 的节点名字的话,这一点比较好解决。用 Netron,打开你的.pb file,整个网络的细节甚至参数的具体数值都能看到。(当然要先去下载安装 Netron,这个比较简单,点两下鼠标就安好啦。)
    2019-4-3 04:43

Zongjun: 回复 Smit:

Netron 链接:GitHub - lutzroeder/netron: Visualizer for neural network, deep learning and machine learning models
2019-4-3 04:43

Smit: 回复 Zongjun:

好的好的,我试试看,谢谢~
2019-4-3 16:17

Smit: 回复 Zongjun:

hi, 我想再请教您一个问题,是我在测试 tflite 文件的时候遇到的。我做的是 ssd 的量化,一切都成功了但是测试 tflite 的时候,发现不返回任何 box,我 debug 发现,利用 tflite_model.get_tensor (output_details [idx][‘index’] 从 tflite 中获取的 box 信息,跟我在未 freeze 的 ckpt 的 debug 结果中看到的截然不同:
ckpt 获取的 box 坐标很正常,有很多小数,但是 tflite 获取到的,是诸如:[15 0 0 0] [ 0 0 9 9][37 15 4 0],怎么会有这么多 0 呢,而且跟 ckpt 的查别也太大了
我想请教的是,您遇到过这种问题吗?会不会是 freeze 时候出的问题呢?
谢谢~
2019-4-8 21:04

Zongjun: 回复 Smit:

如果 ckpt 的结果是对的,说明 training 没问题。如果怀疑 freeze 出问题了,我的想法是 freeze 完得到的.pb 文件,先用 Netron 看一下,是否有问题。如果没有问题,用 Tensorflow load 进这个.pb 文件,读入 Model,再做测试,看看是不是正确。如果正确,那么 freeze 也没有问题,那么就剩下 toco 了,你关掉所有的量化,toco 就是直接生成一个 float32 的 tflite 模型,看看有没有上述的 bug,如果有可能量化出问题了。关掉了量化要是还不行,就考虑用他提供的 pre-trained 的例子跑一下看看有没有这个 bug。嗯。
2019-4-9 07:27

Smit: 回复 Zongjun:

Hi 大神,我尝试另一种方法进行量化,似乎更有效,

convert = tf.contrib.lite.TFLiteConverter.from_frozen_graph ("frozen_graph4_8.pb", input_arrays=input_, output_arrays=output_)
convert.post_training_quantize = True
tflite_model = convert.convert ()
open ("model.tflite", "wb").write (tflite_model)

我想请教的是:
1.这样转化跟 toco 转化是等价的吗?
2.为什么 toco 转出来的,跟上述代码转出来的 tflite,用 Netron 查看,不太一样呢?
用代码转的 tflite 中有 dequantize 这个 op,我可否认为这是成功的标识?
3.最后想请教您一个工程问题,一般量化后模型的精度会下降,您有什么优化技巧或经验吗?
再次,万分感激!
2019-4-10 09:13

Zongjun: 回复 Smit:

啊,不对,你第二种方法用的是 post_training quantization。这个方法只是 quanitze weights 而不会 quantize bias 和 activations.并且 inference 的时候是转化回 float32 进行计算。所以如果你训练时做了 fake quantization,那么 toco 的地方就不要用 post_training_quanitzation 了,直接用 QUANTIZED_UINT8。
我用 toco 一直是你的这个第二种形式,即 python api。所以第二种这个形式没错,就是你的 flag 有些问题。这个方法就是 toco。只不过叫做 TFLiteConverter 而已。
理论上 fake quantize 了之后再用 tflite fully quantize,你的 model 的准确率不应该下降很多,除非:

  1. 你的 model 非常小,非常简单,这个时候可能会有问题。
  2. 你的 toco 声明的 mean 和 std 是错的,这两个数错一点,最后的 inference 就会错很多。具体你可以翻一翻这篇以前的评论,我应该有公式。
    2019-4-12 07:36

Smit: 回复 Zongjun:

Hi 大神,很感激您的回复,我按照您的指导,没有使用 post_training quantization,直接用 QUANTIZED_UINT8,但是我怀疑我的转换方式依然有问题,因为测试了一下 tflite,几乎没有精度……debug 发现,转 tflite 前的.pb 模型,测试结果正常,返回的 prediction 和 localisation 都是正常的,例如 [0.08482143 0.29910713 0.16517857 0.37946427],但是从 tflite 获取到的是诸如 [ 0 17 0 13] 这样的结果……感觉好奇怪,我贴一下转换代码,还要麻烦大神有空帮忙看看,真心求指导了

convert = tf.contrib.lite.TFLiteConverter.from_frozen_graph ("frozen_graph.pb", input_arrays=input_, output_arrays=output_)
convert.inference_type = tf.contrib.lite.constants.QUANTIZED_UINT8
input_arrays = convert.get_input_arrays ()
convert.quantized_input_stats = {input_arrays [0]: (0., 1)} # 因为我检查了一下 train 代码中,主干网络第一层的输入是 0~255,所以这里设置均值 0 方差 1,不知道对不对
tflite_model = convert.convert ()
open ("model.tflite", "wb").write (tflite_model)

2019-4-15 09:40

Zongjun: 回复 Smit:

你的代码看起来正常。你用 Netron 打开.tflite model 看看各个 node 和里面的 parameters 是不是正常,然后都是 uint8 类型了。然后就是你是怎么测试.tflite model 的呢。如果用的是 interpreter 的 python api,可以看看网上的例子,有可能调用 interpreter api 时出错了。
quantized_input_stats 这里,看起来是对的,但是保险起见,我建议你改一些数字试试看精度会不会有变化,如果有的话那就是这里出错了。这个有一点不对,精度差别就会很大。
2019-4-16 08:04

Smit: 回复 Zongjun:

谢谢大神的回复,我基本确定就是我测试 tflite 的代码错了,是用的 interpreter,但是后处理有问题。我的理解是,原本预测的 0~1 的数据 (prediction 和 localisation) 在量化过程中被推到 0~255 了,所以数据变得比较大,但是如何把这个大数映射回去呢?毕竟后处理阶段有一些 box select 等,直接除以 255 的话精度损失很大,我昨天查资料了解到,似乎应该有一个反量化的过程,大神有这部分工作的 demo 可以参考吗?
2019-4-16 11:28

Zongjun: 回复 Smit:

我简单搜了一下,看到了这个链接:https://www.tensorflow.org/lite/models/object_detection/overview
你看它 output 的 locations,都是 uint8 的数字,并不是 float32,但是依然是 work 的。你可以研究研究这个哈。
2019-4-18 01:46

Smit: 回复 Zongjun:

谢谢大神~我研究一下
2019-4-18 11:37

Zongjun: 回复 Smit:

不客气不客气。Good Luck!
2019-4-20 01:57 回复


根据帖子点评整理。

@Zongjun,大神,你好!最近在用腾讯开源的 pocketflow 做量化,遇到了一个小问题,就是在量化训练结束后,需要将生成的 meta 文件转 pb,然后再转 tflite,我原先训练的时候是固定 size 的,现在做测试时想要任意大小,就将下面代码 placeholder 的 shape 改成 [1, None, None,3],但是报错:
ValueError: None is only supported in the 1st dimension.
所以说有办法测试时是任意大小的吗?

# restore the graph with inputs replaced
    net_input = tf.placeholder (tf.float32, shape=[1, None, None, 3], name=net ['input_name'])  # input_name: net_input
    saver = tf.train.import_meta_graph (
      file_path_meta, input_map={net ['input_name_ckpt']: net_input})  
    saver.restore (sess, file_path_meta.replace ('.meta', ''))

nick_nie,发表于 2019-4-7 19:25:12


Zongjun,回复于:2019-4-9 07:38

不好意思,我没用过腾讯的这个 pocketflow 哎。不过,看了一下你的问题,我感觉这个 post 可能对你有用,我不确定哈。ValueError: None is only supported in the 1st dimension. · Issue #21986 · tensorflow/tensorflow · GitHub

nick_nie,发表于 2019-4-10 01:21:37:

之前看到论坛里有好多对基于 slim 框架的模型量化成功了,我最近基于 deeplabv3+ 的模型进行量化遇到了一个问题,首先导入了 resnet_v2_101 的 backbone,deeplabv3+ 模型后面的部分我在 debug 时先注释掉了,然后前向传播是没有问题的,就是在开始 create_training_graph 时报了错:tensorflow.python.framework.errors_impl.InvalidArgumentError: Cannot update edge, incompatible shapes: [?,8,8,512] and [?,16,16,512].

以下是模型部分代码:

 if FLAGS.base_architecture not in ['resnet_v2_50', 'resnet_v2_101']:
        raise ValueError ("'base_architrecture' must be either 'resnet_v2_50' or 'resnet_v2_101'.")

    if FLAGS.base_architecture == 'resnet_v2_50':
        base_model = resnet_v2.resnet_v2_50
    else:
        base_model = resnet_v2.resnet_v2_101

    with tf.contrib.slim.arg_scope (resnet_v2.resnet_arg_scope (batch_norm_decay=_BATCH_NORM_DECAY)):
        logits, end_points = base_model (inputs,
                                        num_classes=None,
                                        is_training=is_train,
                                        global_pool=False,
                                        output_stride=FLAGS.output_stride) # output_stride=16
     inputs_size = tf.shape (inputs)[1:3]
    net = end_points [ FLAGS.base_architecture + '/block4']  # [None, 16, 16, 2048]

输入 size 是 256x256x3,有大神知道是怎么回事吗?


徐建君:

你好,我也遇到与你一样的问题,请问你解决没?
2019-5-10 00:39

nick_nie: 回复 徐建君 :

感觉 create_training_graph 对 dilated convolution 会有 bug,没有再往下继续了,不好意思!
2019-5-20 13:02

小坏坏:

这个问题我解决了,主要是 slim.conv2d 中有两个参数,rate 和 stride ,这两个的设置,要么是 rate=1 stride=other 要么 stride=1 rate=other ,我使用的是第一种,第二种应该也可行,但是我没有试过。大致的写法如下:

            with slim.arg_scope ([slim.conv2d],
                normalizer_fn=slim.batch_norm,
                weights_initializer=tf.truncated_normal_initializer (stddev=0.01),
                biases_initializer=tf.constant_initializer (0.01)):
                with slim.arg_scope ([slim.batch_norm],center=True,
                    scale=True,
                    is_training=is_training):
                    x = slim.conv2d (x, num_outputs=channels,kernel_size=[kernel,kernel],stride=stride)

2019-11-22 16:22


根据该帖以及帖子点评整理。

龙仔,发表于 2019-4-20 14:40:34:

@Zongjun,您好,我最近一直在尝试量化训练,但是遇到一个问题一直没有找到解决方案。在我的网络中有 batch_normalization 层, 在转 tflite 的时候一直提示我

“FusedBatchNorm_mul_0, which is an input to the Add operator producing the output array kws_model/KWS_Model/tower_0/CNN_V1/Relu, is lacking min/max data, which is necessary for quantization. Either target a non-quantized output format, or change the input graph to contain min/max information, or pass --default_ranges_min= and --default_ranges_max= if you do not care about the accuracy of results."

我尝试将 bn 层删除但是又紧接着提示我最后一层出现的错误

“prediction does not have MinMax information, and is not a constant array. Cannot proceed with quantization.”

请问这个问题可能是什么原因导致的呢?最后一层正常来说需要最量化么??多谢。


本楼点评(答复):

Zongjun:

这个问题我也碰到了。我目前用 batch_normalization 唯一成功的套路是,在 tf.slim 中做 mobilenet 的伪量化,其中加入了 bn。我 freeze 模型以后得到.pb 文件,用 netron 打开看,我发现 bn 是被分解成了一个一个的 add/mul 操作而不是一整个叫做 fusedbatchnorm 的 node。
直接用 fusedbatchnorm 这个操作,我得到的错误和你是一样的。我没有再往下去 debug 这里,我的项目要求是计算量尽可能的小。所以我感觉你可以看一下 slim 的 mobilenet 那里的 batch norm,看看能不能用到你的项目上?
2019-4-24 01:01

龙仔: 回复 Zongjun:

好的,多谢大佬深夜回复。
2019-4-24 11:05

龙仔: 回复 Zongjun:

暂时将 bn 层去掉了,先规避了这个问题。
2019-4-24 11:10

Zongjun: 回复 龙仔:

不客气不客气,你将来要是 debug 出了这个问题可以回来分享一下哈!Good Luck!
2019-4-26 00:42

我本是山下那棵: 回复 Zongjun:

您好,我使用 lite 模块将训练好的 pb 模型转换为 lite 模型后,lite 模型在 PC 端的测试不同分类图片都为同一个结果,output 仅包含 1.和 0.(softmax 节点),pb 模型运行正常,分类正确。请问您遇到过类似的问题吗?对于这个问题想了好久没有思路,如果您了解这方面的知识希望不吝赐教。
2019-6-19 21:12

Zongjun: 回复 我本是山下那棵:

如果用的是 quantization-aware training,在 tflite 这里要启用 fully-quantized。还需要在 toco 的指令里给出 mean 和 std 这些 input stats,这两个值稍微错一点,预测结果就会差非常大。(因为你的 pb 模型预测正常,我就默认你的训练和模型没什么问题了。)
首先,我觉得应该检查模型本身。用 Netron 这个软件打开你的.tflite 文件,看看你的模型是否正确。该有的层都在,该量化的都量化了。内部参数看起来比较正常。这些都需要先确保正确。
然后可以调一下 mean/std 那两个值,看看有没有什么变化。这两个值具体的公式我记得我在这个帖子里贴过,你可以找找看哈。
2019-6-20 00:44

我本是山下那棵: 回复 Zongjun:

前边的问题已经解决,是由于网络中存在 batch_normal 造成的精度问题,请问我的 quantization-aware training 是正确的吗?虽然结果是正确的,也可以看到 mIn 和 max 但是在 pb 转 tflite 的时候出现了这个错误 ValueError: Input 0 of node conv2/weights_quant/AssignMinLast was passed float from conv2/weights_quant/min:0 incompatible with expected float_ref.
2019-6-24 21:23

我本是山下那棵: 回复 Zongjun:

class mobileNet:
    training = True
    def __init__(self, height=160, width=160, channel=1, n_class=100):    # <tf.Tensor 'x:0' shape=(?, 160, 160, 1) dtype=float32>

        self.tfx = tf.placeholder (tf.float32, [None, height, width, channel], name='x')
        self.tfy = tf.placeholder (tf.float32, [None, n_class], name='y')


        layer1_conv1_in = channel
        layer1_conv1_out = 60

        conv1_weights = self.wight_variable ([3,3,layer1_conv1_in,layer1_conv1_out])
        conv1_biases = self.bias_variable (shape=[layer1_conv1_out])        

        conv1 = tf.nn.conv2d (self.tfx, conv1_weights, strides=[1, 2, 2, 1],padding="SAME",name="conv1")   conv1_biases
        pool1 = self.max_pool (conv1, 'pool1')

        .....
                .....
                .....

        nodes = conv6.shape [1]._value * conv6.shape [2]._value * conv6.shape [3]._value  # nodes 38400
        self.flatten = tf.reshape (conv6, [-1, nodes])
        self.out = tf.layers.dense (self.flatten, n_class, name='out')  # <tf.Tensor 'out/BiasAdd:0' shape=(?, 100) dtype=float32>
        self.softMAX = tf.nn.softmax (self.out, name='softMAX')
        self.sess = tf.Session ()
        self.loss = tf.losses.softmax_cross_entropy (onehot_labels=self.tfy, logits=self.out)
        if self.training:
            tf.contrib.quantize.create_training_graph (input_graph=tf.get_default_graph (), quant_delay=100)
        
                self.train_op = tf.train.AdamOptimizer (0.001).minimize (self.loss)
        self.correct_prediction = tf.equal (tf.argmax (self.tfy, 1), tf.argmax (self.out, 1))
        self.accuracy = tf.reduce_mean (tf.cast (self.correct_prediction, tf.float32))
        
                if not self.training:
            tf.contrib.quantize.create_eval_graph (input_graph=tf.get_default_graph ())
        self.sess.run (tf.global_variables_initializer ())

2019-6-24 21:25

Zongjun: 回复 我本是山下那棵:

我有以下想法:你的代码可能没有真正的创建两个图。代码中多写函数会清楚一些。比如,写一个 build_model 函数用于创建图。然后写两个 script,train_script 里只做 train,里面创建 train graph,然后有 create_training_graph。另一个 eval_script 里,创建一模一样的只做 eval 的图,调用的是同一个 build_model 函数。eval_script 的图导入 train_script 训练好的 checkpoint files,然后 create_eval_graph,最后在 eval_script 里 freeze 成.pb 文件。这样可以确保把两个图完全分开。

你的代码好像缺少了存储 checkpoint file,然后新建一个 graph 再导入的这个步骤。
2019-6-25 01:00

我本是山下那棵: 回复 Zongjun:

您好,我完成了 tflite 的转化,但是在调用的时候显示 Transpose op only supports 1D-4D input arrays.Node number 11 (TRANSPOSE) failed to prepare.错误的原因在于 interpreter.allocate_tensors (),大概意思是我的某个张量不是 1-4D,关于这点我不太了解,Stack Overflow 上有人提出了一样的问题但是没有解答,请问您是否了解这方面的知识?
2019-6-25 19:59

Zongjun: 回复 我本是山下那棵:

哈,我首先比较好奇,你之前的那个问题是怎么解决的?创建了两个 graph 导入 trained checkpoint 了嘛?
新的这个问题嘛。。。我属实是没见过。。不过,还是 Debug 万能大法,用 Netron 打开你的.tflite 文件,看看转化后的.tflite 是不是正确的,这个永远都是第一步。找到出问题的那个 Node,看一下它的 shape, 和内容,是不是和你预期的一样。如果不一样说明训练到 toco 中间出了问题。如果一样,那就更细节了,你要想想怎么改一下你的 graph,绕开这个问题。Good Luck!
2019-6-26 00:38

ambarion: 回复 我本是山下那棵:

您好,我遇到了和您一样的问题,请问您这个问题解决了吗?具体是如何解决的呢?
2019-12-5 10:46


根据回帖和帖子点评整理。

liangdas,发表于 2019-4-24 21:05:26

@Zongjun
你好,请教个问题:tf.multiply 操作在转 tflite 的时候报错
net = tf.multiply (net_resize, net, name=scope+‘_add_regular’)

错误信息:is lacking min/max data, which is necessary for quantization.


本楼点评(答复):

look:

@Zongjun 大神以及各位朋友你们好。请问我在量化训练 superpoint 算法时,加入 create_eval_graph 后,出现错误:ValueError: Training op found in graph,exiting{‘ApplyAdam’}是怎么回事?和训练时创建 model 是一样的。谢谢
2019-4-25 11:39

Zongjun: 回复 look :

你去下载一个叫做 Netron 的软件,用它打开看看你 freeze 以后得到的.pb file。tf.multiply 那里应该有 fake quant node,这个是记录你的 min 和 max 的。看你的第二个问题,请问你在训练的时候用了 create_training_graph () 这个函数嘛?这个是必须要用的。
2019-4-26 00:44

look: 回复 Zongjun :

@Zongjun 谢谢大神。在加入 create_eval_graph 时报错"ValueError: Training op found in graph,exiting{‘ApplyAdam’}"这个问题解决了,是因为 eval 建图的时候调用了训练时建 model 的函数,表征是否训练的参数没设置正确。现在还有一个问题,报错:
NotFoundError : Restoring from checkpoint failed. This is most likely due to a Variable name or other graph key that is missing from the checkpoint. Please ensure that you have not altered the graph expected based on the checkpoint. Original error:Key superpoint/eval_tower0/descriptor/conv1/conv/act_quant/max not found in checkpoint.
请问大神,是因为只是这一层的 max 没有加进去,需要手动添加吗?现在 freeze 没有成功,看不了 freeze.pb 谢谢您
2019-4-26 10:19

look:

百度了一下,有人说是由于使用了 tf.train.AdamOptimizer () 来更新梯度,所以在保存检查点的时候如果不指定则是全局保存,把优化的变量类似 “w_out/Adam” 这种命名规则的变量也一并保存了,自然在恢复的时候就会出现找不到 XX 变量。解决办法,在声明 saver = tf.train.Saver () 的时候带上参数,即需要保存的变量。是这样吗?
2019-4-26 11:30

look:

把 AdamOptimizer 改成 GradientDescentOptimizer 之后还是报同样的错:Key superpoint/eval_tower0/descriptor/conv1/conv/act_quant/max not found in checkpoint。
2019-4-26 14:50

Zongjun: 回复 look:

这个错误应该是你的 eval graph 和 training graph 不一样导致的。你得确定你的这两个图完全一致(除了 batch normalization 之外如果有的话)。如果有手动加入的 node,那在你的 eval 也是要有的,否则你的两个图还是不一样。
这个得看你的具体代码怎么实现的了。
2019-5-1 00:59


根据回帖和帖子点评整理。