(2018.12.24更新:鉴于评论中有人误解,我这里写清楚一点,后来者也可以先去看看评论区的问题然后再决定要不要看这篇博客~
我当初在添加伪量化代码执行完全量化训练这部分,只找到一篇有用的文章,所以决定给我这样的迷茫小白在这一部分添加一份“小白启发指南”(希望有用)。
这是第一次写博客,我会从评论中不断改进滴~)
1.普通量化步骤及使用详见“第一部分”:
2019年2月21日更新:https://blog.csdn.net/angela_12/article/details/84999473
2.伪量化训练,新出现的博客:
[min,max]的部分可以看看:TensorFlow量化训练
3.伪量化含义理解:
伪量化是完全量化的第一步,它只是模拟了量化的过程,并没有实现量化,只是在训练过程中添加了伪量化节点(也就是后面要说的两句话,创建训练图推理图分别添加进去),计算过程还是用float32计算。
然后训练得出.pb文件,放到toco指令里去实现第二步完整的量化,最后生成tflite,实现int8计算。
(2018.12.24更新: )
1.系统:Ubuntu16.04
2. Python版本:3.5.2
3. TensorFlow Lite(tflite)版本:Release 0.1.7
4. TF版本: 源码安装时版本为1.9.0,后来用pip升级为1.11.0。TF版本查看方法:
命令行输入python ,进入Python环境,输入:
import tensorflow as tf
tf.__version__
前面先说一点概述性的东西,也简单记录下我做这个的心路历程,想直接看干货的就翻到下面去吧。
做了几个月,主要是找不到人问,自己基础差又解决不了 ,很是浪费时间,这个方面的网上的博客千篇一律,写的能用的简直太少了(可能大神们都没时间写基础的东西吧…),现在终于有点成效,所以就想好好整理一下这个实现的过程,给像我一样的小白们一点启发吧……
量化训练不用工具都是手写代码来实现各种功能的话,感觉不用源码安装用pip来安装就好了(参见量化安装,还没写完,这部分别人写了很多,我也是照着他们的做的,先把链接给在这吧),然后主要是写代码来加入量化节点,也就是官方给出的两个关键语句
create_training_graph、create_eval_graph
这两句话放到一起,也就是放在一个.py文件里面写我没弄出来,感觉也不适合放在一起。所以,为了逻辑清晰,也为了减少麻烦(吾等小白只能乖乖按照官方的例子来,试过各种写法全部失败……),还是建议分开写,像官方的两个例子那样:
分为3个.py文件:模型定义(所用网络的定义)、train .py(加入create_training_graph) 和 eval .py (或freeze .py:加入create_eval_graph )。
两个例子分别是:slim写的mobilenet_v1_train.py、mobilenet_v1_eval.py和mobilenet_v1.py,位置的话在这里:
这个例子我最开始看的,里面的格式跟官网给出的“定点量化”文章很像,但是total_loss这里我总出问题,还有learning_rate配着GradientDescentOptimizer用也有问题,然后找不到答案,就跑去社区看问题,发现了后面给的链接中Zongjun大神的回复,然后就问了我的问题,没想到遇到一个又耐心又会讲解的大神,真是这几个月以来最幸福的时刻,再次感谢大神~
有兴趣的可以去看看那个问题,在参考1.这里,基本上就是我做出这个伪量化的整个过程了,里面遇到的问题也基本都有问到(貌似用了将近20天的时间,好慢……)。
上面那个例子没有speech_commands简单清晰,所以主要看这个例子就好了。
参考的代码是:models.py、train.py和freeze.py
我的主要目的是生成量化的tflite,用8bit来计算,之后可以用在手机端来测试性能啥的,所以看了官方文档之后发现,这个只有一种方法就是:quantization-aware training:
2019.3.6更新: 这里的文件夹换了,改成:tensorflow/lite/toco,注意替换一下!!
bazel run --config=opt tensorflow/contrib/lite/toco:toco -- \
--input_file=/home/.../tensorflow/.../Mnist_train/speech_my_frozen_graph.pb \
--output_file=/home/.../tensorflow/.../Mnist_train/speech_my_frozen_graph.tflite \
--input_format=TENSORFLOW_GRAPHDEF \
--output_format=TFLITE \
--inference_type=QUANTIZED_UINT8 \
--input_shapes=1,98,40,1 \
--input_arrays=Reshape_1 \
--output_arrays=labels_softmax \
--allow_custom_ops
bazel-bin/tensorflow/contrib/lite/toco/toco \
--input_file=/home/.../tensorflow/.../Mnist_train/speech_my_frozen_graph.pb \
--output_file=/home/.../tensorflow/.../Mnist_train/speech_my_frozen_graph.tflite \
--input_format=TENSORFLOW_GRAPHDEF \
--output_format=TFLITE \
--inference_type=QUANTIZED_UINT8 \
--input_shapes=1,98,40,1 \
--input_arrays=Reshape_1 \
--output_arrays=labels_softmax \
--allow_custom_ops
1)tflite文件不只是用在移动端的,PC端也是可以的,感觉是因为移动端主要用的是tflite,然后PC端也可以用Python脚本来使用tflite(用的是tf.contrib.lite.Interpreter),所以谷歌就都换成tflite了,之前的quantize_graph.py被谷歌删除了,这个工具感觉可以不用了。现在唯一的保证精度的方法就是伪量化训练生成pb,然后toco转化为tflite,toco转化也可以用Python来写,这部分,我之后来补上。
(这一部分,建议参考社区问题Zongjun大神的第一个回答:https://www.tensorflowers.cn/t/7136)
2)将上面input_file(这里的pb文件路径是最后freeze生成的pb路径)和output_file(这里的tflite路径是要生成的tflite要保存的路径)的路径换成自己的路径,写绝对路径比较好。
3)allow_custom_ops:这个指令可以避免一些不必要的错误,允许一些传统方法,相关知识可以去下面的参考博客中去看。
4)–input_shapes它后面那几个要注意末尾的“s”,要么都加,要么都不加。这几个的获取可以用:
sudo bazel-bin/tensorflow/tools/graph_transforms/summarize_graph \
--in_graph=/home/.../tensorflow/Mnist_train/speech_my_frozen_graph.pb
来查看简要信息(summarize_graph这个也是要bazel来编译的)
不过这里面针对这个例子打印出来的 input_arrays是wav_data,看图也是这个,但是用这个的话会报错,实际上是 Reshape_1,这个是大神试出来的,打印完整结构的话,能看到这个输入,就是在 summarize_graph指令的后面加一句:–print_structure=true。
sudo bazel-bin/tensorflow/tools/graph_transforms/summarize_graph \
--in_graph=/home/.../tensorflow/Mnist_train/speech_my_frozen_graph.pb \
--print_structure=true
5)至于有人提到的–default_ranges_min=0 --default_ranges_max=6这两个指令,我觉得它是Post Training Quantization 需要的,这个方法可以不训练直接转化,但是精度不如训练的,应该是差很多,而且还是用float32计算的(这里可能有点问题,需要再想想)。
6)quantization-aware training文章里提到的–std_value=127.5 --mean_value=127.5这两个指令我没用,还没确定这两个怎么用,知道了再来更新。
(2018.12.18更新: 今天tensorflow的官方公众号发布了一篇讲解speech这个例子的文章,感觉挺有用的,只是除了里面的量化没有讲…不过作为了解TF框架的例子来讲还是挺好的,位置在这里。)
上面是官方的例子,下面写一下我自己的练习,对于这些新技术,我都是在mnist里面实现的,之前测试量化工具和学习tensorflow训练也是用这个,都说它是tensorflow的“hello world”嘛。
(2018.12.21补充:speech.pb大小为3.7M,speech.tflite大小为929.5K)
也分为三部分:
代码解析参考博客:https://blog.csdn.net/real_myth/article/details/51782207
import tensorflow as tf
# 创建图片占位符:x,标签占位符:y 和 随机失活系数keep_prob,以供处理图片,训练和预测时使用
#[x并不是一个特定的值,它是一个placeholder,一个我们需要输入数值当我们需要tensorflow进行运算时。我们想要输入任意数量的mnist图片,每一个都展开成一个784维的向量。我们用一个二维的[None, 784]浮点张量代表。 (这里的None表示维度可以是任意的长度.)]
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')
# 定义mnist网络结构
def build_network(is_training):
# 定义网络类型
#[我们的模型中也需要权重和bias。我们可以把它们看成是额外的输入,Tensorflow有更加好的方法来表示它: Variable. Variable是一个Tensorflow图交互操作中一个可以修改的张量。 它可以在计算中修改。对于机器学习的,一般都有一些Variable模型参数。]
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
if is_training:
finaloutput = tf.nn.softmax(tf.nn.dropout(dense_layer2, keep_prob), name="softmax")
# 为eval调用准备,eval用的网络要去掉 dropout
else:
finaloutput = tf.nn.softmax(dense_layer2, name='softmax')
print('finaloutput:', finaloutput)
return finaloutput
想了解整体mnist代码比较细致的分析,可以看以下博客:博客1,博客2等。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from Mnist_train.mnist_build_network import build_network, x, y, keep_prob
# 加载mnist下载的数据,有四个,数据我是在一个博客给出的百度网盘里下的,不过我的git账号还没弄好,不能上传,需要的话可以去看mnist实现的相关博客找数据下载。
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
def create_training_graph():
#创建训练图,加入create_training_graph:
g = tf.get_default_graph() # 给create_training_graph的参数,默认图
#调用网络定义,也就是拿到输出
logits = build_network(is_training=True) #这里的is_training设置为True,因为前面模型定义写了训练时要用到dropout
# 写loss,mnist的loss是用交叉熵来计算的,loss和optimize方法可以根据自己的情况来设置。
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)
# 加入 create_training_graph函数,注意位置要在loss之后, optimize之前
# if FLAGS.quantize:
# 上面这句是如果用parser设置flag参数的话,就用这种方式设置开关,用法可以自己查一下,或者参考speech的例子就知道了。
tf.contrib.quantize.create_training_graph(input_graph=g, quant_delay=0)
# optimize用原来的Adam效果较好,不知道我这里为什么用GradientDescentOptimizer的话,基本不收敛。
optimize = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy_mean)
# optimize = tf.train.GradientDescentOptimizer(1e-4).minimize(cross_entropy_mean)
# 比较输出类别概率的最大值[tf.argmax 是项的极其有益的函数,它给返回在一个标题里最大值的索引。例如,tf.argmax(y,1) 是我们的模型输出的认为是最有可能是的那个值,而 tf.argmax(y_,1) 是正确的标签的标签。]
prediction_labels = tf.argmax(logits, axis=1, name="output")
# 将得出的最大值与实际分类标签对比,看二者是否一致[如果我们的预测与匹配真正的值,我们可以使用tf.equal来检查。]
correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(y, 1))
# 给出识别准确率[这会返回我们一个布尔值的列表.为了确定哪些部分是正确的,我们要把它转换成浮点值,然后再示均值。 比如, [True, False, True, True] 会转换成 [1,0,1,1] ,从而它的准确率就是0.75.]
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
#后面with这部分对于量化应该没啥影响,记得是tensorboard要用的,应该是出准确率啥的曲线图的……(不过这里应该没用到吧,,我只是搬过来了还没看,会用的想用就用吧,不会的就删掉吧)
with tf.get_default_graph().name_scope('eval'):
tf.summary.scalar('cross_entropy', cross_entropy_mean)
tf.summary.scalar('accuracy', accuracy)
# 返回所需数据,供训练使用
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函数保存所需文件
saver = tf.train.Saver()
# 创建上下文,开始训练sess.run(init)
with tf.Session() as sess:
sess.run(init)
# 一共训练两万次,也可以更多,不过两万次感觉准确率就能达到将近1了
for i in range(20000):
# 每次处理50张图片
batch = mnist.train.next_batch(50)
# 每100次保存并打印一次准确率等
if i % 100 == 0:
# feed_dict喂数据
train_accuracy = sess.run([graph['accuracy']], feed_dict={
graph['x']:batch[0], # batch[0]存的图片数据
graph['y']:batch[1], # 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])
# 保存ckpt(checkpoint)和pbtxt。记得把路径改成自己的路径,写不好相对路径的就直接写绝对路径。绝对路径就是我写的这种完整的路径。
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)
main()
import tensorflow as tf
import os.path
from Mnist_train.mnist_build_network import build_network
from tensorflow.python.framework import graph_util
# 创建推理图
def create_inference_graph():
"""Build the mnist model for evaluation."""
# 调用网络,Create an output to use for inference.
logits = build_network(is_training=False)
# 得到分类输出
tf.nn.softmax(logits, name='output')
def load_variables_from_checkpoint(sess, start_checkpoint):
"""Utility function to centralize checkpoint restoration.
Args:
sess: TensorFlow session.
start_checkpoint: Path to saved checkpoint on disk.
"""
saver = tf.train.Saver(tf.global_variables())
saver.restore(sess, start_checkpoint)
def main():
# Create the model and load its weights.
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
# 推理图
create_inference_graph()
# 加入create_eval_graph(),转化为tflite可接受的格式。以下语句中有路径的,记得改路径。
# 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:ckpt + pbtxt
frozen_graph_def = graph_util.convert_variables_to_constants(
sess, sess.graph_def, ['output'])
# 保存最终的pb模型
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()
以上三部分,就是对照speech例子完成的mnist伪量化训练,这样得出的pb模型才能通过toco工具转化为可用的tflite,转化后的tflite大小大约变为了pb模型的1/4。
(2018.12.21补充:mnist.pb大小为256.3K,mnist.tflite大小为66.4K)
bazel-bin/tensorflow/contrib/lite/toco/toco \
--input_file=/home/.../tensorflow/.../Mnist_train/mnist_fakequantize.pb \
--output_file=/home/.../tensorflow/.../Mnist_train/mnist_fakequantize.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
1)图片里的是之前训练和转化得出来的结果,但是现在里面的input都没有了,我也不知道为什么,现在的指令都是按照官方文档写的,出来后是没有input的,但是之前是有的,然后我测试没有input的结果是正确的,所以不知道这有什么影响,可能之前用的指令在inference_type那里不太一样吧。这个我有时间再研究一下,也可能是我TF版本弄乱了。
2)如果不添加伪量化训练,只用sudo bazel-bin/tensorflow/tools/quantization/quantize_graph工具将普通训练得出的pb文件进行量化的话,得出的pb只是float类型的,尽管也压缩成了1/4,还没报错,但却不是int8的,而这种再用toco转化成的tflite是不能用的,不过具体原因我还不太清楚(此部分具体参见“2.参考博客”)。
1.toco转化tflite参考:
Converter Python API guide
2.post_training_quantize 方法:
Post Training Quantization
(可参考:参考博客)
公司给的任务是完成基于yolo_loss的手势分类和画框检测,将训练出的pb模型量化压缩,转化成tflite。所以研究了上面那些内容,在用tf底层代码写的mnist上面是测试成功了,但是公司给我的代码都是用keras写的,我现在的问题就在于怎么把伪量化加到keras里面。
不过keras没法量化,这个我也在社区问题里面问了,但还是不知道怎么改,如果有大佬有这方面的例子,方便的话发出来看看,我好学习学习~感谢~
(2018.12.20更新)
看了keras和tensorflow结合使用的方法,还有大神之前说的tf.keras.layers方法,我觉得构建模型的地方可以用keras,也是因为我的代码就是keras写的,所以懒得全部去改了。
然后,loss和optimize的部分整个都要改成tf的,keras的Model方法都不要了,模型后面应该也像mnist那样给个输出,然后再去改train函数,把loss处理这部分还加在这里,然后以前keras带的整个train方法,就是fit方法也都不要了,改成运用图,保存ckpt和pbtxt的形式。
至于输出有类别和画框该怎么写,我先试试再说~
生成量化的tflite就是这样了,如果有错的地方,欢迎指正~大家有问题也可以在评论区回复,一起交流学习~