在搭建网络的时候,一般都会通过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)
Tensorflow的模型保存主要分为两个部分:图(Meta Graph)和各节点、参数的值(Checkpoint file)。
Tensorflow的持久化与重载主要用到了几个方法:
- tf.train.saver()
- saver.restore(sess, savepath) //saver是tf.train.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
导致报错。
除了上面的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)
通过以上的方法就能够把训练好的图用于实际应用之中,而且只需要一个文件即可再次使用。