tensorflow中的图及其持久化和加载
在我们创建tensorflow工作的时候,tf就默认生成了一个图,这就是我们的默认图。通过tf.get_default_graph() 可以得到。graphdef是图的描述,说白了就是图的各种信息的集合。
图的持久化
图的固化常见的有两种,一种是.ckpt格式的,这种持久化充分的保留了信息,可以通过加载.ckpt格式的文件,完全恢复一张图,并在其上继续训练。因为tf的训练过程是一个漫长的过程,如果中间出错,这种方式可以保证我们从上次出错前的地方恢复,继续训练,而不用完全从头开始。
另一种方式就是.pb格式的固化,这是把.ckpt格式文件中的参数freeze下来后的一种固化。这种格式的文件应该是不能再训练的。只能用作推导来使用。这也是我们对于一张已经训练好的图,为了部署应用它,我们经常使用的格式。它的基本固化代码如下:
# coding=UTF-8
import tensorflow as tf
from tensorflow.python.framework import graph_util
#定义一张图
inputs = tf.placeholder(tf.float32,[],name='input_node')
a = tf.Variable(3.0)
b = tf.Variable(7.0)
sums = tf.add(inputs, a, name="sum_op")
result = tf.multiply(sums,b, name='output_node')
output_graph = "./pb/easy_pb.pb"#输出路径
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
#将图freeze,即将各种变量转变为常量
output_graph_def = graph_util.convert_variables_to_constants( #模型持久化,将变量值固定
sess,
sess.graph_def,
["output_node"] #与之前的输出节点相对应,如果有多个输出点,使用列表表明
)
with open(output_graph, "wb") as f: #保存模型
f.write(output_graph_def.SerializeToString()) #序列化输出
print sess.run(result,feed_dict={inputs:2})
图的加载
得到,pb的固化网络之后,我们通过对它的加载,然后找到它的输入和输出节点,我们就可以做推导了。譬如我们加载inception模型并推导其最后的输出:
# coding=UTF-8
import tensorflow as tf
input_graph_path = "./pb/classify_image_graph_def.pb"
pic = tf.gfile.FastGFile("./flower.jpg",'rb').read()
with open(input_graph_path , 'rb') as f:
output_graph_def = tf.GraphDef() #定义一个图描述,用于接受参数
output_graph_def.ParseFromString(f.read()) #从.pb中读取并解析参数
tf.import_graph_def(output_graph_def,name='')#将之前定义的图描述导入到默认的图中
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
ipt = sess.graph.get_tensor_by_name("DecodeJpeg/contents:0")
opt = sess.graph.get_tensor_by_name("pool_3/_reshape:0")
squeezed = tf.squeeze(opt)
#for i in range(10):
bottleneck = sess.run(squeezed ,feed_dict={ipt:pic})
print bottleneck.shape
operation和tensor
我之前对这个东西一直是模棱两可的。按字面来说,operation就是操作,tensor就是张量。张量是需要零个或多个张量的流入,具备零个或多个张量的流出。在整个计算图中,图上的节点即为计算,图上的边就是张量。比如在
a = tf.constant([1,2],[2,3])
b = tf.constant([3,4],[4,5])
c = tf.matmul(a,b)
d = tf.Variable(1.0)
f = tf.placeholder(dtype=tf.float32,[2,3])
e = tf.train.GradientDescentOptimizer(0.01).minimize(d)
我们可以看到,a,b,c,d,f是张量。d是变量,f是placeholder,他们都是特殊的张量。e是操作节点。但是应该说明的是,abc虽然是张量,但是他们都隐晦的创建了操作节点。譬如c就创建了MatMul的节点,c实际上是该节点的输出张量,a和b是该节点的输入张量,所以c的默认张量名称是“MatMul:0”,这表明c是该节点的第一个输出张量。形象的说明就是,张量是液体,操作节点 就是水管的节点连接和处理的方式,变量就是状态液体,即可以任意变化,placeholder就是虚拟液体,整个管网系统就是图了。和差积商指数对数赋值等等都是方式,他们都是op,这种op会有输出,输出新的tensor。像上面的e这种op就没有输出,他是调整整个“水管系统”(即图)中的配方(状态液体)的一种操作。
dense函数
在构造神经网络的过程中,我们通常是定义权重和便宜变量,然后通过张量的乘法和加法来构造,然后再引入激活函数。但是这样略显麻烦,如果没有特殊的要求,我们可以使用tf.layers.dense()来直接构建全连接层。
变量复用
我们通过变量的命名空间来限定变量,防止命名冲突引起的错误。tf.variable_scope()来实现命名空间,我们就可以通过tf.get_variable()来获得变量。这个函数中的reuse参数设置为True的时候,如果变量变成已经存在,我们就会立即获得已经存在的变量,即变量的重复使用。这对在训练过程中的模型,我们可以实现即时推导。另外要说的是,在tf中,有tf.Variable()和tf.get_variable()等函数可以创建变量tensor,从变量复用的角度来讲,我更推荐后者。举例说明:
import tensorflow as tf
def get_v(reuse = False):
with tf.variable_scope("v", reuse=reuse):
a = tf.get_variable("v1",shape=[2,3], initializer=tf.ones_initializer() )
a = tf.assign_add(a,a)
return a
if __name__ == '__main__':
a =get_v()
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(2):
print sess.run(a)
p =get_v(True)
for i in range(2):
print sess.run(p)
这段代码定义了一个获得变量a以及一个自加的操作的函数,然后我们创建之。然后我们通过自加操作,使a自加两次,变成4,然后我们通过设置reuse=True,我们再次调用该函数,我们就能立即获得已经创建的a,而不会再次从ones_initializer()去创建,我们获得a是4,在进行两次自加操作,我们就能得到16。所以,我们构建inference的时候,我们就可以用这种方式,即时推导的时候方便,主要是防止在推导过程中不小心创建新变量。
Tensorlfow on Andriod
我们训练好的图模型,通过固化成.pb的格式,我们可以讲其部署在android等移动设备端。
如果对android编程有了解,那么在android端的部署比较简单。无非是使用.so共享库和.jar,他们可以通过bazel编译tensorflow工程中的源码获得,也可以在这里下载已经编译好的。关于向工程中添加共享库的方式,网上有很多介绍。这里我要提醒自己一点,在使用共享库的时候,应该先用
static {
System.loadLibrary("tensorflow_inference");
}
加载,这是我之前没注意到的。
使用方式也很简单
// Load the model from disk.
TensorFlowInferenceInterface inferenceInterface =
new TensorFlowInferenceInterface(assetManager, modelFilename);
// Copy the input data into TensorFlow.
inferenceInterface.feed(inputName, floatValues, 1, inputSize, inputSize, 3);
// Run the inference call.
inferenceInterface.run(outputNames, logStats);
// Copy the output Tensor back into the output array.
inferenceInterface.fetch(outputName, outputs);
本来很简单的一个事情,我测试自己训练的最简单的.pb文件也没问题,然后就上inceptionv5的模型,下载下来后,发现运行时总是报没有OpKerne节点的错误。后来参考github上的一些回答,才明白原本的代码中的一些节点不被轻量级的tensorflow支持。我们需要剥出它们。后来我就下载了一个优化后的.pb,然后就能很好的work了。
这里是我的工程代码,虽然简陋,但部署过程中的基本环节都涉及到了。
Import Data
关于tf的数据输入,官网教程有很丰富的说明和举例,但是我这里要说明的一点点小事情:
官网教程中Decoding image data and resizing it小节中的示例代码中的
image_decoded = tf.image.decode_image(image_string)
似乎不太好使,不能够正确的解析图片。如果图片格式都是Jpg格式的,我使用
tf.image.decode_jpeg()
或者相应的格式的函数可以成功的解析图片。
另外,图片的输入处理过程比较好的流程是是:map->shuffle->batch->repeat
网友评论