You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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的输入处理逻辑:

  1. 更新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)
    
  2. 若使用MLflow自带服务,需额外编写Flask/FastAPI路由处理文件上传请求,替代默认服务的输入解析逻辑。

问题2:numpy数组输入不兼容

错误源于MLflow将numpy数组序列化为Pandas DataFrame,而模型期望四维数组格式。解决步骤:

  1. 调整输入格式:Postman发送JSON时,将numpy数组转为嵌套列表,示例:
    {
      "0": [[[0.1,0.2,0.3],...,[0.4,0.5,0.6]]]
    }
    
  2. 修改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)
    
  3. 添加模型签名:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 09:10:23