Tensorflow的持久化与重载

Tensorflow的参数公用,持久化与重载

1. 参数共用问题:

  • tf.variable_scope(name)
  • tf.get_variable(name, shape, dtype, initializer)

在搭建网络的时候,一般都会通过variable_scope(name)来定义我们的命名域,方便图的结构展示,此时的op的名字就会变成:name/opname,方便统一的管理。

除了命名域之外,一般还会搭配一种参数的定义方法:
tf.get_variable(name)tf.Variable(name),这两种方法的区别是get_variable()方法对于那些名字相同的参数,当命名域scope调用reuse_variable()的时候,不会重新生成一个变量,而是用同一个名字的参数,而tf.Variable()则不能够重用参数

用以下的代码作为例子,假设有两层的CNN,分别有两个卷积核参数W1和W2,如果多次调用这个前向传播的网络,那么就会生成两次的卷积核(共4个)

def my_image_filter(input_images):
    conv1_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv1_weights")
    conv1_biases = tf.Variable(tf.zeros([32]), name="conv1_biases")
    conv1 = tf.nn.conv2d(input_images, conv1_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(conv1 + conv1_biases)

    conv2_weights = tf.Variable(tf.random_normal([5, 5, 32, 32]),
        name="conv2_weights")
    conv2_biases = tf.Variable(tf.zeros([32]), name="conv2_biases")
    conv2 = tf.nn.conv2d(relu1, conv2_weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv2 + conv2_biases)

如果对上面的前向传播方法进行命名域方法改造:

def my_image_filter(input_images):
    with tf.variable_scope("conv1"):
        # Variables created here will be named "conv1/weights", "conv1/biases".
        relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Variables created here will be named "conv2/weights", "conv2/biases".
        return conv_relu(relu1, [5, 5, 32, 32], [32])

if __name__ =="__main__":
    with tf.variable_scope("image_filters") as scope:
        result1 = my_image_filter(image1)
        scope.reuse_variables() ## 重用了参数
        result2 = my_image_filter(image2)

2.持久化与重载(saver方法)

Tensorflow的模型保存主要分为两个部分:图(Meta Graph)和各节点、参数的值(Checkpoint file)。

Tensorflow的持久化与重载主要用到了几个方法:
- tf.train.saver()
- saver.restore(sess, savepath) //saver是tf.train.saver()的实例

  1. 因tf的变量值存在与会话sess中,所以在保存的时候必须连着对话一起保存,在保存的时候,saver也会把当前的默认图也保存在你指定的路径中并形成.meta文件来保存图结构信息:

import tensorflow as tf
w1 = tf.Variable(tf.random_normal(shape=[2]), name='w1')
w2 = tf.Variable(tf.random_normal(shape=[5]), name='w2')
saver = tf.train.Saver()
sess = tf.Session()
sess.run(tf.global_variables_initializer())
saver.save(sess, 'my_test_model',globle_step=1000)

# 以上的代码能够保存当前的图结构和状态信息并保存为以下文件:
# globle_step是指每1000次保存一次
# my_test_model-1000.data-00000-of-00001
# my_test_model-1000.index
# my_test_model-1000.meta
# checkpoint

用以下的代码可以恢复之前的代码并继续训练等:

with tf.Session() as sess:    
    saver = tf.train.import_meta_graph('my-model-1000.meta')
    saver.restore(sess,tf.train.latest_checkpoint('./'))
    print(sess.run('w1:0'))
##Model has been restored. Above statement will print the saved value of w1.

以下是实际上的应用:

在一般的情况之下,如果没有原来的文件,就需要重新的加载我们的图结构,也就是meta文件,然后在进行我们的参数重载,需要注意的是我们的网络中的placeholder的值是不会被保存在在网络中的,只会保存他的大小,可以在重载了图和数据后继续训练或应用。

import tensorflow as tf

#准备我们的输入数据的placeholder,和feed_dict。
w1 = tf.placeholder("float", name="w1")
w2 = tf.placeholder("float", name="w2")
b1= tf.Variable(2.0,name="bias")
feed_dict ={w1:4,w2:8}

#定义一个我们即将要用于存储的节点运算
w3 = tf.add(w1,w2)
w4 = tf.multiply(w3,b1,name="op_to_restore")
sess = tf.Session()
sess.run(tf.global_variables_initializer())

#创建一个Saver()对象来对参数和图结构进行保存
saver = tf.train.Saver()

#Run the operation by feeding input
print sess.run(w4,feed_dict)
#Prints 24 which is sum of (w1+w2)*b1 

#现在保存图结构和参数
saver.save(sess, 'my_test_model',global_step=1000)

在保存了图结构和参数之后,可以用以下方法来对图进行恢复或者修改:
- tf.train.import_meta_graph(name)
- saver.restore(sess, name)
- graph.get_tensor_by_name(“name:0”)这里的参数中,name指的是节点名称,后面跟的“:0”是指节点参数。

import tensorflow as tf

sess=tf.Session()    
#这里开始加载我们之前保存的图文件和参数。
saver = tf.train.import_meta_graph('my_test_model-1000.meta')
## restore这里的最后一个参数是选择最后保存的那个checkpoint,通过查找index文件可以找到
saver.restore(sess,tf.train.latest_checkpoint('./'))


# 下面是重新读取图中的placeholder信息来进行运算。
# 从读取到的节点来构成心的feed_dict来进行训练

graph = tf.get_default_graph()
w1 = graph.get_tensor_by_name("w1:0")
w2 = graph.get_tensor_by_name("w2:0")
feed_dict ={w1:13.0,w2:17.0}

#现在抽取想要计算的节点名称来进行计算
op_to_restore = graph.get_tensor_by_name("op_to_restore:0")

#这里是在原来的图上新添加的运算节点,如果只想用原来的图的话可以不添加,直接run
add_on_op = tf.multiply(op_to_restore,2)

print sess.run(add_on_op,feed_dict)
#This will print 120.

以上就是一般的运用,需要注意的是: 这样的加载暂时我只能运用在前向传播网络中,在有求导等运算的时候,只能用来再一次的训练,而不能直接的运用到实际的预测中,因为优化器的运算中并没有用tf.get_variable来进行参数的构造,也就是不能重用,在多次调用的时候会重新新建节点而该节点并不在之前保存的checkpoint文件中而导致报错。例如新建了link_1/w_1而不是重用原来的link/w导致报错。

3. 通过固化参数到图中来保存(便于广泛的传播)

除了上面的Saver的方法来保存模型之外,还有其他的方法来保存,这种方法也被称为“固化”,通过把参数固化到网络图结构中,不用每次都加载meta文件中图,然后再加载checkpoint文件中的参数,而只需要把固化网络保存到一个文件中即可,当然也必须要知道需要求的节点名称才能运用:

tf.get_default_graph()返回当前会话的默认图
tf.Graph.as_graph_def()返回一个图的序列化的GraphDef表示
    序列化的GraphDef可以导入至另一个图中(使用 import_graph_def())
                              或者使用C++ Session API

graph_util模块的convert_variables_to_constants(
    sess,              ----------------变量所在的会话
    input_graph_def,      -------------持有需要保存的网络结构的GraphDef对象
    output_node_names,    -------------需要保存的节点名称,注意命名域[scope_name/op_name]
    variable_names_whitelist=None,   --要转换的变量名(在默认情况下,所有的变量都被转换)。
    variable_names_blacklist=None   ---变量名的集合,省略转换为常量
                                )
import_graph_def(
    graph_def,     -----pb文件解析出来的GraphDef对象
    input_map=None, ----需要映射到图中op的值,这里是用来填放feed_dict的
    return_elements=None,  --网络需要返回的值
    name=None,     -------这个操作的名称
    op_dict=None,  -------已经弃用的选项
    producer_op_list=None
)

以下是代码结构:

text_input = tf.placeholder(
            dtype=tf.int32, shape=[BATCH_SIZE, self.LENTH_MAX], name="text_input")
text_input_change = tf.reshape(text_input, [BATCH_SIZE, -1], name="Input")
        keep_prob = tf.placeholder(dtype=tf.float32, name="keep_prob")
label = tf.placeholder(dtype=tf.int64, shape=[BATCH_SIZE, ], name="label")

embeding_var = tf.Variable(tf.random_uniform(
            shape=[self.VOCABULARY_SIZE, self.EMBEDING_SIZE]), dtype=tf.float32, name='embeding_var')

## batch_embeding: size [BATCH_SIZE, LENTH_MAX, EMBEDING_SIZE]
batch_embeding = tf.nn.embedding_lookup(embeding_var, text_input_change)

batch_embeding_normal = tf.reshape(
            batch_embeding, [-1, self.LENTH_MAX, self.EMBEDING_SIZE, 1])

# output = self.interface(batch_embeding_normal, keep_prob)
output = self.interface_column(batch_embeding_normal, keep_prob)

with tf.variable_scope("loss"):
    loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(
                logits=output, labels=label, name="loss"))


with tf.variable_scope("accuracy"):
    accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(tf.sigmoid(output),1), label), dtype = tf.float32))

result = tf.nn.softmax(output, name='result')

以上是对网络结构的一个定义,具体代码请看github。
然后我们对我们需要的图结构和输出节点进行保存,由于我们图结构是相对复杂的,所以在保存的时候一般指明我们需要的节点,然后根据依赖关系来保存图。

graph_def = tf.get_default_graph().as_graph_def()

output_graph = graph_util.convert_variables_to_constants(sess, graph_def, ['result'])
## 通过gfile模块来保存固化的图为pb文件
with tf.gfile.GFile("./log/combined_model.pb","wb") as f:
    f.write(output_graph.SerializeToString()) ##对图进行序列化保存

重新加载图:

model_filename = "./log/combined_model.pb"
graph = tf.Graph()

with graph.as_default():
## 从存储的pb文件中读取GraphDef对象
    with tf.gfile.FastGFile(model_filename, 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
    ## 读取文本
    train_datareader = read_utils.ReadData(
                    self.LENTH_MAX, mode="train", load_size=self.LOAD_SIZE, vocabulary_size=self.VOCABULARY_SIZE)
    textnum = train_datareader.Test(text)
    ## 构造新的feed_dict
    feed_dict = {'text_input':textnum, 'keep_prob':1.0}
    ## 把新的feed_dict填入固化的图中运算
    result=tf.import_graph_def(graph_def, input_map=feed_dict, return_elements=['result:0'])      
    ##不指明返回节点时,无返回类型的操作,把文件中的graph导入默认graph
with tf.Session(graph = graph) as sess:
            last = sess.run(result)

通过以上的方法就能够把训练好的图用于实际应用之中,而且只需要一个文件即可再次使用。

你可能感兴趣的:(Tensorflow,深度学习,Tensorflow,持久化)