TensorFlow实战———模型持久化
为了让训练结果可以复用,需要将训练得到的神经网络模型持久化。
持久化代码实现
TensorFlow提供了一个非常简单的API来保存和还原一个神经网络模型,这个API就是tf.train.Saver类。
““python
import tensorflow as tf
v1 = tf.Variable(tf.constant(1.0, shape=[1], name=”v1”)
v2 = tf.Variable(tf.constant(2.0, shape=[2], name=”v2”)
result = v1 + v2
init_op = tf.initialize_all_variables()
saver = tf.train.Saver()#声明tf.train.Saver类用于保存模型
with tf.Session() as sess:
sess.run(init_op)
saver.save(sess, “/path/to/model/model.ckpt”)
TensorFlow模型一般会存在后缀为.ckpt的文件中。虽然上面的程序只指定了一个文件路径,但是在这个文件目录下会出现三个文件,这是因为TensorFlow会将计算图的结构和图上参数取值分开保存。
上面这段代码会生成的第一个文件为model.ckpt.meta,它保存了TensorFlow计算图的结构。第二个文件为model.ckpt,这个文件中保存了TensorFlow程序中每一个变量的取值。最后一个文件为checkpoint文件,这个文件中保存了一个目录下所有的模型文件列表。
```python
import tensorflow as tf
v1 = tf.Variable(tf.constant(1.0, shape=[1], name="v1")
v2 = tf.Variable(tf.constant(2.0, shape=[2], name="v2")
result = v1 + v2
saver = tf.train.Saver()
with tf.Session() as sess:
saver.restore(sess, "path/to/model/model.ckpt")
print sess.run(result)
<div class="se-preview-section-delimiter"></div>
与上面保存模型的代码相比,两段代码唯一不同的是,在加载模型的代码中没有运行变量的初始化过程,而是将变量的值通过已经保存的模型加载进来。如果不希望重复定义图上的运算,也可以直接加载已经持久化的图。
import tensorflow as tf
saver = tf.train.import_meta_graph("/path/to/model/model.ckpt/model.ckpt.meta")
with tf.session() as sess:
saver.restore(sess, "/path/to/model/model.ckpt")
print sess.run(tf.get_default_graph().get_tensor_by_name("add:0"))
<div class="se-preview-section-delimiter"></div>
为了保存或者加载部分变量,在声明tf.train.Saver类时可以提供一个列表来指定需要保存或者加载的变量。除了可以选取需要被加载的变量,tf.train.Saver类也支持在保存或者加载时给变量重命名。
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name="other-v1")
v2 = tf.variabe(tf.constant(2.0, shape=[1]), name="other-v2")
<div class="se-preview-section-delimiter"></div>
#如果直接使用tf.train.Saver来加载模型会报变量找不到的错误
<div class="se-preview-section-delimiter"></div>
#使用一个字典来重命名变量就可以加载原来的模型了。这个字典指定了原来名称为v1的变来那个现在加载到变量v1中(名称为other-v1),v2同理
saver = tf.train.Saver({"v1":v1, "v2":v2})
<div class="se-preview-section-delimiter"></div>
在这个程序中,对变量v1和v2的名称进行了修改。如果直接通过tf.train.Saver默认的构造函数来加载保存的模型,那么程序会报变量找不到的错误。因为保存时候变量的名称和加载时变量的名称不一致。为了解决这个问题,TensorFlow可以通过字典将模型保存时的变量名和需要加载的变量联系起来。
那么,为什么要使用这样的重命名机制呢?这样做的主要目的之一是方便使用变量的滑动平均值。
在TensorFlow中,每一个变量的滑动平均值是通过影子变量维护的,所以要获取变量的滑动平均值实际上是获取这个影子变量的取值。如果在加载模型时直接将影子变量映射到变量自身,那么在使用训练好的模型时就不需要再调用函数来获取变量的滑动平均值了。这样大大方便了滑动平均模型的使用。
import tensorflow as tf
v = tf.Variable(0, dtype=tf.float32, name="v")
<div class="se-preview-section-delimiter"></div>
#在没有声明滑动平均模型时只有一个变量v,所以下面的语句只会输出“v:0"
for variable in tf.all_variables():
print variables.name
ema = tf.train.ExponentialMovingAverage(0.99)
maintain_vaerages_op = ema.apply(tf.all_variables())
<div class="se-preview-section-delimiter"></div>
#在声明滑动平均模型之后,TensorFlow会自动生成一个影子变量v/ExponentialMoving Average。于是下面的语句会输出”v:0“和”v/ExponentialMovingAverage:0“
for variables in tf.all_variables():
print variables.name
saver = tf.train.Saver()
with tf.Session() as sess:
init_op = tf.initialize_all_varialbes()
sess.run(init_op)
sess.run(tf.assign(v, 10))
sess.run(maintain_averages_op)
#保存时,TensorFlow会将v:0和v/ExponentialMovingAverage:0两个变量都存下来。
saver.save(sess, "/path/to/model/model.ckpt")
print sess.run([v, ema.average(v)])
<div class="se-preview-section-delimiter"></div>
以下代码给出了如何通过变量重命名直接读取变量的滑动平均值。
v = tf.Variable(0, dtype=tf.float32, name="v")
<div class="se-preview-section-delimiter"></div>
#通过变量重命名将原来变量v的滑动平均值直接赋值给v
saver = tf.train.Saver({"v/ExponentialMovingAverage":v})
with tf.Session() as sess:
saver.restore(sess, "/path/to/model/model.ckpt")
print sess.run(v)#输出0.099999905,这个值就是原来模型中变量v的滑动平均值
<div class="se-preview-section-delimiter"></div>
为了方便加载时重命名滑动平均变量,tf.train.ExponentialMovingAverage类提供了variables_to_restore函数来生成tf.train.Saver类所需要的变量重命名字典。
import tensorflow as tf
v = tf.Variable(0, dtype=tf.float32, name="v")
ema = tf.train.ExponentialMovingAverage(0.99)
<div class="se-preview-section-delimiter"></div>
#通过使用variables_to_restore函数可以直接生成上面代码中提供的字典
<div class="se-preview-section-delimiter"></div>
#{"v/ExponentialMovingAverage":v}
<div class="se-preview-section-delimiter"></div>
#以下代码输出:
<div class="se-preview-section-delimiter"></div>
#{'v/ExponentialMovingAverage':<tensorflow.python.ops.varialbes.Variable object at 0x7ff6454ddc10>}
print ema.variables_to_store()
saver = tf.train.Saver(ema.variables_to_restore())
with tf.Session() as sess:
saver.restore(sess, "/path/to/model/model.ckpt")
print sess.run(v)
<div class="se-preview-section-delimiter"></div>
TensorFlow提供了convert_varialbes_to_constants函数,通过这个函数可以将计算图中的变量及其取值通过常量的方式保存,这样整个TensorFlow计算图可以统一存放在一个文件中。
import tensorflow as tf
from tensorflow.python.framework import graph_util
v1 = tf.Variable(tf.constant(1.0, shape[1]), name="v1")
v2 = tf.Variable(tf.constant(2.0, shape[1]), name="v2")
result = v1 + v2
init_op = tf.initialize_all_varialbes()
with tf.Session() as sess:
sess.run(init_op)
#导出当前计算图的GraphDef部分,只需要这一部分就可以完成从输入层到输出层的计算过程
graph_def = tf.get_default_graph().as_graph_def()
#将途中的变量及其取值转化为常量,同时将图中不必要的节点去掉。在持久化原理中,其实一些系统运算也会被转化为计算图中的节点(比如变量初始化)。如果只关心程序中定义的某些计算时,和这些计算无关的节点就没有必要导出并保存了。在下面一行代码中,最后一个参数['add']给出了需要保存的节点名称。add节点是上面定义的两个变量相加的操作。注意这里给出的是计算节点的名称,所以没有后面的:0
output_graph_def = graph_util.convert_variables_to_constants(sess, graph_def, ['add'])
#将导出的模型存入文件
with tf.gfile.GFile("/path/to/model/combined_model.pb", "wb") as f:
f.write(output_graph_def.SerializeToString())
<div class="se-preview-section-delimiter"></div>
通过下面的程序可以直接计算定义的加法运算的结果。
import tensorflow as tf
from tensorflow.python.platform import gfile
with tf.Session() as sess:
model_filename = "/path/to/model/combined_model.pb"
#读取保存的模型文件,并将文件解析成对应的GraphDef Protocol Buffer
with gfile.FastGFile(model_filename, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
#将graph_def中保存的图加载到当前的图中,return_elements=["add:0"]给出了返回的张量的名称。在保存的时候给出的是计算节点的名称,所以为“add”,在加载的时候给出的是张量的名称,所以是add:0
result = tf.import_graph_def(graph_def, return_elements=["add:0"])
print sess.run(result)