使用ONNX推理报错:输入缺失,求Keras模型输入签名配置方法
鸢尾花Keras模型转ONNX后的输入签名问题解决
问题描述
基于鸢尾花数据集构建并训练Keras序列模型后,转换为ONNX格式时遇到输入签名配置问题,推理时报错:
ValueError: Required inputs (['x']) are missing from input feed (['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)'])
尝试将输入签名修改为4个TensorSpec(对应4个特征列)后仍未解决问题,完整脚本如下:
import numpy as np import pandas as pd import matplotlib.pyplot as plt import onnxruntime as rt import tf2onnx import onnx plt.style.use('ggplot') from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder, StandardScaler iris = load_iris() X = iris['data'] y = iris['target'] names = iris['target_names'] feature_names = iris['feature_names'] # One hot encoding enc = OneHotEncoder() Y = enc.fit_transform(y[:, np.newaxis]).toarray() # Scale data to have mean 0 and variance 1 # which is importance for convergence of the neural network scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Split the data set into training and testing X_train, X_test, Y_train, Y_test = train_test_split( X_scaled, Y, test_size=0.5, random_state=2) n_features = X.shape[1] n_classes = Y.shape[1] # Visualize the data sets plt.figure(figsize=(16, 6)) plt.subplot(1, 2, 1) for target, target_name in enumerate(names): X_plot = X[y == target] plt.plot(X_plot[:, 0], X_plot[:, 1], linestyle='none', marker='o', label=target_name) plt.xlabel(feature_names[0]) plt.ylabel(feature_names[1]) plt.axis('equal') plt.legend(); plt.subplot(1, 2, 2) for target, target_name in enumerate(names): X_plot = X[y == target] plt.plot(X_plot[:, 2], X_plot[:, 3], linestyle='none', marker='o', label=target_name) plt.xlabel(feature_names[2]) plt.ylabel(feature_names[3]) plt.axis('equal') plt.legend(); # In order to ignore FutureWarning import warnings warnings.simplefilter(action='ignore', category=FutureWarning) warnings.simplefilter(action='ignore', category=DeprecationWarning) import tensorflow as tf def create_custom_model(input_dim, output_dim, nodes, n=1, name='model'): def create_model(): # Create model model = tf.keras.Sequential(name=name) for i in range(n): model.add(tf.keras.layers.Dense(nodes, input_dim=input_dim, activation='relu')) model.add(tf.keras.layers.Dense(output_dim, activation='softmax')) # Compile model model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) return model return create_model models = [create_custom_model(n_features, n_classes, 8, i, 'model_{}'.format(i)) for i in range(1, 4)] for create_model in models: create_model().summary() history_dict = {} # TensorBoard Callback cb = tf.keras.callbacks.TensorBoard() for create_model in models: model = create_model() print('Model name:', model.name) history_callback = model.fit(X_train, Y_train, batch_size=5, epochs=50, verbose=0, validation_data=(X_test, Y_test), callbacks=[cb]) score = model.evaluate(X_test, Y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1]) history_dict[model.name] = [history_callback, model] create_model = create_custom_model(n_features, n_classes, 8, 3) model = history_dict["model_3"][1] #model 3 was the most accurate model.save("Iris.keras") input_signature = [tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype, name="x")] model.output_names=['output'] onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature, opset=13) onnx.save(onnx_model, "Irismodel.onnx") sess = rt.InferenceSession("Irismodel.onnx") input_name = sess.get_inputs()[0].name label_name = sess.get_outputs()[0].name dataset = pd.DataFrame({"sepal length (cm)" : X_test[:,0],"sepal width (cm)": X_test[:,1], "petal length (cm)": X_test[:,2], "petal width (cm)": X_test[:,3]}) pred = sess.run([label_name], dataset)
核心问题分析
- 模型输入结构不匹配:你创建的Keras序列模型是单输入张量结构(形状为
(batch_size, 4),包含4个特征),但推理时错误地将4个特征拆分为独立列名传递,导致输入名与ONNX模型期望的x不匹配。 - 错误的输入签名修改:将输入签名改为4个TensorSpec会强制模型接受4个独立输入,但原Keras模型并未设计成多输入结构,转换后的ONNX模型会期望4个输入张量,而你传递的DataFrame列数据是一维的,不符合模型要求的二维张量形状。
解决方案
方案1:保持原单输入模型结构(推荐)
不需要修改模型,只需调整推理时的输入传递方式,确保输入名和张量形状与ONNX模型一致:
修改脚本最后13行代码为:
model = history_dict["model_3"][1] # model 3 was the most accurate model.save("Iris.keras") # 保持单输入签名,名字设为"x"(与ONNX模型输入名一致) input_signature = [tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype, name="x")] model.output_names=['output'] onnx_model, _ = tf2onnx.convert.from_keras(model, input_signature, opset=13) onnx.save(onnx_model, "Irismodel.onnx") sess = rt.InferenceSession("Irismodel.onnx") input_name = sess.get_inputs()[0].name label_name = sess.get_outputs()[0].name # 直接传递X_test作为输入(形状为(N,4),与训练时格式一致) pred = sess.run([label_name], {input_name: X_test}) # 可选:将输出转换为预测类别 pred_classes = np.argmax(pred[0], axis=1) print(pred_classes)
方案2:修改模型为多命名输入(如需按特征名传递)
如果需要按特征列名作为输入名传递数据,需重构Keras模型为多输入结构:
- 修改模型创建函数:
def create_custom_model(input_dim, output_dim, nodes, n=1, name='model'): def create_model(): # 定义4个带名字的输入层,每个特征对应一个输入 inputs = [ tf.keras.Input(shape=(1,), name="sepal length (cm)"), tf.keras.Input(shape=(1,), name="sepal width (cm)"), tf.keras.Input(shape=(1,), name="petal length (cm)"), tf.keras.Input(shape=(1,), name="petal width (cm)") ] # 将4个输入拼接为单张量,供后续Dense层处理 x = tf.keras.layers.concatenate(inputs) for i in range(n): x = tf.keras.layers.Dense(nodes, activation='relu')(x) output = tf.keras.layers.Dense(output_dim, activation='softmax')(x) # 创建多输入模型 model = tf.keras.Model(inputs=inputs, outputs=output, name=name) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) return model return create_model
- 调整训练时的输入传递:
# 将训练数据拆分为字典格式,对应每个输入名 train_inputs = { "sepal length (cm)": X_train[:, 0:1], "sepal width (cm)": X_train[:, 1:2], "petal length (cm)": X_train[:, 2:3], "petal width (cm)": X_train[:, 3:4] } # 训练模型 history_callback = model.fit(train_inputs, Y_train, batch_size=5, epochs=50, verbose=0, validation_data=({ "sepal length (cm)": X_test[:, 0:1], "sepal width (cm)": X_test[:, 1:2], "petal length (cm)": X_test[:, 2:3], "petal width (cm)": X_test[:, 3:4] }, Y_test), callbacks=[cb])
- 推理时按特征名传递数据:
# 转换为ONNX时无需额外指定输入签名(自动匹配输入层名字) onnx_model, _ = tf2onnx.convert.from_keras(model, opset=13) onnx.save(onnx_model, "Irismodel.onnx") sess = rt.InferenceSession("Irismodel.onnx") label_name = sess.get_outputs()[0].name # 按特征名构造输入字典 pred_inputs = { "sepal length (cm)": X_test[:, 0:1], "sepal width (cm)": X_test[:, 1:2], "petal length (cm)": X_test[:, 2:3], "petal width (cm)": X_test[:, 3:4] } pred = sess.run([label_name], pred_inputs) pred_classes = np.argmax(pred[0], axis=1) print(pred_classes)
内容的提问来源于stack exchange,提问作者Some Dude
相关产品推荐
相关产品推荐

