第86课 - 模型持久化:Pickle、Joblib 与 ONNX
学习目标
完成本课后,你将能够:
- 理解模型持久化的重要性及其在机器学习工作流中的作用。
- 掌握使用Pickle和Joblib保存与加载Scikit-learn模型的标准流程。
- 了解ONNX格式的优势,并能将一个训练好的模型转换为ONNX格式。
- 根据项目需求选择合适的持久化方案,并规避常见的陷阱。
核心概念
想象一下,你花了一整天时间精心训练了一个机器学习模型,它表现优异。现在,你需要把这份“智能”分享给同事,或者部署到服务器上,让它处理真实世界的请求。但你总不能每次都从头开始训练吧?这就需要用到模型持久化——简单来说,就是把训练好的模型“保存”成一个文件,下次需要时再“加载”回来,即装即用。
在Python的机器学习生态中,我们有三种主流的选择:
-
Pickle:Python的内置序列化模块。它就像一个万能文件夹,可以把几乎任何Python对象(包括我们的模型)打包存起来。优点是简单、无需额外安装;缺点是对大型NumPy数组的序列化效率不是最高,且跨不同Python版本或库版本时可能有兼容性问题。
-
Joblib:专门为高效序列化包含大量NumPy数组的对象(如Scikit-learn模型)而优化的库。它通常比Pickle更快,生成的文件也更小,是保存Scikit-learn模型的推荐选择。可以把它看作是一个“优化版文件夹”,专门为科学计算数据打包。
-
ONNX (Open Neural Network Exchange):这是一个开放的模型表示标准。它就像一个“通用翻译器”,无论你的模型是用PyTorch、TensorFlow还是Scikit-learn训练的,都可以转换成ONNX格式。转换后的模型可以在任何支持ONNX的运行时(如ONNX Runtime)中运行,实现跨框架、跨平台部署,是生产环境部署的利器。
代码示例
下面,我们将以一个经典的鸢尾花分类模型为例,演示三种持久化方法。
准备工作:训练一个模型
首先,我们训练一个简单的模型作为后续操作的对象。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import numpy as np
# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练一个随机森林分类器
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# 评估一下
y_pred = model.predict(X_test)
print(f"模型在测试集上的准确率: {accuracy_score(y_test, y_pred):.2f}")
方法一:使用Pickle保存与加载
import pickle
# 1. 保存模型到文件
with open('iris_model.pkl', 'wb') as f: # 'wb' 表示以二进制写模式打开
pickle.dump(model, f)
print("模型已使用Pickle保存至 iris_model.pkl")
# 2. 从文件加载模型
with open('iris_model.pkl', 'rb') as f: # 'rb' 表示以二进制读模式打开
loaded_model = pickle.load(f)
# 3. 验证加载的模型
loaded_pred = loaded_model.predict(X_test)
print(f"加载后的模型准确率: {accuracy_score(y_test, loaded_pred):.2f}")
方法二:使用Joblib保存与加载(推荐用于Scikit-learn)
import joblib
# 1. 保存模型到文件
joblib.dump(model, 'iris_model.joblib')
print("模型已使用Joblib保存至 iris_model.joblib")
# 2. 从文件加载模型
loaded_model_joblib = joblib.load('iris_model.joblib')
# 3. 验证加载的模型
loaded_pred_joblib = loaded_model_joblib.predict(X_test)
print(f"使用Joblib加载的模型准确率: {accuracy_score(y_test, loaded_pred_joblib):.2f}")
# 可以顺便比较一下文件大小
import os
pkl_size = os.path.getsize('iris_model.pkl')
joblib_size = os.path.getsize('iris_model.joblib')
print(f"Pickle文件大小: {pkl_size} bytes, Joblib文件大小: {joblib_size} bytes")
方法三:使用ONNX转换与推理
这需要先安装 sklearn-onnx 和 onnxruntime。
pip install sklearn-onnx onnxruntime
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
import onnxruntime as rt
# 1. 将Scikit-learn模型转换为ONNX
# 需要指定输入数据的类型和形状(样本数可以是None)
initial_type = [('float_input', FloatTensorType([None, 4]))]
onnx_model = convert_sklearn(model, initial_types=initial_type)
# 2. 将ONNX模型保存为文件
with open("iris_model.onnx", "wb") as f:
f.write(onnx_model.SerializeToString())
print("模型已转换并保存为ONNX格式: iris_model.onnx")
# 3. 使用ONNX Runtime进行推理
# 创建推理会话
sess = rt.InferenceSession("iris_model.onnx")
# 获取输入名称
input_name = sess.get_inputs()[0].name
label_name = sess.get_outputs()[0].name
# 准备输入数据(需要是NumPy数组,且类型为float32)
X_test_onnx = X_test.astype(np.float32)
# 进行推理
pred_onnx = sess.run([label_name], {input_name: X_test_onnx})[0]
print(f"使用ONNX Runtime的预测结果形状: {pred_onnx.shape}")
print(f"前5个样本的预测: {pred_onnx[:5]}")
实践练习
练习1:基础操作
- 加载
sklearn.datasets中的digits手写数字数据集。 - 使用
SVM分类器(sklearn.svm.SVC)进行训练。 - 分别使用
Pickle和Joblib将训练好的模型保存为digits_svm.pkl和digits_svm.joblib。 - 编写代码加载这两个模型,并在测试集上验证它们的准确率是否一致。
练习2:封装函数
创建一个函数 save_load_model(model, filename, method='joblib'),它可以接收一个模型对象、文件名和方法('pickle' 或 'joblib'),并完成保存和加载的全过程,最后返回加载后的模型。测试该函数。
练习3:探索ONNX(进阶)
尝试将你在练习1中训练的SVM模型转换为ONNX格式。
- 注意,SVM模型在转换时可能需要指定其他参数(如
options)。 - 使用
onnxruntime加载转换后的模型,并在测试集上计算准确率,与原始Scikit-learn模型的结果对比。 - (可选)比较三种格式(
.pkl,.joblib,.onnx)文件的大小。
常见错误
-
忘记保存预处理器:模型通常需要和预处理步骤(如
StandardScaler,OneHotEncoder)一起工作。只保存模型而不保存预处理器,会导致在新数据上推理失败。解决方案是使用Pipeline,并将整个Pipeline一起保存。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler pipe = Pipeline([('scaler', StandardScaler()), ('model', RandomForestClassifier())]) pipe.fit(X_train, y_train) joblib.dump(pipe, 'full_pipeline.joblib') # 保存整个Pipeline -
版本不兼容:使用Pickle或Joblib加载模型时,如果Pandas、NumPy、Scikit-learn等库的版本与保存时不一致,可能引发错误。在团队协作或部署时,务必记录并保持环境一致性,或使用Docker容器化。
-
ONNX操作符不支持:不是所有模型或操作都能顺利转换为ONNX。一些自定义的转换器或特定的模型参数可能不受支持,转换时会报错。需要查阅
sklearn-onnx的文档,看是否有对应的转换器。 -
文件路径错误:保存和加载时使用了不同的路径,或者文件名拼写错误。建议使用绝对路径或明确的工作目录管理。
-
使用Pickle加载不受信任的文件:Pickle文件可以包含恶意代码,切勿加载来源不明的.pkl文件。Joblib和ONNX相对安全,但在生产环境中,任何文件都应来自可信源。
小结
本课我们学习了将训练好的机器学习模型“固化”下来的关键技术:
- Pickle:Python原生,简单通用,适合快速原型和简单的模型序列化。
- Joblib:针对科学计算优化,是保存Scikit-learn模型的首选,效率更高。
- ONNX:一种开放的中间表示格式,核心优势是跨平台、跨框架,是模型部署到生产环境(特别是移动端、嵌入式设备或不同技术栈的服务器)的强大工具。
选择建议:
- 对于实验、开发和简单的模型共享,使用 Joblib。
- 当你需要将模型部署到非Python环境、或追求极致的推理性能和跨语言支持时,选择 ONNX。
- 永远记住保存与模型配套的预处理流程,并保持运行环境的一致性。
掌握模型持久化,你才能将机器学习的成果从“实验室”带到“现实世界”。下一课,我们将学习如何用Flask将保存好的模型包装成API,实现真正的服务化部署。