将tensorflow 1.x & 2.x转化成onnx文件(以arcface-tf2人脸识别模型为例)

将tensorflow 1.x & 2.x转化成onnx文件

文章目录

*
将tensorflow 1.x & 2.x转化成onnx文件

+ 一、tensorflow 1.x转化成onnx文件
+
* 1、ckpt文件生成
* 2、打印权重参数名称
* 3、ckpt文件转pb
* 4、ckpt文件转onnx(–checkpoint)
+ 二、tensorflow 2.x转化成onnx文件
+
* 1、ckpt转savemodel(pb)
*
1)错误用法(不能冻结权重生成pb)
2)正确用法(saved_model)
* 2、pb转onnx
* 3、小总结(★★★)
+ 三、实操练习

一、tensorflow 1.x转化成onnx文件

1、ckpt文件生成

参考 tensorflow实现将ckpt转pb文件


import tensorflow as tf

'''
适合tf1.x  (tf2.x保存权重文件时没有meta文件)
checkpoint是检查点文件,文件保存了一个目录下所有的模型文件列表;
model.ckpt.meta文件保存了TensorFlow计算图的结构,可以理解为神经网络的网络结构,该文件可以被 tf.train.import_meta_graph 加载到当前默认的图来使用。
ckpt.data : 保存模型中每个变量的取值
'''
v1 = tf.Variable(tf.random_normal([1, 2]), name="v1")
v2 = tf.Variable(tf.random_normal([2, 3]), name="v2")
init_op = tf.global_variables_initializer()
saver = tf.train.Saver()
with tf.Session() as sess:
    sess.run(init_op)
    print("v1:", sess.run(v1))
    print("v2:", sess.run(v2))
    saver_path = saver.save(sess, "save/model.ckpt")
    print("Model saved in file:", saver_path)

之后会在save文件夹下产生4个文件(index,checkpint,meta网络结构文件和权重文件,这里注意 tf2在生成ckpt文件时,不会产生后缀名为meta的文件

|-save
    |-checkpoint
    |-model.ckpt.data-00000-of-00001
    |-model.ckpt.index
    |-model.ckpt.meta

2、打印权重参数名称

参考TensorFlow拾遗(一) 打印网络结构与变量


import tensorflow as tf
import os

'''适合tf1.x版本打印网络权重参数,tf2.x版本会报错AttributeError: module 'tensorflow_core._api.v2.train' has no attribute 'import_meta_graph'''

def txt_save(data, output_file):
    file = open(output_file, 'a')
    for i in data:
        s = str(i) + '\n'
        file.write(s)
    file.close()

def network_param(input_checkpoint, output_file=None):
    saver = tf.train.import_meta_graph(input_checkpoint + ".meta", clear_devices=True)
    with tf.Session() as sess:
        saver.restore(sess, input_checkpoint)
        variables = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES)
        for i in variables:
            print(i)
        txt_save(variables, output_file)

if __name__ == '__main__':
    checkpoint_path = './save/model.ckpt'
    output_file = 'network_param.txt'
    if not os.path.exists(output_file):
        network_param(checkpoint_path, output_file)

输出结果如下

<tf.variable 'v1:0' shape="(1," 2) dtype="float32_ref">
<tf.variable 'v2:0' shape="(2," 3) dtype="float32_ref">
</tf.variable></tf.variable>

3、ckpt文件转pb

参考 tensorflow实现将ckpt转pb文件


''' 将CKPT 转换成 PB格式的文件的过程可简述如下'''

'''
1、函数freeze_graph中,最重要的就是要确定"指定输出的节点名称",这个节点名称必须是原模型中存在的节点,对于freeze操作,我们需要定义输出结点的名字。因为网络其实是比较复杂的,定义了输出结点的名字,那么freeze的时候就只把输出该结点所需要的子图都固化下来,其他无关的就舍弃掉。因为我们freeze模型的目的是接下来做预测。所以,output_node_names一般是网络模型最后一层输出的节点名称,或者说就是我们预测的目标。
2、在保存的时候,通过convert_variables_to_constants函数来指定需要固化的节点名称,对于鄙人的代码,需要固化的节点只有一个:output_node_names。注意节点名称与张量的名称的区别,例如:"input:0"是张量的名称,而"input"表示的是节点的名称。
3、源码中通过graph = tf.get_default_graph()获得默认的图,这个图就是由saver = tf.train.import_meta_graph(input_checkpoint + '.meta', clear_devices=True)恢复的图,因此必须先执行tf.train.import_meta_graph,再执行tf.get_default_graph() 。
4、实质上,我们可以直接在恢复的会话sess中,获得默认的网络图,更简单的方法,如下:
'''
import tensorflow as tf
from tensorflow import graph_util

'''
通过传入 CKPT 模型的路径得到模型的图和变量数据
通过 import_meta_graph 导入模型中的图
通过 saver.restore 从模型中恢复图中各个变量的数据
通过 graph_util.convert_variables_to_constants 将模型持久化
'''
def freeze_graph(input_checkpoint, output_graph):
    '''
    :param input_checkpoint:
    :param output_graph: PB模型保存路径
    :return:
    '''

    saver = tf.train.import_meta_graph(input_checkpoint + '.meta', clear_devices=True)

    with tf.Session() as sess:
        saver.restore(sess, input_checkpoint)
        output_graph_def = graph_util.convert_variables_to_constants(
            sess=sess,
            input_graph_def=sess.graph_def,
            output_node_names=['v1','v2'])

        with tf.gfile.GFile(output_graph, "wb") as f:
            f.write(output_graph_def.SerializeToString())
        print("%d ops in the final graph." % len(output_graph_def.node))

if __name__ == '__main__':

    input_checkpoint='./save/model.ckpt'

    out_pb_path="./save/pb/frozen_model.pb"

    freeze_graph(input_checkpoint,out_pb_path)

4、ckpt文件转onnx(–checkpoint)

注意:ckpt不用先转pb, 直接用df2onnx的–checkpoint即可。以下命令在命令行操作,注意需要先在原模型中打印输出 网络的输入(inputs) 和 输出(outputs)名称
比如这样子:

参考

'''--checkpoint适用于tf1,不适用于tf2'''
python -m tf2onnx.convert --checkpoint tensorflow2onnx_test/save/model.ckpt.meta --inputs v1:0 --outputs v2:0 --output tensorflow2onnx_test/save/onnx/onnxModel.onnx --opset 9

二、tensorflow 2.x转化成onnx文件

tf2整合了Keras包, tf1上面的代码在tf2中基本无效了,这里参考tf2官方文档

1、ckpt转savemodel(pb)

1)错误用法(不能冻结权重生成pb)

参考https://blog.csdn.net/qq_37116150/article/details/105736728(好吧,冤有头债有主,只是为了避坑)

以下操作是在原始项目模型代码中执行的,可以在实践练习中看到。

[En]

The following actions are performed in the original project model code, which can be seen in the hands-on exercise.


        Convert Keras model to ConcreteFunction
        注意这个Input,是自己定义的输入层名
        full_model = tf.function(lambda Input: model(Input))
        full_model = full_model.get_concrete_function(
            tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype))

        frozen_func = convert_variables_to_constants_v2(full_model)
        frozen_func.graph.as_graph_def()

        layers = [op.name for op in frozen_func.graph.get_operations()]
        print("-" * 50)
        print("Frozen model layers: ")
        for layer in layers:
            print(layer)

        print("-" * 50)
        print("Frozen model inputs: ")
        print(frozen_func.inputs)
        print("Frozen model outputs: ")
        print(frozen_func.outputs)

        tf.io.write_graph(graph_or_graph_def=frozen_func.graph,
                          logdir="./checkpoints/",
                          name="arc_mbv2.pb",
                          as_text=False)

导出来只有一个pb文件。

2)正确用法(saved_model)

参考

tf.saved_model.save(model,"./checkpoints/pb")

导出来是一个文件夹。

|- pb
    |- assets
    |- variables
        |-variables.data-00000-of-00001
        |-variables.index
    |- saved_model.pb

2、pb转onnx

python -m tf2onnx.convert --saved-model RLDD_test/utils/recognition/pb_savemodel/  --output RLDD_test/utils/recognition/onnx/onnxModel1.onnx --opset 9

注意这里不用输入 --inputs--outputs参数

输出结果如下:

Skipping registering GPU devices...

2022-03-28 11:07:02.884797: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1096] Device interconnect StreamExecutor with strength 1 edge matrix:
2022-03-28 11:07:02.885144: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1102]      0
2022-03-28 11:07:02.885621: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] 0:   N
2022-03-28 11:07:04.447991: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:814] Optimization results for grappler item: graph_to_optimize
2022-03-28 11:07:04.448224: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:816]   constant_folding: Graph size after: 716 nodes (-274), 1540 edges (-548), t
ime = 784.703ms.

2022-03-28 11:07:04.449014: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:816]   function_optimizer: function_optimizer did nothing. time = 5.174ms.

2022-03-28 11:07:04.449179: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:816]   constant_folding: Graph size after: 716 nodes (0), 1540 edges (0), time =
259.701ms.

2022-03-28 11:07:04.449614: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:816]   function_optimizer: function_optimizer did nothing. time = 9.665ms.

2022-03-28 11:07:50,510 - INFO - Using tensorflow=2.1.0, onnx=1.8.0, tf2onnx=1.9.3/1190aa
2022-03-28 11:07:50,510 - INFO - Using opset <onnx, 9>
2022-03-28 11:11:22,648 - INFO - Computed 0 values for constant folding
2022-03-28 11:14:15,289 - INFO - Optimizing ONNX model
2022-03-28 11:14:19,545 - INFO - After optimization: BatchNormalization -45 (53->8), Cast -1 (1->0), Const -156 (290->134), Identity -6 (6->0), Reshape -17 (18->1),
Transpose -225 (227->2)
2022-03-28 11:14:19,929 - INFO -
2022-03-28 11:14:19,930 - INFO - Successfully converted TensorFlow model RLDD_test/utils/recognition/pb_savemodel/ to ONNX
2022-03-28 11:14:19,931 - INFO - Model inputs: ['input_image']
2022-03-28 11:14:19,932 - INFO - Model outputs: ['OutputLayer']
2022-03-28 11:14:19,933 - INFO - ONNX model is saved at RLDD_test/utils/recognition/onnx/onnxModel1.onnx
</onnx,>

3、小总结(★★★)

  1. 要想将ckpt转化成onnx, 必须要有模型源码,否则无法确定tf2onnx.convert的inputs和outputs参数( 适合tf1.x版本
  2. tf2由于没有meta文件,无法使用–checkpoint进行compile,所以只能先将ckpt转化成pb,注意 命名格式为saved_model.pb,否则会报错:
OSError: SavedModel file does not exist at: RLDD_test/utils/recognition/ckpt//{saved_model.pbtxt|saved_model.pb}
  1. tf2中的ckpt转化成savemodel时,会 自动生成包含 saved_model.pb 的文件夹,如果使用错误方法生成pb文件的话,在运行pb转onnx命令时,会报错:
RuntimeError: MetaGraphDef associated with tags 'serve' could not be found in SavedModel. To inspect available tag-sets in the SavedModel, please use the SavedModel
CLI: saved_model_cli

三、实操练习

任务:下载arcface-tf2 源码 + tf2预训练权重文件(自己之前尝试过训练 mobileNetV3 + arcface head进行人脸识别,但是属实训不好,经常”炸炉”,因此这里 万分感谢作者能够开源代码并提供预训练的权重文件),将基于 tf2的人脸识别项目转移到onnx推理引擎中。
在使用该模型进行推理时,会发现 MobileNetV2在使用 ArcFace Loss之前需要 进行特征和权重的L2归一化。这样再使用 点积相似度dot)时,可以 将计算的相似度落在 0~1 区间上,并且相似度越大,则该图像在特征空间上与原图像越近。(具体实例可以参考facex-zoo)

源码中有ResNet50和MobileNetV2两个模型,这里以MobileNetV2模型举个栗子

  1. 根据源码中README.md的提示,导入相应的模型权重文件,运行test.py
  2. 在test.py中加入这段代码, 将ckpt模型保存成saveModel的形式
tf.saved_model.save(model,"./checkpoints/pb")
  1. 生成savemodel文件夹之后, 使用如下命令将savemodel转化成onnx,注意修改savemodel路径。(此过程比较耗时,请耐心等待)
python -m tf2onnx.convert --saved-model RLDD_test/utils/recognition/pb_savemodel/  --output RLDD_test/utils/recognition/onnx/onnxModel1.onnx --opset 9

这里注意:不单单只有一个pb文件, 如果使用冻结权重保存的pb文件 ,在运行savemodel转化成onnx的命令会报错

  1. 接着使用onnxRuntime推理引擎加载onnx文件,即可实现结果的输出:

参考python关于onnx模型的一些基本操作

import cv2
import onnx
from onnx import helper
import onnxruntime
import numpy as np

if __name__ == '__main__':

    '''一、获取onnx模型的输出层'''

    '''二、获取中节点输出数据'''

    '''三、onnx前向InferenceSession的使用'''
    '''
        关于onnx的前向推理,onnx使用了onnxruntime计算引擎。
       onnx runtime是一个用于onnx模型的推理引擎。微软联合Facebook等在2017年搞了个深度学习以及机器学习模型的格式标准–ONNX,顺路提供了一个专门用于ONNX模型推理的引擎(onnxruntime)。
    '''

    sess = onnxruntime.InferenceSession('./onnx/onnxModel1.onnx')

    img = cv2.imread("../img/calibrate_glasses.jpg")
    img = cv2.resize(img, (112, 112))
    img = img.astype(np.float32) / 255.

    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    if len(img.shape) == 3:
        img = np.expand_dims(img, 0)

    outputs = sess.run([], {"input_image": img})
    print(outputs)

补充一点,没必要尝试用 cv2.dnn.readNet来加载onnx模型,可能会因为opencv-python版本报如下错误:

cv2.error: OpenCV(4.4.0) C:\Users\appveyor\AppData\Local\Temp\1\pip-req-build-1hg9yufe\opencv\modules\dnn\src\layers\convolution_layer.cpp:94: error: (-213:The function/feature is not implemented) Unsupported asymmetric padding in convolution layer in function 'cv::dnn::BaseConvolutionLayerImpl::BaseConvolutionLayerImpl'

还不如老老实实用 onnxRuntime

  1. 结合我之前独家配置的”秘方”(scrfd模型 + 自己构建的人脸数据库进行人脸检测),完成如下人脸识别任务。

将tensorflow 1.x & 2.x转化成onnx文件(以arcface-tf2人脸识别模型为例)
将tensorflow 1.x & 2.x转化成onnx文件(以arcface-tf2人脸识别模型为例)

Original: https://blog.csdn.net/qq_33934427/article/details/123800910
Author: 王小希ww
Title: 将tensorflow 1.x & 2.x转化成onnx文件(以arcface-tf2人脸识别模型为例)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/497019/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球