.h5转onnx,获取onnx中间层特征值

0. 背景

因为近些年工作中基本都是使用pytorch框架,tensorflow框架还是最初的时候使用过一段时间,现在一个项目中给到的训练后的模型是基于tensorflow keras保存的.h5文件。但是在后面部署的时候需要使用onnx进行,所以需要进行一下模型的转换。

环境依赖

这些库之间都互相有依赖关系,经过多次尝试,各个库版本如下
tensorflow 2.11.0
onnx 1.11.0
protobuf 3.19.0
tf2onnx 1.14.0
numpy 1.20.0

步骤1. 从一个HDF5文件中加载一个Keras模型,并将其保存为TensorFlow的原生格式。

步骤2. 将TensorFlow模型(SavedModel格式)转换为ONNX(Open Neural Network Exchange)格式

整体代码和后续命令的意义可以分为两部分来解释:

import keras

model_path = "model2/model2.h5"
model = keras.models.load_model(model_path)
model.summary()

model.save('model_2_tfmodel', save_format='tf')
python -m tf2onnx.convert --saved-model ./model_2_tfmodel/ --output ./model2.onnx --opset 11 --verbose

第一部分(Python代码):

设置模型路径并加载模型:

model_path = "model2/model2.h5"  
model = keras.models.load_model(model_path)

这部分代码的作用是设置Keras模型(.h5格式)的路径,并将其加载到内存中,存储在变量model中。

model.summary()

这行代码用于打印模型的架构摘要,包括各层的名称、输出形状和参数数量。

保存模型为TensorFlow SavedModel格式:

model.save('model_2_tfmodel', save_format='tf')

这里,模型被保存为TensorFlow的SavedModel格式,这是一种更为通用和可移植的格式,特别适用于在不同平台或工具之间共享模型。

第二部分(命令行命令):

python -m tf2onnx.convert --saved-model ./model_2_tfmodel/ --output ./model2.onnx --opset 11 --verbose

这条命令使用了tf2onnx工具,该工具用于将TensorFlow模型(SavedModel格式)转换为ONNX(Open Neural Network Exchange)格式。ONNX是一个用于表示深度学习模型的开放标准,旨在使模型能够在不同的深度学习框架之间轻松交换。

–saved-model ./model_2_tfmodel/ 指定了要转换的TensorFlow SavedModel的路径。
–output ./model2.onnx 指定了转换后的ONNX模型的输出路径和文件名。
–opset 11 指定了ONNX操作符集(opset)的版本,这对于确保模型在不同框架之间的兼容性和功能正确性很重要。
–verbose 是一个可选参数,用于在转换过程中输出更多详细信息,有助于调试和了解转换过程的进展。
综上所述,整体代码和命令的意义是:首先,使用Keras加载一个预训练的深度学习模型,查看其结构摘要,并将其保存为TensorFlow SavedModel格式。然后,使用tf2onnx工具将该SavedModel转换为ONNX格式,以便在不同的深度学习框架或工具中使用。

keras指定输出特定层的特征值

from tensorflow.keras.models import Model
model = load_model('./tsimplemodel.h5')
# 创建一个新的 model,该 model 的输出是中间层的输出 -2为倒数第二层 -1为倒数第一层
intermediate_layer_model = Model(inputs=model.input, outputs=model.layers[-3].output)

基于onnx获取指定层的输出特征值

在部署的时候,把上面的keras保存的.h5文件,转换为了onnx文件,同样想获取对应层的输出特征值
我们需要获取输出的中间层的名称,ONNX中并没有直接的层索引
但你可以通过一些方法来识别它,比如查看ONNX模型的结构或使用其他工具

使用ONNX Python库:
可以使用ONNX库来加载模型并检查其结构。这包括获取模型的输入和输出信息,以及遍历模型的节点(层)。
步骤:
安装ONNX库(如果还没有安装的话)
使用ONNX库加载模型,并获取图的节点信息
遍历节点,查找你感兴趣的层,并记下它的名称
示例代码:

import onnx  
import onnx.helper as helper  

# 加载ONNX模型  
model = onnx.load("model2.onnx")  

# 遍历图中的所有节点  
# 打印出模型中所有节点的操作类型、名称、输入和输出
for node in model.graph.node:  
    print(node.op_type, node.name, node.input, node.output)  

# 你可以根据op_type(操作类型)或名称来识别你感兴趣的层

输出结果如下

D:\Anaconda3\envs\py3.8\python.exe E:/doubleNet/checkONNX.py
MatMul StatefulPartitionedCall/sequential_2/dense_10/MatMul ['dense_10_input', 'StatefulPartitionedCall/sequential_2/dense_10/MatMul/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_10/MatMul:0']
Add StatefulPartitionedCall/sequential_2/dense_10/BiasAdd ['StatefulPartitionedCall/sequential_2/dense_10/MatMul:0', 'StatefulPartitionedCall/sequential_2/dense_10/BiasAdd/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_10/BiasAdd:0']
Relu StatefulPartitionedCall/sequential_2/dense_10/Relu ['StatefulPartitionedCall/sequential_2/dense_10/BiasAdd:0'] ['StatefulPartitionedCall/sequential_2/dense_10/Relu:0']
MatMul StatefulPartitionedCall/sequential_2/dense_11/MatMul ['StatefulPartitionedCall/sequential_2/dense_10/Relu:0', 'StatefulPartitionedCall/sequential_2/dense_11/MatMul/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_11/MatMul:0']
Add StatefulPartitionedCall/sequential_2/dense_11/BiasAdd ['StatefulPartitionedCall/sequential_2/dense_11/MatMul:0', 'StatefulPartitionedCall/sequential_2/dense_11/BiasAdd/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_11/BiasAdd:0']
Relu StatefulPartitionedCall/sequential_2/dense_11/Relu ['StatefulPartitionedCall/sequential_2/dense_11/BiasAdd:0'] ['StatefulPartitionedCall/sequential_2/dense_11/Relu:0']
MatMul StatefulPartitionedCall/sequential_2/dense_12/MatMul ['StatefulPartitionedCall/sequential_2/dense_11/Relu:0', 'StatefulPartitionedCall/sequential_2/dense_12/MatMul/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_12/MatMul:0']
Add StatefulPartitionedCall/sequential_2/dense_12/BiasAdd ['StatefulPartitionedCall/sequential_2/dense_12/MatMul:0', 'StatefulPartitionedCall/sequential_2/dense_12/BiasAdd/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_12/BiasAdd:0']
Relu StatefulPartitionedCall/sequential_2/dense_12/Relu ['StatefulPartitionedCall/sequential_2/dense_12/BiasAdd:0'] ['StatefulPartitionedCall/sequential_2/dense_12/Relu:0']
MatMul StatefulPartitionedCall/sequential_2/dense_13/MatMul ['StatefulPartitionedCall/sequential_2/dense_12/Relu:0', 'StatefulPartitionedCall/sequential_2/dense_13/MatMul/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_13/MatMul:0']
Add StatefulPartitionedCall/sequential_2/dense_13/BiasAdd ['StatefulPartitionedCall/sequential_2/dense_13/MatMul:0', 'StatefulPartitionedCall/sequential_2/dense_13/BiasAdd/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_13/BiasAdd:0']
Relu StatefulPartitionedCall/sequential_2/dense_13/Relu ['StatefulPartitionedCall/sequential_2/dense_13/BiasAdd:0'] ['StatefulPartitionedCall/sequential_2/dense_13/Relu:0']
MatMul StatefulPartitionedCall/sequential_2/dense_14/MatMul ['StatefulPartitionedCall/sequential_2/dense_13/Relu:0', 'StatefulPartitionedCall/sequential_2/dense_14/MatMul/ReadVariableOp:0'] ['StatefulPartitionedCall/sequential_2/dense_14/MatMul:0']
Add StatefulPartitionedCall/sequential_2/dense_14/BiasAdd ['StatefulPartitionedCall/sequential_2/dense_14/MatMul:0', 'StatefulPartitionedCall/sequential_2/dense_14/BiasAdd/ReadVariableOp:0'] ['dense_14']

Process finished with exit code 0

在Keras中,可以使用Model来获取任何层的输出作为一个新的模型,这被称为"layer model"或"intermediate model"。在案例中,已经创建了一个intermediate_layer_model来获取倒数第三层的输出。

当将Keras模型转换为ONNX模型后,要获取特定层的特征值,需要执行以下步骤:

  1. 确定ONNX模型中的节点名称:从您提供的ONNX模型输出内容,您需要找到对应于Keras中倒数第三层输出的节点名称。根据输出,这可能是类似StatefulPartitionedCall/sequential_2/dense_11/Relu:0的节点。

  2. 设置ONNX模型的输入:与Keras模型一样,您需要准备输入数据。

  3. 使用ONNX运行时来执行模型:使用ONNX运行时(ONNX Runtime)来运行模型并捕获特定层的输出。

  4. 获取特定层的输出:在ONNX Runtime中,您可以指定输出名称来获取特定层的输出。

以下是一个示例代码,演示如何使用ONNX Runtime获取特定层的特征值:

import onnx
import onnxruntime as ort

# 加载ONNX模型
onnx_model = onnx.load("model1.onnx")

# 创建ONNX运行时会话
session = ort.InferenceSession("model1.onnx")

# 准备输入数据,这里假设您的输入数据名为'input_data'并且已经准备好
input_name = session.get_inputs()[0].name
input_data = ...  # 这里替换为实际的输入数据

# 执行模型,获取倒数第三层的特征值
# 根据上面的ONNX模型输出,假设倒数第三层的输出名称为'StatefulPartitionedCall/sequential_2/dense_11/Relu:0'
output_name = 'StatefulPartitionedCall/sequential_2/dense_11/Relu:0'
raw_output = session.run([output_name], {input_name: input_data})[0]

# raw_output 现在包含了您想要的特定层的特征值

请注意,您需要将'StatefulPartitionedCall/sequential_2/dense_11/Relu:0'替换为实际的层输出名称,这取决于您的模型和转换过程。此外,input_data应该是与您的模型输入维度匹配的NumPy数组或其他合适的数据类型。

确保您已经安装了ONNX和ONNX Runtime库。如果尚未安装,可以通过以下命令进行安装:

pip install onnx onnxruntime

使用ONNX Runtime,您可以方便地在ONNX模型上执行推理,并获取任何层的输出。

问题:

onnxruntime.capi.onnxruntime_pybind11_state.InvalidArgument: [ONNXRuntimeError] : 2 : INVALID_ARGUMENT : Invalid Output Name:StatefulPartitionedCall/sequential_2/dense_12/BiasAdd:0
要访问 ONNX 模型的内部结构和节点信息,你可以使用 ONNX Python API,而不是 ONNX Runtime API。
但是,如果你想要在 ONNX Runtime 中获取模型的中间层输出,这通常不是直接支持的,因为 ONNX Runtime 主要用于高效的模型推理,并不提供直接访问中间层输出的接口

解决方式

修改ONNX模型:使用Python的onnx库手动修改模型的.graph.output部分,将希望获取的中间层输出添加到输出列表中

import onnx

# 加载模型
model = onnx.load('model1.onnx')

# 获取想要的中间层输出名
intermediate_layer_output_name  = 'StatefulPartitionedCall/sequential_2/dense_12/BiasAdd:0'

found = False
for node in model.graph.node:
    if any(output == intermediate_layer_output_name for output in node.output):
        found = True


if not found:
    raise ValueError(f"Intermediate layer with output name '{intermediate_layer_output_name}' not found.")

# 确保中间层输出不在已有输出中
if intermediate_layer_output_name not in [o.name for o in model.graph.output]:
    # 添加中间层输出到模型输出列表
    new_output = onnx.ValueInfoProto()
    new_output.name = intermediate_layer_output_name
    model.graph.output.append(new_output)

#保存修改后的模型
onnx.save(model, 'modified_model1.onnx')

使用修改后的模型modified_model1.onnx进行推理。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

算法服务端

# -- coding: utf-8 --**
import json
import pickle
import onnxruntime
import numpy as np
import time
import socket


def getFeatures(platform_1, platform_2):
    features = []
    # 定义需要按照顺序提取的索引
    indices_first_pair = [1, 3]
    indices_second_pair = [5, 7]

    # 从A中提取数据,先遍历索引1和3,然后遍历索引5和7
    features_from_A_first = [platform_1[j][idx] for j in range(6) for idx in indices_first_pair]
    features_from_A_second = [platform_1[j][idx] for j in range(6) for idx in indices_second_pair]
    features_from_A = features_from_A_first + features_from_A_second

    # 从B中提取数据,同样先遍历索引1和3,然后遍历索引5和7
    features_from_B_first = [platform_2[j][idx] for j in range(6) for idx in indices_first_pair]
    features_from_B_second = [platform_2[j][idx] for j in range(6) for idx in indices_second_pair]
    features_from_B = features_from_B_first + features_from_B_second

    # 将两个列表的数据合并到一个列表中
    features = features_from_A + features_from_B

    return features


if __name__ == "__main__":

    # 设置服务器默认端口号
    PORT = 7889
    # 创建一个套接字socket对象,用于进行通讯
    # socket.AF_INET 指明使用INET地址集,进行网间通讯
    # socket.SOCK_DGRAM 指明使用数据协议,即使用传输层的udp协议
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # address = ("192.168.10.45", PORT)address = ("192.168.10.11", PORT)
    address = ("0.0.0.0", PORT)
    # 为服务器绑定一个固定的地址,ip和端口
    server_socket.bind(address)
    # 接收客户端传来的数据 recvfrom接收客户端的数据,默认是阻塞的,直到有客户端传来数据
    # recvfrom 参数的意义,表示最大能接收多少数据,单位是字节
    # recvfrom返回值说明
    # receive_data表示接受到的传来的数据,是bytes类型, receive_data.decode()解码,将bytes类型转换为字符串类型
    # client_address 表示传来数据的客户端的身份信息,客户端的ip和端口,元组
    print("等待接受客户端发送的消息...")
    # 数据预处理
    # 加载预处理模型
    # 数据归一化和标准化

    with open('hangyuan_double/firststandard.pkl', 'rb') as f:
        ssx = pickle.load(f)
        ssy = pickle.load(f)
    with open('hangyuan_double/secondstandard.pkl', 'rb') as f:
        ssx1 = pickle.load(f)
        ssy1 = pickle.load(f)

    onnx_file_name1 = "hangyuan_double/modified_model1.onnx"
    # onnxruntime.InferenceSession用于获取一个 ONNX Runtime 推理器
    ort_session1 = onnxruntime.InferenceSession(onnx_file_name1)

    onnx_file_name2 = "hangyuan_double/model2.onnx"
    # onnxruntime.InferenceSession用于获取一个 ONNX Runtime 推理器
    ort_session2 = onnxruntime.InferenceSession(onnx_file_name2)

    #isDataError = False
    while True:
        receive_data, client = server_socket.recvfrom(1024)

        print("来自客户端%s,发送的%s" % (client, receive_data.decode()))
        print(client[1], type(client))

        receive_data = json.loads(receive_data.decode())
        print(receive_data, type(receive_data))

        id = receive_data["id"]
        platform_1 = receive_data["platform"][0]["data"]
        platform_2 = receive_data["platform"][1]["data"]

        predict_data1 = []
        features = getFeatures(platform_1, platform_2)
        predict_data1.append(features)
      
        predict_data_ss1 = ssx.transform(predict_data1)  # 数据标准化

        input_name = ort_session1.get_inputs()[0].name
        input_shape = ort_session1.get_inputs()[0].shape
        #output_name = ort_session1.get_outputs()[0].name
        output_name = 'StatefulPartitionedCall/sequential_2/dense_12/Relu:0'

        sample_test = predict_data_ss1.reshape(1, 48).astype(np.float32)
        ort_inputs = {input_name: sample_test}
        ort_output1 = ort_session1.run([output_name], ort_inputs)
        intermediate_output1 = ort_output1[0]
        #print(intermediate_output1)

        #标定系数获取方式
        exo1 = [[1.05433692]]
        # 生成两列冲击次数
        # 新野水台子冲击次数=28天(过车时间(2024年1月23日)-建站时间(2023年12月26日))*日均通行量(3000)*4
        impact1 = [[336000]]
        impact2 = [[336000]]
        # 将冲击次数(X2、中间层输出(intermediate_output1)、标定系数(exo1)拼接在一起)、
        finalx = np.hstack((impact1, impact2, intermediate_output1, exo1))
        # 数据标准化
        finalx_ss = ssx1.transform(finalx)

        sample_test2 = finalx_ss.reshape(1, 99).astype(np.float32)

        ort_inputs2 = {ort_session2.get_inputs()[0].name: sample_test2}
        ort_output2 = ort_session2.run(None, ort_inputs2)[0]


        fpre_result_in = ssy1.inverse_transform(ort_output2)
        print(fpre_result_in)

        predict_value = fpre_result_in[0].tolist()

        send_data = {"cmd": "prediction", "id": id, "weight": predict_value}
        server_socket.sendto(json.dumps(send_data).encode('utf-8'), client)
    # 不再接收数据的时候,将套接字socket关闭
    server_socket.close()

客户端(采集器)

import socket
import json


def start_udp_client():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket:
        # 创建一个JSON对象并转换为字符串
        #json_data = {"name": "Alice123", "age123": 30}
        json_data = {"cmd":"origin",
                     "id":14225706,
                     "direction":0,
                     "speed":55,
                     "carOnlyoneId":-110808975,
                     "platform":[{"id":9,
                                  "car_wheelnumber_total":24393,
                                  "data":[[3141,1653,495,0,59,1491,307,30,61],  [2494,1301,-3,214,57,1196,-29,262,57],
                                          [2639,1471,178,306,57,1172,183,349,57],[3047,1747,177,687,60,1307,104,732,62],
                                          [3881,2156,259,778,61,1729,72,822,64],  [3895,2206,369,867,60,1693,118,911,62]]},
                                 {"id":10,
                                 "car_wheelnumber_total":16106,
                                 "data":[[3254,1492,111,0,63,1766,146,42,65],[3204,1343,265,227,57,1865,-18,268,59],
                                         [2678,1281,118,313,59,1401,346,353,58],[3580,1995,196,695,60,1588,169,736,61],
                                         [3355,1882,248,785,60,1475,92,826,62], [3719,1901,164,874,61,1820,262,915,63]]}]
        }
        data = json.dumps(json_data).encode()

        #client_socket.sendto(data, ('localhost', 12345))  # 发送数据到服务器
        client_socket.sendto(data, ('localhost', 7889))  # 发送数据到服务器

        # 由于UDP是无连接的,我们可能需要循环接收响应或设置超时
        response, server_addr = client_socket.recvfrom(1024)  # 接收响应
        print("Received response from server:", response.decode())

        # 解析响应的JSON数据
        response_json = json.loads(response.decode())
        print("Response JSON:", response_json)


if __name__ == "__main__":
    start_udp_client()
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值