• 带你了解TensorFlow pb模型常用处理方法


    摘要:TensorFlow 模型训练完成后,通常会通过frozen过程保存一个最终的pb模型。

    本文分享自华为云社区《TensorFlow pb模型修改和优化》,作者:luchangli。

    TensorFlow 模型训练完成后,通常会通过frozen过程保存一个最终的pb模型。保存的pb模型是以GraphDef数据结构保存的,可以序列化保存为二进制pb模型或者文本pbtxt模型。GraphDef本质上是一个DAG有向无环图,里面主要是存放了一个算子node list,每个算子具有名称,attr等内容,以及通过input包含了node之间的连接关系。

    整个GraphDef的输入节点是以Placeholder节点来标识的,模型参数权重通常是以Const节点来保存的。不同于onnx,GraphDef没有对输出进行标识,好处是可以通过node_name:idx来引用获取任意一个节点的输出,缺点是一般需要通过netron手动打开查看模型输出,或者通过代码分析没有输出节点的node作为模型输出节点。下面简单介绍下pb模型常用的一些处理方法。

    pb模型保存

    1. # write pb model
    2. with tf.io.gfile.GFile(model_path, "wb") as f:
    3. f.write(graph_def.SerializeToString())
    4. # write pbtxt model
    5. tf.io.write_graph(graph_def, os.path.dirname(model_path), os.path.basename(model_path))

    创建node

    1. from tensorflow.core.framework import attr_value_pb2
    2. from tensorflow.core.framework import node_def_pb2
    3. from tensorflow.python.framework import tensor_util
    4. pld_node = node_def_pb2.NodeDef()
    5. pld_node.name = name
    6. pld_node.op = "Placeholder"
    7. shape = tf.TensorShape([None, 3, 256, 256])
    8. pld_node.attr["shape"].CopyFrom(attr_value_pb2.AttrValue(shape=shape.as_proto()))
    9. dtype = tf.dtypes.as_dtype("float32")
    10. pld_node.attr["dtype"].CopyFrom(attr_value_pb2.AttrValue(type=dtype.as_datatype_enum))
    11. # other commonly used setting
    12. node.input.extend(in_node_names)
    13. node.attr["value"].CopyFrom(
    14. attr_value_pb2.AttrValue(tensor=tensor_util.make_tensor_proto(
    15. np_array, np_array.type, np_array.shape)))

    构建模型和保存

    1. import tensorflow as tf
    2. import numpy as np
    3. tf.compat.v1.disable_eager_execution()
    4. tf.compat.v1.reset_default_graph()
    5. m = 200
    6. k = 256
    7. n = 128
    8. a_shape = [m, k]
    9. b_shape = [k, n]
    10. np.random.seed(0)
    11. input_np = np.random.uniform(low=0.0, high=1.0, size=a_shape).astype("float32")
    12. kernel_np = np.random.uniform(low=0.0, high=1.0, size=b_shape).astype("float32")
    13. # 构建模型
    14. pld1 = tf.compat.v1.placeholder(dtype="float32", shape=a_shape, name="input1")
    15. kernel = tf.constant(kernel_np, dtype="float32")
    16. feed_dict = {pld1: input_np}
    17. result_tf = tf.raw_ops.MatMul(a=pld1, b=kernel, transpose_a=False, transpose_b=False)
    18. with tf.compat.v1.Session() as sess:
    19. results = sess.run(result_tf, feed_dict=feed_dict)
    20. print("results:", results)
    21. # 保存模型
    22. dump_model_name = "matmul_graph.pb"
    23. graph = tf.compat.v1.get_default_graph()
    24. graph_def = graph.as_graph_def()
    25. with tf.io.gfile.GFile(dump_model_name, "wb") as f:
    26. f.write(graph_def.SerializeToString())

    当然一般用其他方式而不是raw_ops构建模型。

    pb模型读取

    1. from google.protobuf import text_format
    2. graph_def = tf.compat.v1.GraphDef()
    3. # read pb model
    4. with tf.io.gfile.GFile(model_path, "rb") as f:
    5. graph_def.ParseFromString(f.read())
    6. # read pbtxt model
    7. with open(model_path, "r") as pf:
    8. text_format.Parse(pf.read(), graph_def)

    node信息打印

    常用信息:

    1. node.name
    2. node.op
    3. node.input
    4. node.device
    5. # please ref https://www.tensorflow.org/api_docs/python/tf/compat/v1/AttrValue
    6. node.attr[attr_name].f # b, i, tensor, etc.
    7. # graph_def中node遍历:
    8. for node in graph_def.node:
    9. ##

    对于node的input,一般用node_name:idx如node_name:0来表示输入来自上一个算子的第idx个输出。:0省略则是默认为第0个输出。 名称前面加^符号是控制边。这个input是一个string list,这里面的顺序也对应这个node的各个输入的顺序。

    创建GraphDef和添加node

    1. graph_def_n = tf.compat.v1.GraphDef()
    2. for node in graph_def_o.node:
    3. node_n = node_def_pb2.NodeDef()
    4. node_n.CopyFrom(node)
    5. graph_def_n.node.extend([node_n])
    6. # you probably need copy other value like version, etc. from old graph
    7. graph_def_n.version = graph_def_o.version
    8. graph_def_n.library.CopyFrom(graph_def_o.library)
    9. graph_def_n.versions.CopyFrom(graph_def_o.versions)

    return graph_def_n

    没有onnx模型往graph里面添加节点的topo排序要求

    设置placeholder的shape

    参考前面创建node部分,通过修改Placeholder的shape属性。

    模型shape推导

    需要导入模型到tf:tf.import_graph_def(graph_def, name='')。当然需要先设置正确的pld的shape。

    然后获取node的输出tensor:graph.get_tensor_by_name(node_name + ":0")。

    最后可以从tensor里面获取shape和dtype。

    pb模型图优化

    思路一般比较简单:

    1,子图连接关系匹配,比如要匹配conv2d+bn+relu这个pattern连接关系。由于每个node只保存其输入的node连接关系,要进行DFS/BFS遍历图一般需要每个node的输入输出,这可以首先读取所有的node连接关系并根据input信息同时创建一个output信息map。

    2,子图替换,先创建新的算子,再把旧的算子替换为新的算子。这个需要创建新的node或者直接修改原来的node。旧的不要的算子可以创建个新图拷贝时丢弃,新的node可以直接extend到graph_def。

    3,如果替换为TF内置的算子,算子定义可以参考tensorflow raw_ops中的定义,但是有些属性(例如数据类型attr "T")没有列出来:https://www.tensorflow.org/api_docs/python/tf/raw_ops

    当然也可以替换为自定义算子,这就需要用户开发和注册自定义算子:https://www.tensorflow.org/guide/create_op

    如上所述,TensorFlow的pb模型修改优化可以直接使用python代码实现,极大简化开发过程。当然TensorFlow也可以注册grappler和post rewrite图优化pass在C++层面进行图优化,后者除了可以用于推理,也可以用于训练优化。

    saved model与pb模型的相互转换

    可以参考:tensorflow 模型导出总结 - 知乎

    saved model保存的是一整个训练图,并且参数没有冻结。而只用于模型推理serving并不需要完整的训练图,并且参数不冻结无法进行转TensorRT等极致优化。当然也可以saved_model->frozen pb->saved model来同时利用两者的优点。

    pb转onnx

    使用tf2onnx库GitHub - onnx/tensorflow-onnx: Convert TensorFlow, Keras, Tensorflow.js and Tflite models to ONNX

    1. #!/bin/bash
    2. graphdef=input_model.pb
    3. inputs=Placeholder_1:0,Placeholder_2:0
    4. outputs=output0:0,output1:0
    5. output=${graphdef}.onnx
    6. python -m tf2onnx.convert \
    7. --graphdef ${graphdef} \
    8. --output ${output} \
    9. --inputs ${inputs} \
    10. --outputs ${outputs}\
    11. --opset 12

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    C++:深拷贝和浅拷贝——拷贝构造、赋值构造必须自定义
    Centos安装RabbitMQ超详细(必须收藏)
    cario库——C++画图
    Halcon 双相机标定与拼图(一)
    【操作系统】操作系统笔记
    客户需求调研的三个实用工具
    spring mvc \ spring boot \ spring cloud
    波束形成,通过matlab仿真不同参数的波束形成以及旁絆级
    Python综合案例(数据计算相关方法)
    Promise系列总结
  • 原文地址:https://blog.csdn.net/devcloud/article/details/126298807