TensorFlow模型部署FastAPI遇Dictionary Update Sequence Error求助
问题
在Google Colab训练了TensorFlow模型,本地运行可正常预测,但部署到FastAPI后,传入相同DataFrame数据时报错。错误日志显示Dictionary Update Sequence Error及vars()参数无__dict__属性,推测和输入维度有关,但本地使用相同数据时模型可成功预测。以下是相关代码和错误日志:
完整错误日志
INFO: Started server process [283] INFO:uvicorn.error:Started server process [283] INFO: Waiting for application startup. INFO:uvicorn.error:Waiting for application startup. Public URL: NgrokTunnel: "https://f287-35-238-142-115.ngrok.io" -> "http://localhost:10000" INFO: Application startup complete. INFO:uvicorn.error:Application startup complete. INFO: Uvicorn running on http://127.0.0.1:10000 (Press CTRL+C to quit) INFO:uvicorn.error:Uvicorn running on http://127.0.0.1:10000 (Press CTRL+C to quit) 1/1 [==============================] - 0s 68ms/step INFO: 54.86.50.139:0 - "POST /predict HTTP/1.1" 500 Internal Server Error ERROR: Exception in ASGI application Traceback (most recent call last): File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 152, in jsonable_encoder data = dict(obj) ValueError: dictionary update sequence element #0 has length 1; 2 is required During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 157, in jsonable_encoder data = vars(obj) TypeError: vars() argument must have __dict__ attribute The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/usr/local/lib/python3.8/dist-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi result = await app(self.scope, self.receive, self.send) File "/usr/local/lib/python3.8/dist-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__ return await self.app(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/fastapi/applications.py", line 270, in __call__ await super().__call__(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/applications.py", line 124, in __call__ await self.middleware_stack(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/errors.py", line 184, in __call__ raise exc File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/errors.py", line 162, in __call__ await self.app(scope, receive, _send) File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/exceptions.py", line 79, in __call__ raise exc File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/exceptions.py", line 68, in __call__ await self.app(scope, receive, sender) File "/usr/local/lib/python3.8/dist-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__ raise e File "/usr/local/lib/python3.8/dist-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__ await self.app(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 706, in __call__ await route.handle(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 276, in handle await self.app(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 66, in app response = await func(request) File "/usr/local/lib/python3.8/dist-packages/fastapi/routing.py", line 255, in app content = await serialize_response( File "/usr/local/lib/python3.8/dist-packages/fastapi/routing.py", line 152, in serialize_response return jsonable_encoder(response_content) File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 117, in jsonable_encoder encoded_value = jsonable_encoder( File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 160, in jsonable_encoder raise ValueError(errors) from e ValueError: [ValueError('dictionary update sequence element #0 has length 1; 2 is required'), TypeError('vars() argument must have __dict__ attribute')] ERROR:uvicorn.error:Exception in ASGI application Traceback (most recent call last): File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 152, in jsonable_encoder data = dict(obj) ValueError: dictionary update sequence element #0 has length 1; 2 is required During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 157, in jsonable_encoder data = vars(obj) TypeError: vars() argument must have __dict__ attribute The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/usr/local/lib/python3.8/dist-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi result = await app(self.scope, self.receive, self.send) File "/usr/local/lib/python3.8/dist-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__ return await self.app(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/fastapi/applications.py", line 270, in __call__ await super().__call__(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/applications.py", line 124, in __call__ await self.middleware_stack(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/errors.py", line 184, in __call__ raise exc File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/errors.py", line 162, in __call__ await self.app(scope, receive, _send) File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/exceptions.py", line 79, in __call__ raise exc File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/exceptions.py", line 68, in __call__ await self.app(scope, receive, sender) File "/usr/local/lib/python3.8/dist-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__ raise e File "/usr/local/lib/python3.8/dist-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__ await self.app(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 706, in __call__ await route.handle(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 276, in handle await self.app(scope, receive, send) File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 66, in app response = await func(request) File "/usr/local/lib/python3.8/dist-packages/fastapi/routing.py", line 255, in app content = await serialize_response( File "/usr/local/lib/python3.8/dist-packages/fastapi/routing.py", line 152, in serialize_response return jsonable_encoder(response_content) File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 117, in jsonable_encoder encoded_value = jsonable_encoder( File "/usr/local/lib/python3.8/dist-packages/fastapi/encoders.py", line 160, in jsonable_encoder raise ValueError(errors) from e ValueError: [ValueError('dictionary update sequence element #0 has length 1; 2 is required'), TypeError('vars() argument must have __dict__ attribute')] INFO: Shutting down INFO:uvicorn.error:Shutting down INFO: Waiting for application shutdown. INFO:uvicorn.error:Waiting for application shutdown. INFO: Application shutdown complete. INFO:uvicorn.error:Application shutdown complete. INFO: Finished server process [283] INFO:uvicorn.error:Finished server process [283]
模型保存代码
import pickle Pkl_Filename = "model_tree.pkl" with open(Pkl_Filename, 'wb') as file: pickle.dump(NN_model, file)
FastAPI部署代码
class Music(BaseModel): SRC: float CN: float DS: float AGE: float MKE: float MDL: float CS: float FB: float CB: float M: float VC: float class Config: schema_extra = { "example": { "SRC": 1, "CN": 215, "DS": 16.0, "AGE": 34, "MKE": 13, "MDL": 399, "CS": 16, "FB": 1, "CB": 9, "M": 4, "VC": 4 } } from fastapi import FastAPI import pickle app = FastAPI() @app.on_event("startup") def load_model(): global model model = pickle.load(open("model_tree.pkl", "rb")) @app.get('/') def index(): return {'message': 'This is the homepage of the API '} @app.post('/predict') def get_music_category(data: Music): received = data.dict() SRC = received['SRC'] CN = received['CN'] DS = received['DS'] AGE = received['AGE'] MKE = received['MKE'] MDL = received['MDL'] CS = received['CS'] FB = received['FB'] CB = received['CB'] M = received['M'] VC = received['VC'] temp_list = [SRC, CN, DS, AGE, MKE, MDL, CS, FB, CB, M, VC] # temp_df = pd.DataFrame(temp_list).transpose() # temp_df = temp_df.astype(float) temp_df = pd.concat([pd.Series(x, name=f'column_{i}') for i,x in enumerate(temp_list)], axis=1) # df = pd.read_json(temp_df) pred_name = model.predict(temp_df) return {'prediction': pred_name}
解决方案
问题根源
报错并非输入维度问题,而是TensorFlow模型的预测结果无法被FastAPI的jsonable_encoder序列化为JSON。model.predict()返回的是TensorFlow的Tensor对象或numpy数组,这类对象无法直接被FastAPI转换成JSON格式,从而触发序列化错误。
另外,构建temp_df的方式冗余且可能导致列名不匹配模型训练时的特征名,但这不是当前报错的直接原因。
修复步骤
转换预测结果为可序列化格式
将model.predict()的结果转换为Python原生类型(如列表或标量):pred_name = model.predict(temp_df) # 转换为Python列表 pred_result = pred_name.tolist() # 单样本预测时取第一个元素,避免嵌套列表 pred_result = pred_result[0] if len(pred_result) == 1 else pred_result简化输入数据构建
替换复杂的pd.concat方式,直接创建符合模型输入要求的DataFrame,确保列名和训练时一致:# 需与模型训练时的特征列顺序保持一致 feature_cols = ["SRC", "CN", "DS", "AGE", "MKE", "MDL", "CS", "FB", "CB", "M", "VC"] temp_df = pd.DataFrame([received], columns=feature_cols)优化模型保存方式(可选但推荐)
不建议用pickle保存TensorFlow模型,官方推荐使用TensorFlow原生保存方式:# 保存模型 NN_model.save("model_tree") # 加载模型 from tensorflow.keras.models import load_model model = load_model("model_tree")
修改后的完整预测函数
@app.post('/predict') def get_music_category(data: Music): received = data.dict() feature_cols = ["SRC", "CN", "DS", "AGE", "MKE", "MDL", "CS", "FB", "CB", "M", "VC"] temp_df = pd.DataFrame([received], columns=feature_cols) pred_name = model.predict(temp_df) # 转换为可序列化的Python类型 pred_result = pred_name.tolist() # 处理单样本预测的嵌套列表问题 if len(pred_result) == 1 and isinstance(pred_result[0], list): pred_result = pred_result[0] return {'prediction': pred_result}
内容的提问来源于stack exchange,提问作者gmelgkermglermgmrelkgm
相关产品推荐
相关产品推荐

