本节介绍TensorFlow持久化的工作原理和持久化之后文件中的数据格式

一、持久化代码实现

TensorFlow提供了一个非常简单的API来保存和还原一个神经网络模型。这个API就是tf.train.Saver类。

以下代码给出了保存TensorFlow计算图的方法。

import tensorflow as tf

#声明两个变量并计算他们的和
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.global_variables_initializer()
# 声明tf.train.Saver 类用于保存模型
saver = tf.train.Saver()

wiyh tf.Session() as sess:
    sess.run(init_op)
    # 将模型保存在/path/to/model/model.ckpt文件
    saver.save(sess,"/path/to/model/model.ckpt")

TensorFlow模型一般会存在后缀为.ckpt的文件中。虽然以上程序只能指定一个文件路径,但是在这个文件目录之下会出现三个文件。这是因为TensorFlow会将计算图的结构和图上的参数取值分开保存。上面代码生成的第一个文件是model.ckpt.meta,保存了计算图的结构,我们可以简单理解为神经网络的网络结构。第二个文件为model.ckpt,这个文件保存了TensorFlow程序中每一个变量的取值。最后一个为checkpoint文件,这个文件中保存了一个目录下所有的模型文件列表。

那么,怎样加载这个模型呢?

import tensorflow as tf

# 声明两个变量并计算他们的和
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

saver = tf.train.Saver()

wiyh tf.Session() as sess:
    # 加载已经保存的模型,并通过已经保存的模型中的变量值来计算加法
    saver.restore(sess,"/path/to/model/model.ckpt")
    print(sess.run(result))

这段代码基本上和保存模型的代码很相似,在加载模型的程序中也是先定义了TensorFlow计算图上的所有运算,并且声明了一个tf.train.Saver()类。不同的地方就是在加载模型的代码中没有运行变量的初始化过程,而是将变量的值通过已经保存的模型参数加载进来。

如果不想重复定义图上的运算,也可以直接加载已经持久化的图。

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")))

上面程序说了tf.train.Saver()类是通过张量的名称来获取张量的参数,那么如果声明的计算图中的变量与已经保存的模型中的变量名称不一样咋办?

tf.train.Saver()是支持在保存或者加载时给变量重命名的。

# 这里声明的变量名称于已经保存的模型中的变量名称不同。
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name="other-v1")
v2 = tf.Variable(tf.constant(2.0, shape=[1]), name="other-v2")
  
#使用一个字典(dictionary)来重命名变量可以加载原来的模型,这个字典指定了原来名称为“v1”的变量现在
#加载到变量v1中(名称为“other-v1”),原来名称为“v2”的变量现在加载到变量v2中
saver - tf.train.Saver({"v1":v1,"v2":v2})

这样TensorFlow 可以通过字典将模型保存呢是的变量名与需要加载的变量联系起来。这样做的主要目的之一joshi方便使用变量的滑动平均值。

在TensorFlow 中每一个变量的滑动平均值是通过影子变量维护的,所以要获取变量的滑动平均值实际上就是获取这个影子变量的取值。如果在加载模型时,直接将影子变量映射到变量本身,那么在使用训练好的模型时就不需要再调用函数来获取变量的滑动平均值了。

以下通过三段代码来解释如何在加载模型时候,直接将影子变量映射到定义的变量本身。

代码1

import tensorflow as tf 
v = tf.Variable(0, dtype=tf.float32, name="v")
for variable in tf.global_variables():
    print(variable.name)
    #在没有申请活动平均模型时只有一个变量v,所以以上语句只会输出“v:0”
ema = tf.train.ExponentialMovingAverage(0.99)
maintain_averages_op = ema.apply(tf.global_variables())
# 在申请滑动变量模型之后,会自定生成一个影子变量
#所以以下语句会输出,“v:0”和"v/ExponentialMovingAverage:0"
for variables in tf.global_variables():
    print(variables.name)

saver = tf.train.Saver()
with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(maintain_averages_op)
    # 保存时 ,会将“v:0”和"v/ExponentialMovingAverage:0"都保存下来    
    saver.save(sess,"/.../.../.../model.ckpt")
    print(sess.run([v,ema.average(v)]))

这段代码给出了保存模型的时候会将影子变量一起保存。

代码2:

v = tf.Variable(0, dtype=tf.float32, name="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.09999999905,这个值是元俩模型中变量v的滑动平均值

从这段程序之中可以看出,读取的变量v的值实际上是上面代码中变量v的滑动平均值,通过这个方法,就可以使用完全一样的代码来计算滑动平均模型的前向传播的结果。

代码3

为了方便加载时重命名滑动平均变量,tf.train.ExponentialMovingAverage 类提供了variables_to_restore 函数来生成tf.train.Saver类所需要的变量重命名字典。以下代码给出了variables_to_restore 函数的使用。

import tensorflow as tf
 v = tf.Variable(0, dtype=tf.float32, name="v") 
 ema = tf.train.ExponentialMovingAverage(0.99)
 
 # 通过使用variables_to_restore 函数可以直接生成代码2中提供的字典
 
 #以下代码会输出:
 # 
 print(ema.variables_to_restore())
 
 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)) #输出即为原来模型中变量v的滑动平均模型