基于Pickle的图像分类模型Docker部署后推理报错求助
图像分类模型Docker部署后API调用问题排查
问题概述
基于Pickle文件构建图像分类项目,加载模型后通过Docker部署,使用Postman调用localhost+端口时遇到两类问题:
- 传入图片时,提示预测器仅支持text/csv、application/json等类型与pandas-records等格式,不支持当前的multipart/form-data类型;
- 传入numpy数组时,出现错误:
"Encountered an unexpected error while evaluating the model. Verify that the serialized input Dataframe is compatible with the model for inference."
相关代码文件
run.py
import pickletools import model_service import pickle import mlflow from PIL import Image import numpy as np from keras.utils import image_utils import requests from mlflow.models.signature import infer_signature import tensorflow as tf print('FIRST') class ModelFunction: def __init__(self): self.model_service_obj = model_service.MlflowModelService() def store_model(self,pickle_file): #pickle file will be in string format print('inside store_model...') infile = open(pickle_file,'rb+') model = pickle.load(infile) #load is used to load pickled data from a file-like object print('model pickle file loaded....') self.model_service_obj.saveModel(model,"keras","ImageClassification","preprocess.py") def load_model(self): self.model = self.model_service_obj.loadModel("ImageClassification","1") return self.model class_labels=[ "Plane", "Car", "Bird", "Cat", "Deer", "Dog", "Frog", "Horse", "Horse", "Boat", "Truck" ] obj = ModelFunction() obj.store_model("image_pickle.pkl") model = obj.load_model() img=image_utils.load_img("frog.jpg",target_size=(32,32)) image_to_test=image_utils.img_to_array(img)/255 list_of_images=np.expand_dims(image_to_test,axis=0) #predict results=model.predict(list_of_images) single_result=results[0] most_likely_class_index=int(np.argmax(single_result)) class_likelihood=single_result[most_likely_class_index] class_label=class_labels[most_likely_class_index] print("This is image is a {} - Likelihood: {:2f}".format(class_label,class_likelihood))
model_service.py
from asyncore import read import logging import pickle import mlflow import wrapper import os import sqlite3 import sqlalchemy import sys from mlflow.models.signature import infer_signature #Steps: # 1.save Model # 2.log model - set conda env , mlflow save model , mlflow log model # 3.register model # 4.load model class MlflowModelService: def saveModel(self,model,variant,readable_model_id,preprocess_file_path=None): print('inside storeModel of model service.......') readable_model_id = readable_model_id.replace("/","__$__") model_name = "Original-Model" with mlflow.start_run() as active_run:#mlflow work starts active_run = mlflow.active_run() mlflow.keras.save_model(model,model_name) #Save a scikit-learn model to a path on the local file system pyfunc_model_uri = self.logModel(readable_model_id,model_name,preprocess_file_path) self.registerModel(pyfunc_model_uri,readable_model_id) def loadModel(self,readable_model_id,version): model_uri = 'models:/' + str(readable_model_id) + "/" + version print('model uri of loadmodel=='+ model_uri) self.model = mlflow.pyfunc.load_model(model_uri) return self.model def logModel(self,readable_model_id,model_name,preprocess_file_path): artifacts = { "Original_Model":model_name, "Original-model":preprocess_file_path } model_data = mlflow.pyfunc.log_model( artifact_path=str(readable_model_id), python_model= wrapper.Model_Wrapper(), artifacts=artifacts, code_path= ["wrapper.py"] #signature=infer_signature ) return model_data.model_uri def registerModel(self,model_uri,readable_model_id): model_data = mlflow.register_model(model_uri,readable_model_id)
wrapper.py
import mlflow import os import importlib.util import preprocess import tensorflow as tf class Model_Wrapper(mlflow.pyfunc.PythonModel): def __init__(self): print("Directory ----> ",os.getcwd()) def load_context(self,context): self.model=mlflow.pyfunc.load_model(context.artifacts["Original_Model"]) def predict(self, context, model_input): print ("Invoking predict with \n", model_input) if "Preprocessor_file" in context.artifacts.keys(): spec = importlib.util.spec_from_file_location("preprocess", context.artifacts["Preprocessor_file"]) preprocessor = importlib.util.module_from_spec(spec) spec.loader.exec_module(preprocessor) if "user_data" in context.artifacts.keys(): os.chdir(context.artifacts["user_data"]) model_input = preprocessor.preprocess(model_input) return self.model.predict(model_input)
问题排查与解决方案
问题1:不支持multipart/form-data格式
MLflow默认PyFunc服务仅支持结构化数据(JSON/CSV),不直接处理文件上传。需修改模型Wrapper的输入处理逻辑:
- 更新Wrapper的predict方法,添加图片二进制数据解析逻辑:
def predict(self, context, model_input): # 兼容两种输入:JSON数组转来的DataFrame,或二进制图片 import io from PIL import Image import numpy as np import pandas as pd if isinstance(model_input, pd.DataFrame): # 处理数组类输入 model_input = model_input.values.reshape(-1, 32, 32, 3) else: # 处理multipart/form-data传入的图片二进制 img = Image.open(io.BytesIO(model_input)) img = img.resize((32,32)) model_input = np.array(img)/255.0 model_input = np.expand_dims(model_input, axis=0) # 原有预处理逻辑 if "Original-model" in context.artifacts.keys(): spec = importlib.util.spec_from_file_location("preprocess", context.artifacts["Original-model"]) preprocessor = importlib.util.module_from_spec(spec) spec.loader.exec_module(preprocessor) model_input = preprocessor.preprocess(model_input) return self.model.predict(model_input) - 若使用MLflow自带服务,需额外编写Flask/FastAPI路由处理文件上传请求,替代默认服务的输入解析逻辑。
问题2:numpy数组输入不兼容
错误源于MLflow将numpy数组序列化为Pandas DataFrame,而模型期望四维数组格式。解决步骤:
- 调整输入格式:Postman发送JSON时,将numpy数组转为嵌套列表,示例:
{ "0": [[[0.1,0.2,0.3],...,[0.4,0.5,0.6]]] } - 修改Wrapper的predict方法,将DataFrame转回模型需要的数组形状:
def predict(self, context, model_input): import pandas as pd import numpy as np if isinstance(model_input, pd.DataFrame): # 还原为(None,32,32,3)的输入形状 model_input = model_input.values.reshape(-1, 32, 32, 3) # 原有预处理与预测逻辑 if "Original-model" in context.artifacts.keys(): spec = importlib.util.spec_from_file_location("preprocess", context.artifacts["Original-model"]) preprocessor = importlib.util.module_from_spec(spec) spec.loader.exec_module(preprocessor) model_input = preprocessor.preprocess(model_input) return self.model.predict(model_input) - 添加模型签名:在
model_service.py的logModel方法中,明确输入输出格式,让MLflow自动校验:def logModel(self,readable_model_id,model_name,preprocess_file_path): # 生成模型签名 import numpy as np from mlflow.models.signature import infer_signature input_example = np.random.rand(1,32,32,3) # 加载原模型获取输出示例 temp_model = mlflow.keras.load_model(model_name) signature = infer_signature(input_example, temp_model.predict(input_example)) artifacts = { "Original_Model":model_name, "Original-model":preprocess_file_path } model_data = mlflow.pyfunc.log_model( artifact_path=str(readable_model_id), python_model= wrapper.Model_Wrapper(), artifacts=artifacts, code_path= ["wrapper.py"], signature=signature ) return model_data.model_uri
额外优化点
- 修正
wrapper.py的模型加载逻辑:原模型是Keras模型,需用mlflow.keras.load_model替代mlflow.pyfunc.load_model:def load_context(self,context): self.model = mlflow.keras.load_model(context.artifacts["Original_Model"]) - Docker部署时,确保
requirements.txt包含所有依赖:pillow、numpy、tensorflow、mlflow、pandas等。
内容的提问来源于stack exchange,提问作者Ashu mishra
相关产品推荐
相关产品推荐

