TensorFlow:有没有办法将冻结图转换为检查点模型



可以将检查点模型转换为冻结图(.ckpt文件到.pb文件)。但是,是否有将 pb 文件再次转换为检查点文件的反向方法?

我想它需要将常量转换回变量 - 有没有办法将正确的常量识别为变量并将它们恢复到检查点模型中?

目前支持将变量转换为常量:https://www.tensorflow.org/api_docs/python/tf/graph_util/convert_variables_to_constants

但反之则不然。

这里也提出了一个类似的问题:Tensorflow:将常量张量从预先训练的Vgg模型转换为变量

但该解决方案依赖于使用 ckpt 模型来恢复权重变量。有没有办法从PB文件而不是检查点文件恢复权重变量?这对于重量修剪可能很有用。

有一种方法可以通过图形编辑器将常量转换回TensorFlow中的可训练变量。但是,您需要指定要转换的节点,因为我不确定是否有办法以强大的方式自动检测这一点。

以下是步骤:

步骤 1:加载冻结图形

我们将.pb文件加载到图形对象中。

import tensorflow as tf
# Load protobuf as graph, given filepath
def load_pb(path_to_pb):
with tf.gfile.GFile(path_to_pb, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
with tf.Graph().as_default() as graph:
tf.import_graph_def(graph_def, name='')
return graph
tf_graph = load_pb('frozen_graph.pb')

步骤 2:查找需要转换的常量

以下是在图形中列出节点名称的 2 种方法:

  • 使用此脚本打印它们
  • print([n.name for n in tf_graph.as_graph_def().node])

您要转换的节点可能被命名为"Const"。可以肯定的是,最好在 Netron 中加载图形以查看哪些张量存储了可训练的权重。通常,可以安全地假设所有 const 节点都曾经是变量。

确定这些节点后,让我们将它们的名称存储到一个列表中:

to_convert = [...] # names of tensors to convert

步骤 3:将常量转换为变量

运行此代码以转换指定的常量。它本质上为每个常量创建相应的变量,并使用 GraphEditor 从图形中解开常量,并将变量挂钩。

import numpy as np
import tensorflow as tf
import tensorflow.contrib.graph_editor as ge
const_var_name_pairs = []
with tf_graph.as_default() as g:
for name in to_convert:
tensor = g.get_tensor_by_name('{}:0'.format(name))
with tf.Session() as sess:
tensor_as_numpy_array = sess.run(tensor)
var_shape = tensor.get_shape()
# Give each variable a name that doesn't already exist in the graph
var_name = '{}_turned_var'.format(name)
# Create TensorFlow variable initialized by values of original const.
var = tf.get_variable(name=var_name, dtype='float32', shape=var_shape,   
initializer=tf.constant_initializer(tensor_as_numpy_array))
# We want to keep track of our variables names for later.
const_var_name_pairs.append((name, var_name))
# At this point, we added a bunch of tf.Variables to the graph, but they're
# not connected to anything.
# The magic: we use TF Graph Editor to swap the Constant nodes' outputs with
# the outputs of our newly created Variables.
for const_name, var_name in const_var_name_pairs:
const_op = g.get_operation_by_name(const_name)
var_reader_op = g.get_operation_by_name(var_name + '/read')
ge.swap_outputs(ge.sgv(const_op), ge.sgv(var_reader_op))

步骤4:将结果另存为.ckpt

with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
save_path = tf.train.Saver().save(sess, 'model.ckpt')
print("Model saved in path: %s" % save_path)

还有中提琴!此时您应该完成:)我能够自己完成这项工作,并验证模型权重是否保留 - 唯一的区别是图形现在可以训练。如果有任何问题,请告诉我。

如果你有构建网络的源代码,它可以相对容易地完成,因为卷积/全连接的名称没有被冻结图方法改变,所以你基本上可以调查图并将常量操作与其变量匹配,只需加载带有常量值的变量。

感谢@Almog大卫上面精彩的回答;我面临着完全相同的情况

  • 我有frozen_inference_graph.pb,但没有检查站;
  • 我有生成frozen_inference_graph.pb的源代码,但我不知道参数。

以下是解决困境的三个步骤。

1. 从frozen_inference_graph.pb中获取节点名称和值对

import tensorflow as tf
from tensorflow.python.framework import tensor_util
def get_node_values(old_graph_path):
old_graph = tf.Graph()
with old_graph.as_default():
old_graph_def = tf.GraphDef()
with tf.gfile.GFile(old_graph_path, "rb") as fid:
serialized_graph = fid.read()
old_graph_def.ParseFromString(serialized_graph)
tf.import_graph_def(old_graph_def, name='')
old_sess = tf.Session(graph=old_graph)
# get all the nodes from the graph def
nodes = old_sess.graph.as_graph_def().node
value_dict = {}
for node in nodes:
value = node.attr['value'].tensor
try:
# get name and value (numpy array) from tensor 
value_dict[node.name] = tensor_util.MakeNdarray(value) 
except:
# some tensor doesn't have value; for example np.squeeze
# just ignore it 
pass
return value_dict
value_dict = get_node_values("frozen_inference_graph.pb")

2. 使用现有代码创建新图;调整模型参数,直到新图中的所有节点都存在于value_dict

new_graph = tf.Graph()
with new_graph.as_default():
tf.create_global_step()
#existing code 
# ...
# ...
# ...
model_variables = tf.model_variables()
unseen_variables = set(model_variable.name[:-2] for model_variable in model_variables) - set(value_dict.keys())
print  ("n".join(sorted(list(unseen_variables))))

3.为变量赋值并保存到检查点(或保存到图形)

new_graph_path = "model.ckpt"
saver = tf.train.Saver(model_variables)
assign_ops = []
for variable in model_variables:
print ("Assigning", variable.name[:-2])
# variable names have ":0" but constant names doesn't have.
value = value_dict[variable.name[:-2]]
assign_ops.append(variable.assign(value))
sess =session.Session(graph = new_graph)
sess.run(tf.global_variables_initializer())
sess.run(assign_ops)
saver.save(sess, new_graph_path+"model.ckpt")

这是我能想到的解决这个问题的唯一方法。但是,它仍然存在一些缺点:如果重新加载模型检查点,您会发现(以及所有有用的变量)许多不需要的assign变量,例如Assign_700/value。这是不可避免的,看起来很丑。如果您有更好的建议,请随时发表评论。谢谢。

如果你有构建网络的源代码,它可以相对容易地完成,因为卷积/全连接的名称没有被冻结图方法改变,所以你基本上可以调查图并将常量操作与其变量匹配匹配,只需加载变量与常量值。

如果你没有构建网络的代码,它仍然可以完成,但要做到这一点并不简单。

例如,您可以搜索图形中的所有节点并查找常量类型的操作,然后在找到常量类型的所有操作后,您可以查看该操作是否连接到卷积/完全连接,例如......(或者您可以只转换它依赖于您的所有常量)。

找出要转换为变量的常量后,可以将变量添加到保存常量值的图形中,然后使用 Tensorflow 图形编辑器重新连接 const 操作与变量之间的连接(使用reroute_ts方法)。

完成后,您可以保存图形,当您再次加载它时,您将拥有变量(但请注意,常量仍将保留在图形中,但它们可以通过图形转换工具进行优化)

最新更新