用Flask部署sklearn pipeline需手动解析JSON、确保二维输入、固定类别编码、禁用稀疏输出、校验拟合状态,并推荐用skops替代joblib以避免版本兼容问题。

用Flask快速包装sklearn.pipeline.Pipeline对象
直接把训练好的pipeline丢进Flask路由里调用是最常见做法,但要注意它不能直接处理JSON请求体。你需要手动解析request.json,再喂给pipeline.predict()或pipeline.predict_proba()。
常见错误是传入嵌套字典或单个样本不加外层列表——scikit-learn的predict()默认要求二维输入(哪怕只预测1条),所以[data]比data更安全。
- 确保
pipeline已用joblib.dump()保存,API启动时用joblib.load()加载,别在每次请求里重新加载 - 输入字段名必须和训练时一致;如果用了
ColumnTransformer,列顺序错一位就会报ValueError: X has 5 features, but ColumnTransformer is expecting 6 - 对数值型字段做类型强制转换:
float(request.json.get("age")),否则numpy可能因字符串输入崩在StandardScaler上
处理LabelEncoder或OneHotEncoder带来的编码不一致问题
训练时用LabelEncoder转过目标变量,不代表API能自动反解;更危险的是OneHotEncoder在训练时没见过的类别,线上遇到会直接抛ValueError: Found unknown categories。
解决方法不是关掉handle_unknown="ignore"就完事——它会让对应列全为0,影响预测逻辑。稳妥做法是:在训练前用pd.Categorical固定所有可能取值,或改用sklearn.preprocessing.OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1)。
立即学习“Python免费学习笔记(深入)”;
-
OneHotEncoder必须设sparse_output=False(新版本默认True),否则Flask返回JSON时会卡在TypeError: Object of type ndarray is not JSON serializable - 如果流水线末端接了
LogisticRegression,predict_proba()返回的是二维数组,取第一行要用[0].tolist(),不然JSON序列化失败 - 别在
transform()里偷偷改原始数据结构(比如删列),API输入字段数必须严格匹配训练时的n_features_in_
用sklearn.utils.validation.check_is_fitted()避免未拟合流水线上线
上线后发现预测全是0或报NotFittedError: This StandardScaler instance is not fitted yet,大概率是加载模型时路径写错,或者joblib.load()读到了空文件/旧版本pickle。
加一层运行时校验能提前暴露问题:
from sklearn.utils.validation import check_is_fitted
try:
check_is_fitted(pipeline.named_steps['scaler'])
except NotFittedError:
raise RuntimeError("Pipeline step 'scaler' is not fitted — check model file integrity")- 对每个关键步骤(如
scaler、encoder、classifier)单独校验,比只查pipeline顶层更准 - 不要依赖
hasattr(pipeline, "classes_")判断是否拟合——有些步骤(如StandardScaler)根本没有classes_属性 - 本地测试时用
pipeline.score(X_test, y_test)跑一遍,比单纯“能启动”更有意义
绕过Pickle兼容性坑:用skops保存/加载流水线
用joblib保存的模型,在Python 3.9训练、3.12环境加载可能出ModuleNotFoundError或AttributeError;换pickle更糟,连同版本都得一模一样。
skops把模型转成标准.skops格式,本质是带签名的ZIP+JSON元数据,加载时不依赖原训练环境的包版本:
import skops.io as sio
sio.dump(pipeline, "model.skops")
# 部署机上
pipeline = sio.load("model.skops", trusted=True)- 必须加
trusted=True,否则skops拒绝加载任意代码(安全机制) - 它不支持自定义类(比如你写了继承
TransformerMixin的类),只认scikit-learn官方组件 - 加载后仍要走
check_is_fitted(),skops只保结构不保状态
真正麻烦的从来不是“怎么搭个接口”,而是训练环境和生产环境之间那几行没写进文档的requirements.txt差异,还有那个谁都没动过、但悄悄被Git LFS截断了的.joblib文件。


















